diff --git a/hiboo/account/login.py b/hiboo/account/login.py
index d5add1917402e790ebb514527d23809ecc9aef7e..4eb56c44dc33db8324d1744e1daad74d134d19be 100644
--- a/hiboo/account/login.py
+++ b/hiboo/account/login.py
@@ -3,10 +3,14 @@ from hiboo.account import blueprint, forms
 from flask_babel import lazy_gettext as _
 from flask import session
 from authlib.jose import JsonWebToken
+from io import BytesIO
 
 import datetime
 import flask_login
 import flask
+import pyotp
+import qrcode
+import base64
 
 @blueprint.route("/signin/password", methods=["GET", "POST"])
 def signin_password():
@@ -113,4 +117,53 @@ def password_reset():
         models.db.session.commit()
         flask.flash(_("Successfully reset your password"), "success")
         return flask.redirect(flask.url_for(".signin_password"))
-    return flask.render_template("account_password_reset.html", form=form)
+    return flask.render_template("account_auth_password_reset.html", form=form)
+
+@blueprint.route("/auth/totp/reset", methods=["GET", "POST"])
+def totp_reset():
+    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('.totp_reset')},
+        'user_uuid': {'essential': True}
+    }
+    try:
+        claims = jwt.decode(token, key, claims_options=claims_options)
+        claims.validate()
+        user = models.User.query.get(claims["user_uuid"]) or flask.abort(404)
+    except Exception as e:
+        flask.flash(_("Invalid or expired reset link"), "danger")
+        return flask.redirect(flask.url_for(".signin_password"))
+    auth = user.auths[models.Auth.TOTP]
+    form = forms.TotpForm()
+    if form.validate_on_submit():
+        if auth.check_totp(form.totp.data):
+            del session["totp_reseted"]
+            flask.flash(_("TOTP is valid"), "success")
+            return flask.redirect(flask.url_for(".signin_password"))
+        else:
+            flask.flash(_("Invalid or expired TOTP, try again or disable and re-enable TOTP to reset your private key"), "danger")
+            return flask.redirect(flask.url_for(".totp_reset", token=token))
+    if "totp_reseted" not in session:
+        auth.set_otp_key()
+        models.log(models.History.MFA, comment=str(_("TOTP has been reseted")),
+                   user=user)
+        models.db.session.add(auth)
+        models.db.session.commit()
+        flask.flash(_("Successfully reset TOTP key"), "success")
+        session["totp_reseted"] = True
+    key = auth.value
+    issuer = flask.current_app.config['WEBSITE_NAME']
+    totp_uri = pyotp.totp.TOTP(key).provisioning_uri(
+        name=user.username,
+        issuer_name=issuer)
+    img = qrcode.make(totp_uri).get_image()
+    buffered = BytesIO()
+    img.save(buffered, format="PNG")
+    qr = base64.b64encode(buffered.getvalue()).decode('ascii')
+    return flask.render_template(
+        "account_auth_totp_reset.html",
+        key=key, name=user.username, issuer=issuer, qr=qr, form=form
+     )
diff --git a/hiboo/account/templates/account_auth_totp_reset.html b/hiboo/account/templates/account_auth_totp_reset.html
new file mode 100644
index 0000000000000000000000000000000000000000..c01602286d89c7236b89a4dae3c523a89a253543
--- /dev/null
+++ b/hiboo/account/templates/account_auth_totp_reset.html
@@ -0,0 +1,36 @@
+{% extends "base.html" %}
+
+{% block title %} {% trans %}Reset Two-factor authentication{% endtrans %} {% endblock %}
+{% block subtitle %}{% trans %}Reset Time-based One-Time Password (TOTP) key{% endtrans %}{% endblock %}
+
+{% block content %}
+
+<blockquote class="quote-info">
+  <h5>{% trans %}Howto{% endtrans %}</h5>
+  <p>{% trans %}Scan this QR code or use text informations to configure a TOTP client{% endtrans %}</p>
+</blockquote>
+<div class="row">
+  <div class="col-md-6 col text-center">
+    <img src="data:image/png;base64,{{ qr }}" class="rounded mb-4" width=250 height=250>
+  </div>
+  <div class="col-md-6 col">
+    <ul class="list-group", style="max-width: 500px">
+      <li class="list-group-item d-flex justify-content-between">
+        {% trans %}Secret key{% endtrans %}<code>{{ key }}</code>
+      </li>
+      <li class="list-group-item d-flex justify-content-between">
+        {% trans %}Name{% endtrans %}<code>{{ name }}</code>
+      </li>
+      <li class="list-group-item d-flex justify-content-between">
+        {% trans %}Issuer{% endtrans %}<code>{{ issuer }}</code>
+      </li>
+    </ul>
+  </div>
+  {{ macros.form(form) }}
+</div>
+
+{% endblock %}
+
+{% block actions %}
+<a href="{{ url_for(".totp_disable") }}" class="btn btn-warning">{% trans %}Disable TOTP{% endtrans %}</a>
+{% endblock %}
diff --git a/hiboo/user/templates/user_details.html b/hiboo/user/templates/user_details.html
index 6ed369d2f00d46f55a789034354024eb3b92c8d7..892d5e18abe05274e4a7606dd1dbebc3110aa934 100644
--- a/hiboo/user/templates/user_details.html
+++ b/hiboo/user/templates/user_details.html
@@ -18,7 +18,7 @@
           <dt class="col-sm-3">{% trans %}Created at{% endtrans %}</dt>
           <dd class="col-sm-9">{{ user.created_at }}</dd>
 
