Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • acides/hiboo
  • frju365/hiboo
  • pascoual/hiboo
  • thedarky/hiboo
  • jeremy/hiboo
  • cyrinux/hiboo
  • a.f/hiboo
  • mickge/hiboo
  • llaq/hiboo
  • vaguelysalaried/hiboo
  • felinn-glotte/hiboo
  • AntoninDelFabbro/hiboo
  • docemmetbrown/hiboo
13 results
Show changes
Showing
with 364 additions and 209 deletions
{% raw %}
{% extends "base.html" %}
{% block title %}
<a href="/docs" class="docs-header-img d-inline-block me-2 rounded" style="">
<img src="/docs/assets/img/logo_hiboo.png" alt="logo_hiboo"/>
</a>
{% trans %}Documentation{% endtrans %}{% endblock %}
{% endraw %}
{% raw %}
{% block submenu %}
{% endraw %}
{% include "menu.html" %}
{% raw %}
{% endblock %}
{% endraw %}
{% raw %}
{% block content %}
{% endraw %}
<div class="row">
{% include "aside.html" %}
<div class="col-md-9 docs-content">
{{ page.content }}
</div>
</div>
{% include "search.html" %}
{% raw %}
{% endblock %}
{% endraw %}
{% raw %}
{% block actions %}
{% endraw %}
<a href="{{ config.repo_url }}" class="btn btn-outline-secondary">{% raw %}{% trans %}Edit on Gitlab{% endtrans %}{% endraw %}</a>
{% raw %}
{% endblock %}
{% endraw %}
<nav class="navbar navbar-expand-lg">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#docsNavbarNav"
aria-controls="docsNavbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="docsNavbarNav">
<ul class="navbar-nav me-auto">
{% for nav_item in nav %}
{% if nav_item.children %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ nav_item.title }}
</a>
<ul class="dropdown-menu">
{% for nav_item in nav_item.children %}
<li {% if nav_item.active %}current{% endif %}>
<a class="dropdown-item" href="{{ nav_item.url|url }}">{{ nav_item.title }}</a>
</li>
{% endfor %}
</ul>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link {% if nav_item.active %}current{% endif %}" href="{{ nav_item.url|url }}">{{ nav_item.title }}</a>
</li>
{% endif %}
{% endfor %}
<li class="nav-item ml-3">
<a href="#" class="nav-link" data-bs-toggle="modal" data-bs-target="#mkdocs_search_modal">
{% raw %}{{ render_icon("search") }}{% endraw %}
{% trans %}Search{% endtrans %}
</a>
</li>
</ul>
</div>
</nav>
<script>var base_url = {{ base_url|tojson }};</script>
{%- for script in config.extra_javascript %}
{{ script|script_tag }}
{%- endfor %}
<div class="modal docs-search" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="searchModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="searchModalLabel">{% trans %}Search{% endtrans %}</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans %}Close{% endtrans %}"></button>
</div>
<div class="modal-body">
<p>{% trans %}From here you can search these documents. Enter your search terms below.{% endtrans %}</p>
<form>
<div class="form-group">
<input type="search" class="form-control mb-3" placeholder="{% trans %}Search...{% endtrans %}" id="mkdocs-search-query" title="{% trans %}Type search term here{% endtrans %}">
</div>
</form>
<div id="mkdocs-search-results" data-no-results-text="{% trans %}No results found{% endtrans %}"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
<li>
<a class="d-inline-block rounded docs-side ds-link ms-2" href="{{ toc_item.url }}">
{{ toc_item.title }}
</a>
{% if toc_item.children %}
<ul class="list-unstyled ms-4">
{% for toc_item in toc_item.children %}
{% include "toc/toc-item.html" %}
{% endfor %}
</ul>
{% endif %}
</li>
<ul class="list-unstyled small">
{% for toc_item in page.toc %}
{% include "toc/toc-item.html" %}
{% endfor %}
</ul>
import flask import flask
from flask_bootstrap import Bootstrap5
from werkzeug.middleware import proxy_fix from werkzeug.middleware import proxy_fix
from hiboo import models, configuration, debug, utils from hiboo import models, configuration, debug, utils
...@@ -10,15 +11,21 @@ def create_app_from_config(config): ...@@ -10,15 +11,21 @@ def create_app_from_config(config):
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.wsgi_app = proxy_fix.ProxyFix(app.wsgi_app) app.wsgi_app = proxy_fix.ProxyFix(app.wsgi_app)
# Initialize flask_bootstrap
bootstrap = Bootstrap5(app)
# Initialize application extensions # Initialize application extensions
config.init_app(app) config.init_app(app)
models.db.init_app(app) models.db.init_app(app)
utils.limiter.init_app(app) utils.limiter.init_app(app)
utils.translation.init_app(app) utils.translation.init_app(app, locale_selector=utils.get_locale)
utils.login.init_app(app) utils.login.init_app(app)
utils.login.user_loader(models.User.get) utils.login.user_loader(models.User.get)
utils.migrate.init_app(app, models.db) utils.migrate.init_app(app, models.db)
utils.redis.init_app(app)
# Initialize cache
cache_config = config.get_config_by_prefix('CACHE_')
utils.cache.init_app(app, config=cache_config)
# Initialize debugging tools # Initialize debugging tools
if app.config.get("DEBUG"): if app.config.get("DEBUG"):
...@@ -32,14 +39,17 @@ def create_app_from_config(config): ...@@ -32,14 +39,17 @@ def create_app_from_config(config):
return dict(config=app.config, utils=utils) return dict(config=app.config, utils=utils)
# Import views # Import views
from hiboo import account, user, profile, service, application, sso, api from hiboo import account, user, group, profile, moderation, service, application, sso, api, docs
app.register_blueprint(account.blueprint, url_prefix='/account') app.register_blueprint(account.blueprint, url_prefix='/account')
app.register_blueprint(user.blueprint, url_prefix='/user') app.register_blueprint(user.blueprint, url_prefix='/user')
app.register_blueprint(group.blueprint, url_prefix='/group')
app.register_blueprint(profile.blueprint, url_prefix='/profile') app.register_blueprint(profile.blueprint, url_prefix='/profile')
app.register_blueprint(service.blueprint, url_prefix='/service') app.register_blueprint(service.blueprint, url_prefix='/service')
app.register_blueprint(application.blueprint, url_prefix='/application') app.register_blueprint(application.blueprint, url_prefix='/application')
app.register_blueprint(moderation.blueprint, url_prefix='/moderation')
app.register_blueprint(sso.blueprint, url_prefix='/sso') app.register_blueprint(sso.blueprint, url_prefix='/sso')
app.register_blueprint(api.blueprint, url_prefix='/api') app.register_blueprint(api.blueprint, url_prefix='/api')
app.register_blueprint(docs.blueprint, url_prefix='/docs')
# Enable global CLI # Enable global CLI
from hiboo import cli from hiboo import cli
......
...@@ -3,4 +3,4 @@ import flask ...@@ -3,4 +3,4 @@ import flask
blueprint = flask.Blueprint("account", __name__, template_folder="templates") blueprint = flask.Blueprint("account", __name__, template_folder="templates")
from hiboo.account import login, home, settings from hiboo.account import login, home, settings
\ No newline at end of file
from hiboo.captcha import captcha from hiboo.captcha import captcha
from hiboo.format import NameFormat
from wtforms import validators, fields from wtforms import validators, fields
from flask_babel import lazy_gettext as _ from flask_babel import lazy_gettext as _
from flask_bootstrap import SwitchField
import flask_wtf import flask_wtf
class LoginForm(flask_wtf.FlaskForm): class LoginForm(flask_wtf.FlaskForm):
username = fields.StringField(_('Username'), [validators.DataRequired()]) username = fields.StringField(_('Username'), [validators.DataRequired()])
password = fields.PasswordField(_('Password'), [validators.DataRequired()]) password = fields.PasswordField(_('Password'), [validators.DataRequired()])
remember_me = fields.BooleanField(_('Remember me'), default=False) remember_me = SwitchField(_('Remember me'), default=False)
submit = fields.SubmitField(_('Sign in')) submit = fields.SubmitField(_('Sign in'))
...@@ -17,12 +19,12 @@ class TotpForm(flask_wtf.FlaskForm): ...@@ -17,12 +19,12 @@ class TotpForm(flask_wtf.FlaskForm):
class SignupForm(flask_wtf.FlaskForm): class SignupForm(flask_wtf.FlaskForm):
username = fields.StringField(_('Username'), [ formatter = NameFormat.registry["lowercase"]
validators.DataRequired(), username = fields.StringField(
validators.Regexp("(^[a-z0-9-_]+$)", message=(_("Your username must be \ _('Username'),
comprised of lowercase letters, numbers and '-' '_' only"))) formatter.validators(),
], description = (_("Your username must be \ description = (_("The username can be between 3 and 30 characters long. {}".format(formatter.message)))
comprised of lowercase letters, numbers and '-' '_' only"))) )
password = fields.PasswordField(_('Password'), [validators.DataRequired()]) password = fields.PasswordField(_('Password'), [validators.DataRequired()])
password2 = fields.PasswordField(_('Confirm password'), password2 = fields.PasswordField(_('Confirm password'),
[validators.DataRequired(), validators.EqualTo('password')]) [validators.DataRequired(), validators.EqualTo('password')])
......
import enum
from hiboo.account import blueprint from hiboo.account import blueprint
from hiboo.profile import common from hiboo.profile import common
from hiboo import security, models from hiboo import security, models
...@@ -9,7 +10,12 @@ import flask_login ...@@ -9,7 +10,12 @@ import flask_login
@blueprint.route("/home") @blueprint.route("/home")
@security.authentication_required() @security.authentication_required()
def home(): def home():
return flask.render_template("account_home.html") user = flask_login.current_user
page = flask.request.args.get('page', 1, type=int)
events = user.history.filter(models.History.user == user).order_by(
models.History.created_at.desc()).paginate(page=page, per_page=25)
# TODO also query user.groups non individual events (renamed or removed group)
return flask.render_template("account_home.html", events=events)
@blueprint.route("/profiles") @blueprint.route("/profiles")
...@@ -22,4 +28,4 @@ def profiles(): ...@@ -22,4 +28,4 @@ def profiles():
(service, profile) for profile in (service, profile) for profile in
models.Profile.filter(service, user).all() models.Profile.filter(service, user).all()
]) ])
return flask.render_template("account_profiles.html", profiles=profiles, common=common) return flask.render_template("account_profiles.html", profiles=profiles, common=common)
\ No newline at end of file
...@@ -3,27 +3,23 @@ from hiboo.account import blueprint, forms ...@@ -3,27 +3,23 @@ from hiboo.account import blueprint, forms
from flask_babel import lazy_gettext as _ from flask_babel import lazy_gettext as _
from flask import session from flask import session
from authlib.jose import JsonWebToken from authlib.jose import JsonWebToken
from io import BytesIO
import datetime import datetime
import flask_login import flask_login
import flask import flask
import pyotp
import qrcode
import base64
@blueprint.route("/signin/password", methods=["GET", "POST"]) @blueprint.route("/signin/password", methods=["GET", "POST"])
def signin_password(): def signin_password():
form = forms.LoginForm() form = forms.LoginForm()
if form.validate_on_submit(): if form.validate_on_submit():
user = models.User.login(form.username.data, form.password.data) user = models.User.login(form.username.data, form.password.data)
if form.remember_me.data is True:
session.permanent = True
if user and models.Auth.TOTP in user.auths and user.auths[models.Auth.TOTP].enabled: if user and models.Auth.TOTP in user.auths and user.auths[models.Auth.TOTP].enabled:
session["username"] = user.username session["username"] = user.name
return flask.redirect(utils.url_for(".signin_totp")) return flask.redirect(utils.url_for(".signin_totp"))
elif user: elif user:
flask_login.login_user(user) flask_login.login_user(user)
if form.remember_me.data is True:
session.permanent = True
return flask.redirect(utils.url_or_intent(".home")) return flask.redirect(utils.url_or_intent(".home"))
else: else:
flask.flash(_("Wrong credentials"), "danger") flask.flash(_("Wrong credentials"), "danger")
...@@ -35,7 +31,7 @@ def signin_password(): ...@@ -35,7 +31,7 @@ def signin_password():
def signin_totp(): def signin_totp():
form = forms.TotpForm() form = forms.TotpForm()
username = session.get("username") or flask.abort(403) username = session.get("username") or flask.abort(403)
user = models.User.query.filter_by(username=username).first() or flask.abort(403) user = models.User.query.filter_by(name=username).first() or flask.abort(403)
if form.validate_on_submit(): if form.validate_on_submit():
if user.auths[models.Auth.TOTP].check_totp(form.totp.data): if user.auths[models.Auth.TOTP].check_totp(form.totp.data):
flask_login.login_user(user) flask_login.login_user(user)
...@@ -76,7 +72,7 @@ def signup(): ...@@ -76,7 +72,7 @@ def signup():
flask.flash(_("A user with the same username exists already"), "danger") flask.flash(_("A user with the same username exists already"), "danger")
else: else:
user = models.User() user = models.User()
user.username = form.username.data user.name = form.username.data
auth = models.Auth(models.Auth.PASSWORD, enabled=True) auth = models.Auth(models.Auth.PASSWORD, enabled=True)
auth.set_password(form.password.data) auth.set_password(form.password.data)
user.auths = {models.Auth.PASSWORD: auth} user.auths = {models.Auth.PASSWORD: auth}
......
...@@ -58,7 +58,7 @@ def totp_enable(): ...@@ -58,7 +58,7 @@ def totp_enable():
key = user.auths[models.Auth.TOTP].value key = user.auths[models.Auth.TOTP].value
issuer = flask.current_app.config['WEBSITE_NAME'] issuer = flask.current_app.config['WEBSITE_NAME']
totp_uri = pyotp.totp.TOTP(key).provisioning_uri( totp_uri = pyotp.totp.TOTP(key).provisioning_uri(
name=user.username, name=user.name,
issuer_name=issuer) issuer_name=issuer)
img = qrcode.make(totp_uri).get_image() img = qrcode.make(totp_uri).get_image()
buffered = BytesIO() buffered = BytesIO()
...@@ -80,13 +80,13 @@ def totp_enable(): ...@@ -80,13 +80,13 @@ def totp_enable():
flask.flash(_("Scan this QR code or use the informations below it to configure your TOTP client"), "info") flask.flash(_("Scan this QR code or use the informations below it to configure your TOTP client"), "info")
return flask.render_template( return flask.render_template(
"account_auth_totp_enable.html", "account_auth_totp_enable.html",
key=key, name=user.username, issuer=issuer, qr=qr, form=form key=key, name=user.name, issuer=issuer, qr=qr, form=form
) )
@blueprint.route("/auth/totp/disable", methods=["GET", "POST"]) @blueprint.route("/auth/totp/disable", methods=["GET", "POST"])
@security.authentication_required() @security.authentication_required()
@security.confirmation_required("disable TOTP") @security.confirmation_required(_("disable TOTP"))
def totp_disable(): def totp_disable():
user = flask_login.current_user user = flask_login.current_user
auth = user.auths[models.Auth.TOTP] auth = user.auths[models.Auth.TOTP]
......
{% extends "form.html" %} {% extends "form.html" %}
{% block title %}{% trans %}New password{% endtrans %}{% endblock %} {% block title %}
{% trans %}New password{% endtrans %}
{% endblock %}
{% extends "form.html" %} {% extends "form.html" %}
{% block title %}{% trans %}Reset your password{% endtrans %}{% endblock %} {% block title %}
{% trans %}Reset your password{% endtrans %}
{% endblock %}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}{% trans %}Two-factor authentication (2FA){% endtrans %}{% endblock %} {% block title %}
{% block subtitle %}{% trans %}with time-based one-time password (TOTP){% endtrans %}{% endblock %} {% trans %}Two-factor authentication (2FA){% endtrans %}
{% endblock %}
{% block subtitle %}
{% trans %}with time-based one-time password (TOTP){% endtrans %}
{% endblock %}
{% block content %} {% block content %}
<div class="col-md-6 col">
<div class="col-md-6 col"> <p>{% trans %}TOTP is an optional secondary layer of the authentication
<p>{% trans %}TOTP is an optional secondary layer of the authentication process used to enforce the protection of your account with a one-time password. You can read <a href="https://en.wikipedia.org/wiki/Time-based_one-time_password">this Wikipedia page</a> if you want to learn more about this mechanism.{% endtrans %} process used to enforce the protection of your account with a one-time
</p> password. You can read
</div> <a href="https://en.wikipedia.org/wiki/Time-based_one-time_password">this
Wikipedia page</a>
{% if enabled %} if you want to learn more about this mechanism.{% endtrans %}</p>
</div>
<div class="col-md-6 col"> {% if enabled %}
<div class="col-md-6 col">
<blockquote class="quote-success"> <div class="alert alert-success">
<h5>{% trans %}Two-factor authentication is enabled{% endtrans %}</h5> <h5 class="alert-heading">{% trans %}Two-factor authentication is enabled{% endtrans %}</h5>
<p>{% trans %}Click on <i>Disable TOTP</i> to disable it{% endtrans %}</p> <p>{% trans %}Click on <i>Disable TOTP</i> to disable it{% endtrans %}</p>
</p> </div>
</blockquote>
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<h5>{% trans %}Test your one-time password{% endtrans %}</h5> <h5>{% trans %}Test your one-time password{% endtrans %}</h5>
<p>{% trans %}Feel free to use this form in order to check your client configuration{% endtrans %}</p> <p>{% trans %}Feel free to use this form in order to check your client configuration{% endtrans %}</p>
</div> </div>
<div class="card-footer"> <div class="card-footer">
{{ macros.form(form, layout=None) }} {{ render_form(form) }}
</div> </div>
</div> </div>
{% else %}
</div> <div class="col-md-6 col">
<div class="alert alert-info">
{% else %} <h5 class="alert-heading">{% trans %}Two-factor authentication is disabled{% endtrans %}</h5>
<p>{% trans %}Click on <i>Enable TOTP</i> to configure it{% endtrans %}</p>
<div class="col-md-6 col"> </div>
<blockquote class="quote-info"> <div class="alert alert-warning">
<h5>{% trans %}Two-factor authentication is disabled{% endtrans %}</h5> <h5 class="alert-heading">{% trans %}Attention{% endtrans %}</h5>
<p>{% trans %}Click on <i>Enable TOTP</i> to configure it{% endtrans %}</p> <p>{% trans %}You will need a working TOTP client in order to complete
</blockquote> this configuration. Several open-source apps can help you for this
(and some on mobile are available on
<blockquote class="quote-warning"> <a href="https://search.f-droid.org/?q=totp&lang=fr">
<h5>{% trans %}Attention{% endtrans %}</h5> F-Droid</a>){% endtrans %}</p>
<p>{% trans %}You will need a working TOTP client in order to complete this configuration. Several open-source apps can help you for this (and some on mobile are available on <a href="https://search.f-droid.org/?q=totp&lang=fr">F-Droid</a>){% endtrans %}</p> </div>
</blockquote> </div>
</div> {% endif %}
{% endif %}
{% endblock %} {% endblock %}
{% block actions %} {% block actions %}
{% if enabled %} {% if enabled %}
<a href="{{ url_for(".totp_disable") }}" class="btn btn-outline-warning">{% trans %}Disable TOTP{% endtrans %}</a>
<a href="{{ url_for(".totp_disable") }}" class="btn btn-warning">{% trans %}Disable TOTP{% endtrans %}</a> {% else %}
{% else %} <a href="{{ url_for(".totp_enable") }}" class="btn btn-outline-info">{% trans %}Enable TOTP{% endtrans %}</a>
<a href="{{ url_for(".totp_enable") }}" class="btn btn-info">{% trans %}Enable TOTP{% endtrans %}</a> {% endif %}
{% endif %}
{% endblock %} {% endblock %}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}{% trans %}Two-factor authentication (2FA){% endtrans %}{% endblock %} {% block title %}
{% block subtitle %}{% trans %}with time-based one-time password (TOTP){% endtrans %}{% endblock %} {% trans %}Two-factor authentication (2FA){% endtrans %}
{% endblock %}
{% block subtitle %}
{% trans %}with time-based one-time password (TOTP){% endtrans %}
{% endblock %}
{% block content %} {% block content %}
<div class="col-md-6 col">
<div class="col-md-6 col"> <div class="card">
<img class="card-img-top w-50 mx-auto p-2"
<div class="card"> src="data:image/png;base64,{{ qr }}"
<img class="card-img-top w-50 mx-auto" src="data:image/png;base64,{{ qr }}" alt="TOTP QR code"> alt="TOTP QR code"/>
<ul class="list-group list-group-flush"> <ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-center"> <li class="list-group-item d-flex justify-content-between align-items-center">
<b>{% trans %}Secret key{% endtrans %}</b> <code>{{ key }}</code> <b>{% trans %}Secret key{% endtrans %}</b> <code>{{ key }}</code>
</li> </li>
<li class="list-group-item d-flex justify-content-between align-items-center"> <li class="list-group-item d-flex justify-content-between align-items-center">
<b>{% trans %}Name{% endtrans %}</b> <code>{{ name }}</code> <b>{% trans %}Name{% endtrans %}</b> <code>{{ name }}</code>
</li> </li>
<li class="list-group-item d-flex justify-content-between align-items-center"> <li class="list-group-item d-flex justify-content-between align-items-center">
<b>{% trans %}Issuer{% endtrans %}</b> <code>{{ issuer }}</code> <b>{% trans %}Issuer{% endtrans %}</b> <code>{{ issuer }}</code>
</li> </li>
</ul> </ul>
<div class="card-footer"> <div class="card-footer">
{{ macros.form(form, layout=None) }} {{ render_form(form) }}
</div>
</div> </div>
</div> </div>
</div>
{% endblock %} {% endblock %}
{% block actions %} {% block actions %}
<a href="{{ url_for(".totp_disable") }}" class="btn btn-warning">{% trans %}Cancel{% endtrans %}</a> <a href="{{ url_for(".totp_disable") }}" class="btn btn-outline-warning">{% trans %}Cancel{% endtrans %}</a>
{% endblock %} {% endblock %}
{% extends "form.html" %} {% extends "form.html" %}
{% block title %}{% trans %}Update contact info{% endtrans %}{% endblock %} {% block title %}
{% trans %}Update contact info{% endtrans %}
{% block content %} {% endblock %}
{{ macros.form(form) }}
{% endblock %}
\ No newline at end of file
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}{% trans %}My account{% endtrans %}{% endblock %} {% block title %}
{% block subtitle %}{% trans %}status and history{% endtrans %}{% endblock %} {% trans %}My account{% endtrans %}
{% endblock %}
{% block subtitle %}
{% trans %}status and history{% endtrans %}
{% endblock %}
{% block content %} {% block content %}
<div class="row"> <div class="row">
<div class="col-md-6 col"> <div class="col-12 col-xl-6">
<section class="content"> <section class="content">
<div class="row"> <div class="row">
<div class="col-md-6 col"> <div class="col-md-6 col">
{{ macros.infobox(_("Account age"), "{} days".format((current_user.created_at.today() - current_user.created_at).total_seconds() // 86400), "blue", "calendar") }} {{ macros.infobox(_("Account age"), "{} days".format((current_user.created_at.today() - current_user.created_at).total_seconds() // 86400), "primary", "calendar-event-fill") }}
</div> </div>
<div class="col-md-6 col"> <div class="col-md-6 col">
{{ macros.infobox(_("Profile count"), current_user.profiles.filter_by(status="active").count(), "red", "users") }} {{ macros.infobox(_("Profile count"), current_user.profiles.filter_by(status="active").count(), "danger", "person-vcard-fill") }}
</div>
</div> </div>
</div> <div class="row">
<div class="row"> <div class="col-md-6 col">
<div class="col-md-6 col"> {{ macros.infobox(_("Pending requests"), current_user.profiles.filter_by(status="request").count(), "success", "hourglass-split") }}
{{ macros.infobox(_("Pending requests"), current_user.profiles.filter_by(status="request").count(), "green", "hourglass") }} </div>
<div class="col-md-6 col">
{{ macros.infobox(_("Role"), _("administrator") if current_user.is_admin else _("registered user"), "warning", "person-badge-fill") }}
</div>
</div> </div>
<div class="col-md-6 col"> {% if current_user.groups %}
{{ macros.infobox(_("Role"), _("administrator") if current_user.is_admin else _("registered user"), "yellow", "lock") }} <div class="row">
<div class="col-md-6 col">
{{ macros.infobox(_("Membership"), current_user.groups | map(attribute='groupname') | join(', '), "purple", "diagram-3-fill") }}
</div>
</div> </div>
</div> {% endif %}
</section> </section>
</div>
<div class="col-12 col-xl-6">
{{ macros.timeline(events) }}
</div>
</div> </div>
<div class="col-md-6 col">
{{ macros.timeline(current_user.history) }}
</div>
</div>
{% endblock %} {% endblock %}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}{% trans %}My account{% endtrans %}{% endblock %} {% block title %}
{% block subtitle %}{% trans %}my profiles{% endtrans %}{% endblock %} {% trans %}My account{% endtrans %}
{% endblock %}
{% block subtitle %}
{% trans %}my profiles{% endtrans %}
{% endblock %}
{% block content %} {% block content %}
<div class="row"> <div class="row g-3">
{% for service, profile in profiles %} {% for service, profile in profiles %}
{% set service = profile.service %} {% set service = profile.service %}
{% set status = profile.STATUSES[profile.status] %} {% set status = profile.STATUSES[profile.status] %}
<div class="col-xs col-md-6 col-lg-4"> <div class="col-xs col-md-6 col-lg-4">
<div class="card card-widget widget-user-2"> <div class="card">
<div class="widget-user-header bg-{{ macros.colors[loop.index0 % 7] }}"> <div class="card-header text-white"
<h3>{{ service.name }} / {{ profile.username }}</h3> style="background-color: var(--bs-{{ macros.colors[loop.index0 % 7] }})">
{% if profile.comment %}<h5>{{ profile.comment }}&nbsp;</h5> <h3>{{ service.name }} / {{ profile.name }}</h3>
{% else %}<h5>{% trans %}No profile description{% endtrans %}&nbsp;</h5> {% if profile.comment %}
{% endif %} <h5>{{ profile.comment }}</h5>
</div> {% else %}
<div class="card-body p-0"> <h5>{% trans %}No profile description{% endtrans %}</h5>
<div class="py-2 px-3">{{ macros.profile_status(profile) }}</div> {% endif %}
<ul class="nav flex-column nav-pills"> </div>
{% if not service.single_profile %} <div class="card-body">
{% if service.policy in ("open", "burst") and profile.__class__.filter(service, current_user).count() < service.max_profiles %} <div class="py-2 px-3">
<li class="nav-item"> {{ macros.profile_status(profile) }}
<a class="nav-link" href="{{ utils.url_for("profile.create", service_uuid=service.uuid) }}"> </div>
<i class="fas fa-user-plus"></i>&nbsp; <div class="list-group list-group-flush">
{% trans %}Create another profile{% endtrans %} {% if not service.single_profile %}
</a> {% if service.policy in ("open", "burst") and profile.__class__.filter(service, current_user).count() < service.max_profiles %}
</li> <a class="list-group-item list-group-item-action"
{% elif service.policy in ("managed", "burst") %} href="{{ utils.url_for("profile.create", service_uuid=service.uuid) }}">
<li class="nav-item"> {{ render_icon("plus-circle-fill") }}
<a class="nav-link" href="{{ utils.url_for("profile.create", service_uuid=service.uuid) }}"> {% trans %}Create another profile{% endtrans %}</a>
<i class="fas fa-user-plus"></i>&nbsp; {% elif service.policy in ("managed", "burst") %}
{% trans %}Request another profile{% endtrans %} <a class="list-group-item list-group-item-action"
</a> href="{{ utils.url_for("profile.create", service_uuid=service.uuid) }}">
</li> {{ render_icon("question-circle-fill") }}
{% endif %} {% trans %}Request another profile{% endtrans %}</a>
{% if service.policy not in ("locked",) %} {% endif %}
<li class="nav-item"> {% if service.policy not in ("locked",) %}
<a class="nav-link" href="{{ utils.url_for("profile.claim", service_uuid=service.uuid) }}"> <a class="list-group-item list-group-item-action"
<i class="fas fa-hand-paper"></i>&nbsp; href="{{ utils.url_for("profile.claim", service_uuid=service.uuid) }}">
{% trans %}Claim another profile{% endtrans %} {{ render_icon("hand-index-fill") }}
</a> {% trans %}Claim another profile{% endtrans %}</a>
</li> {% endif %}
{% endif %} {% endif %}
{% endif %} <a class="list-group-item list-group-item-action"
{% if profile.transition_step %} href="{{ utils.url_for("profile.edit", profile_uuid=profile.uuid) }}">
{% set transition = profile.TRANSITIONS[profile.transition] %} {{ render_icon("pen-fill") }}
{% set color, status = profile.STATUSES[transition.to] %} {% trans %}Edit this profile{% endtrans %}</a>
{% set when = transition.delta(profile) %} {% if profile.transition_step %}
<li class="nav-item"> {% set transition = profile.TRANSITIONS[profile.transition] %}
<a class="nav-link" href="#"> {% set color, status = profile.STATUSES[transition.to] %}
<i class="fas fa-clock"></i>&nbsp; {% set when = transition.delta(profile) %}
{% trans status, when %}Profile will be {{ status }} in {{ when }}{% endtrans %} <a class="list-group-item list-group-item-action" href="#">
</a> {{ render_icon("clock") }}
</li> {% trans status, when %}Profile will be {{ status }} in {{ when }}{% endtrans %}</a>
{% endif %} {% endif %}
{% for action in profile.actions %} {% for action in profile.actions %}
<li class="nav-item"> <a class="list-group-item list-group-item-action"
<a class="nav-link" href="{{ action.url(profile=profile) }}"> href="{{ action.url(profile=profile) }}">
<i class="{{ action.icon }}"></i>&nbsp; {{ render_icon(action.icon) }}
{{ action.description | capitalize }} {{ action.description | capitalize }}</a>
</a> {% endfor %}
</li> </div>
{% endfor %} </div>
</ul> </div>
</div> </div>
</div> {% endfor %}
</div> </div>
{% endfor %}
</div>
{% endblock %} {% endblock %}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}{% trans %}Sign in{% endtrans %}{% endblock %} {% block title %}
{% block subtitle %}{% trans %}to access your account{% endtrans %}{% endblock %} {% trans %}Sign in{% endtrans %}
{% endblock %}
{% block content %} {% block subtitle %}
{% trans %}to access your account{% endtrans %}
{{ macros.form(form) }}
{% endblock %} {% endblock %}
{% block actions %} {% block content %}
<a href="{{ utils.url_for(".signup") }}" class="btn btn-success">{% trans %}Sign up{% endtrans %}</a> <div class="row mb-3">
<div class="col-lg-6">
{{ render_form(form, form_type='horizontal', horizontal_columns=('lg', 3, 9) ) }}
</div>
</div>
{% if config['OPEN_SIGNUP'] %}
{% set signup_url = url_for("account.signup") %}
<br><p class="text-secondary">{% trans %}If you don't already have an account, you can <a href="{{ signup_url }}">sign up</a> now!{% endtrans %}</p>
{% endif %}
{% endblock %} {% endblock %}
{% extends "base.html" %} {% extends "form.html" %}
{% block title %}{% trans %}Time-based one-time password (TOTP) verify{% endtrans %}{% endblock %}
{% block subtitle %}{% trans %}to access your account{% endtrans %}{% endblock %}
{% block content %}
{{ macros.form(form) }}
{% block title %}
{% trans %}Time-based one-time password (TOTP) verify{% endtrans %}
{% endblock %}
{% block subtitle %}
{% trans %}to access your account{% endtrans %}
{% endblock %} {% endblock %}
{% block actions %} {% block actions %}
<a href="{{ utils.url_for(".signup") }}" class="btn btn-success">{% trans %}Sign up{% endtrans %}</a> <a href="{{ utils.url_for(".signup") }}" class="btn btn-success">
{% trans %}Sign up{% endtrans %}</a>
{% endblock %} {% endblock %}