diff --git a/hiboo/account/login.py b/hiboo/account/login.py index 316189eb545682dca2bf8e06b88bec27b8e01179..590ade9fc4dae9d21f67bb0b814221cca3906dda 100644 --- a/hiboo/account/login.py +++ b/hiboo/account/login.py @@ -2,6 +2,7 @@ from hiboo import models, utils, security from hiboo.account import blueprint, forms from flask_babel import lazy_gettext as _ from flask import session +from authlib.jose import JsonWebToken import datetime import flask_login @@ -32,6 +33,20 @@ def signout(): @blueprint.route("/signup", methods=["GET", "POST"]) def signup(): + if not flask.current_app.config['OPEN_SIGNUP']: + token = flask.request.args.get('token') or flask.abort(403) + key = flask.current_app.config["SECRET_KEY"] + jwt = JsonWebToken(['HS512']) + claims_options = { + 'exp': {'essential': True, 'value': datetime.datetime.now().timestamp()}, + 'aud': {'essential': True, 'value': flask.url_for('.signup')} + } + try: + claims = jwt.decode(token, key, claims_options=claims_options) + claims.validate() + except Exception as e: + flask.flash(_("Invalid or expired signup link"), "danger") + return flask.redirect(flask.url_for(".signin")) form = forms.SignupForm() if form.validate_on_submit(): conflict = models.User.query.filter_by(username=form.username.data).first() @@ -72,4 +87,4 @@ def reset(token_uuid): models.db.session.commit() flask.flash(_("Successfully reset your password"), "success") return flask.redirect(flask.url_for(".signin")) - return flask.render_template("account_reset.html", form=form) \ No newline at end of file + return flask.render_template("account_reset.html", form=form) diff --git a/hiboo/application/social.py b/hiboo/application/social.py index 60e7b7411289a09c06eb007faf7e585922b3a05a..2a632be6ba9c2a010addea01d7de191d69bb5f9e 100644 --- a/hiboo/application/social.py +++ b/hiboo/application/social.py @@ -43,8 +43,8 @@ class SynapseApplication(base.SAMLApplication): def configure(self, form): return { - "acs": form.application_uri.data + "/_matrix/saml2/authn_response", - "entityid": form.application_uri.data + "/_matrix/saml2/metadata.xml", + "acs": form.application_uri.data + "/_synapse/client/saml2/authn_response", + "entityid": form.application_uri.data + "/_synapse/client/saml2/metadata.xml", "sign_mode": "response" } diff --git a/hiboo/application/templates/application_peertube.html b/hiboo/application/templates/application_peertube.html index 2128c25f9ecca97b44014badc855d45cb14e228d..5966d10bb190f6ad288199de99c39b2ed0d631da 100644 --- a/hiboo/application/templates/application_peertube.html +++ b/hiboo/application/templates/application_peertube.html @@ -6,10 +6,10 @@ <dd><pre>{{ url_for("sso.oidc_discovery", service_uuid=service.uuid, _external=True) }}</pre></dd> <dt>Client ID</dt> -<dd><pre>service.config["client_id"]</pre></dd> +<dd><pre>{{ service.config["client_id"] }}</pre></dd> <dt>Client secret</dt> -<dd><pre>service.config["client_secret"]</pre></dd> +<dd><pre>{{ service.config["client_secret"] }}</pre></dd> <dt>Scope</dt> <dd><pre>openid email profile</pre></dd> @@ -20,4 +20,4 @@ <dt>Email property</dt> <dd><pre>email</pre></dd> -{% include "application_oidc.html" %} \ No newline at end of file +{% include "application_oidc.html" %} diff --git a/hiboo/cli.py b/hiboo/cli.py index e9cf242a845f5f82310cfb85dfc225bf567470d6..e9e70ea69d98a4aa91553de627e56306d0af0833 100644 --- a/hiboo/cli.py +++ b/hiboo/cli.py @@ -1,6 +1,9 @@ from flask import cli +from hiboo import models from hiboo.profile import common +import time + tasks = cli.AppGroup("tasks") @@ -19,3 +22,10 @@ def tasks_loop(): while True: common.apply_all_transitions() time.sleep(30) + + +@tasks.command("purge") +def purge(): + # Temporary command to list purgeable accounts before we include + # this in the main task loop + print(models.User.get_unused().all()) diff --git a/hiboo/configuration.py b/hiboo/configuration.py index e36dbe777df23e60bcdd034fe57bc4df10f93e3f..a2005a9e3ba66818fc4abea8068d6d15e8436be8 100644 --- a/hiboo/configuration.py +++ b/hiboo/configuration.py @@ -14,6 +14,7 @@ DEFAULT_CONFIG = { 'TEMPLATES_AUTO_RELOAD': False, 'MAIL_DOMAIN': 'tedomum.net', 'WEBSITE_NAME': 'Hiboo', + 'OPEN_SIGNUP': True, 'API_TOKEN': 'changeMe' } diff --git a/hiboo/models.py b/hiboo/models.py index 518dec9222081bd68e22eba6c2ad5713b82bc4dc..b54eef96ad3b972d6a50a9dacd771977eeacc6a4 100644 --- a/hiboo/models.py +++ b/hiboo/models.py @@ -114,6 +114,17 @@ class User(db.Model): profile.status = Profile.ACTIVE return profile + @classmethod + def get_unused(cls, interval=datetime.timedelta(hours=2)): + return (cls.query + .join(cls.profiles.and_(Profile.status != Profile.PURGED), isouter=True) + .filter(Profile.uuid == None) + .filter( + datetime.datetime.now() - interval > + sqlalchemy.sql.func.coalesce(cls.updated_at, cls.created_at) + ) + ) + class Auth(db.Model): """ An authenticator is a method to authenticate a user. diff --git a/hiboo/user/templates/user_list.html b/hiboo/user/templates/user_list.html index 1414649c8f3933b9eae9fbf2be8f1c0a9ababb95..78ac14af3a96e966cb2004a3cee62d9d0f80b479 100644 --- a/hiboo/user/templates/user_list.html +++ b/hiboo/user/templates/user_list.html @@ -24,3 +24,7 @@ </div> </div> {% endblock %} + +{% block actions %} +<a href="{{ url_for(".invite") }}" class="btn btn-primary">{% trans %}Sign-up link{% endtrans %}</a> +{% endblock %} diff --git a/hiboo/user/views.py b/hiboo/user/views.py index 7a814435e2a8578f03e7f8f74fe18185a097691c..5ea587490d8442d3f83cb124c20ea31aeb297c44 100644 --- a/hiboo/user/views.py +++ b/hiboo/user/views.py @@ -1,6 +1,7 @@ from hiboo.user import blueprint, forms from hiboo import models, utils, security from flask_babel import lazy_gettext as _ +from authlib.jose import jwt import datetime import flask @@ -39,4 +40,21 @@ def password_reset(user_uuid): models.db.session.commit() reset_link = flask.url_for("account.reset", token_uuid=token.uuid, _external=True) flask.flash(_("Reset link: {}").format(reset_link), "success") - return flask.redirect(flask.url_for(".details", user_uuid=user.uuid)) \ No newline at end of file + return flask.redirect(flask.url_for(".details", user_uuid=user.uuid)) + + +@blueprint.route("/invite", methods=["GET", "POST"]) +@security.admin_required() +@security.confirmation_required("generate a signup link") +def invite(): + expired = datetime.datetime.now() + datetime.timedelta(days=1) + payload = { + "exp": int(expired.timestamp()), + "aud": flask.url_for('account.signup') + } + header = {"alg": "HS512"} + key = flask.current_app.config["SECRET_KEY"] + token = jwt.encode(header, payload, key) + signup_link = flask.url_for("account.signup", token=token, _external=True) + flask.flash(_("Signup link: {}").format(signup_link), "success") + return flask.redirect(flask.url_for("user.list")) diff --git a/requirements-prod.txt b/requirements-prod.txt index 8f0ec4f003faf81b013f38609039de7559ed35cf..23554484ed162755689bf12a2d5342202daee800 100644 --- a/requirements-prod.txt +++ b/requirements-prod.txt @@ -1,67 +1,66 @@ -alembic==1.4.2 +alembic==1.6.5 argon2-cffi==20.1.0 -astroid==2.4.2 -Authlib==0.14.1 -Babel==2.8.0 +Authlib==0.15.4 +axon @ git+https://forge.tedomum.net/tedomum/axon.git@5980a68da60c8de3fb7cbc8402a0f3db84bc3693 +Babel==2.9.1 bcrypt==3.2.0 blinker==1.4 -certifi==2020.4.5.1 -cffi==1.14.0 -chardet==3.0.4 +certifi==2021.5.30 +cffi==1.14.6 +charset-normalizer==2.0.3 click==7.1.2 -cryptography==3.1.1 -decorator==4.4.2 -defusedxml==0.6.0 -dnspython==1.16.0 -email-validator==1.1.0 -Flask==1.1.2 +cryptography==3.4.7 +decorator==5.0.9 +defusedxml==0.7.1 +Deprecated==1.2.12 +dnspython==2.1.0 +elementpath==2.2.3 +email-validator==1.1.3 +Flask==1.1.4 Flask-Babel==2.0.0 Flask-DebugToolbar==0.11.0 Flask-Limiter==1.4 Flask-Login==0.5.0 -Flask-Migrate==2.5.3 +Flask-Migrate==3.0.1 flask-redis==0.4.0 Flask-Script==2.0.6 -Flask-SQLAlchemy==2.4.4 -Flask-WTF==0.14.3 -gunicorn==20.0.4 -idna==2.9 -infinity==1.4 -intervals==0.8.1 -isort==4.3.21 +Flask-SQLAlchemy==2.5.1 +Flask-WTF==0.15.1 +greenlet==1.1.0 +gunicorn==20.1.0 +idna==3.2 +importlib-resources==5.2.0 +infinity==1.5 +intervals==0.9.2 itsdangerous==1.1.0 -Jinja2==2.11.2 -jwcrypto==0.8 -lazy-object-proxy==1.4.3 +Jinja2==2.11.3 +jwcrypto==0.9.1 limits==1.5.1 -lxml==4.5.0 -Mako==1.1.2 -MarkupSafe==1.1.1 -mccabe==0.6.1 -mysqlclient==2.0.1 +lxml==4.6.3 +Mako==1.1.4 +MarkupSafe==2.0.1 +mysqlclient==2.0.3 passlib==1.7.4 -Pillow==7.1.2 -pkgconfig==1.5.1 -psycopg2==2.8.6 +Pillow==8.3.1 +psycopg2==2.9.1 pycparser==2.20 -pylint==2.5.3 -pyOpenSSL==19.1.0 -pysaml2==6.2.0 -python-dateutil==2.8.1 +pyOpenSSL==20.0.1 +pysaml2==7.0.1 +python-dateutil==2.8.2 python-editor==1.0.4 -pytz==2020.1 -PyYAML==5.3.1 -redis==3.5.0 -requests==2.23.0 -six==1.14.0 -SQLAlchemy==1.3.16 +pytz==2021.1 +PyYAML==5.4.1 +redis==3.5.3 +requests==2.26.0 +six==1.16.0 +SQLAlchemy==1.4.22 terminaltables==3.1.0 -toml==0.10.1 -urllib3==1.25.9 -validators==0.14.3 +urllib3==1.26.6 +validators==0.18.2 Werkzeug==0.16.0 wrapt==1.12.1 -WTForms==2.3.1 -WTForms-Components==0.10.4 -xmlsec==1.3.8 -git+https://forge.tedomum.net/tedomum/axon.git +WTForms==2.3.3 +WTForms-Components==0.10.5 +xmlschema==1.6.4 +xmlsec==1.3.11 +zipp==3.5.0