-		  <dt class="col-sm-3">{% trans %}Updated at{% endtrans %}</dt>
+          <dt class="col-sm-3">{% trans %}Updated at{% endtrans %}</dt>
           <dd class="col-sm-9">{{ user.created_at }}</dd>
 
           <dt class="col-sm-3">{% trans %}Auth. methods{% endtrans %}</dt>
@@ -31,10 +31,10 @@
           {% endfor %}
           {% endif %}
 
-		  {% if user.time_to_deletion() %}
-		  <dt class="col-sm-3">{% trans %}Deleted in{% endtrans %}</dt>
-		  <dd class="col-sm-9">{{ utils.babel.dates.format_timedelta(user.time_to_deletion()) }}</dd>
-		  {% endif %}
+          {% if user.time_to_deletion() %}
+          <dt class="col-sm-3">{% trans %}Deleted in{% endtrans %}</dt>
+          <dd class="col-sm-9">{{ utils.babel.dates.format_timedelta(user.time_to_deletion()) }}</dd>
+          {% endif %}
         </dl>
       </div>
     </div>
@@ -76,4 +76,7 @@
 
 {% block actions %}
 <a href="{{ url_for(".password_reset", user_uuid=user.uuid) }}" class="btn btn-warning">{% trans %}Password reset{% endtrans %}</a>
+{% if user.auths["totp"] %}
+<a href="{{ url_for(".totp_reset", user_uuid=user.uuid) }}" class="btn btn-warning">{% trans %}TOTP reset{% endtrans %}</a>
+{% endif %}
 {% endblock %}
diff --git a/hiboo/user/views.py b/hiboo/user/views.py
index cf242338e6891b3be861c365fa6892f0f5b9da45..fb5e08805165483a36e88e7d0d3c20d38ce30344 100644
--- a/hiboo/user/views.py
+++ b/hiboo/user/views.py
@@ -48,6 +48,25 @@ def password_reset(user_uuid):
     return flask.redirect(flask.url_for(".details", user_uuid=user.uuid))
 
 
+@blueprint.route("/auth/totp/reset/<user_uuid>", methods=["GET", "POST"])
+@security.admin_required()
+@security.confirmation_required("generate a totp reset link")
+def totp_reset(user_uuid):
+    user = models.User.query.get(user_uuid) or flask.abort(404)
+    expired = datetime.datetime.now() + datetime.timedelta(days=1)
+    payload = {
+        "exp": int(expired.timestamp()),
+        "aud": flask.url_for('account.totp_reset'),
+        "user_uuid": user.uuid
+    }
+    header = {"alg": "HS512"}
+    key = flask.current_app.config["SECRET_KEY"]
+    token = jwt.encode(header, payload, key)
+    reset_link = flask.url_for("account.totp_reset", token=token, _external=True)
+    flask.flash(_("Reset link: {}").format(reset_link), "success")
+    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")