Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,17 @@ jobs:
name: test with OCB
makepot: "true"
- container: ghcr.io/oca/oca-ci/py3.10-odoo18.0:latest
exclude: "password_security"
include: "auth_totp_enforce"
name: test with Odoo
- container: ghcr.io/oca/oca-ci/py3.10-ocb18.0:latest
exclude: "password_security"
include: "auth_totp_enforce"
name: test with OCB
makepot: "true"
- container: ghcr.io/oca/oca-ci/py3.10-odoo18.0:latest
exclude: "password_security,auth_totp_enforce"
name: test with Odoo
- container: ghcr.io/oca/oca-ci/py3.10-ocb18.0:latest
exclude: "password_security,auth_totp_enforce"
name: test with OCB
makepot: "true"
services:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ addon | version | maintainers | summary
[auth_saml](auth_saml/) | 18.0.1.1.3 | <a href='https://github.com/vincent-hatakeyama'><img src='https://github.com/vincent-hatakeyama.png' width='32' height='32' style='border-radius:50%;' alt='vincent-hatakeyama'/></a> | SAML2 Authentication
[auth_session_timeout](auth_session_timeout/) | 18.0.1.0.0 | | This module disable all inactive sessions since a given delay
[auth_signup_verify_email](auth_signup_verify_email/) | 18.0.1.0.0 | | Force uninvited users to use a good email for signup
[auth_totp_enforce](auth_totp_enforce/) | 18.0.1.0.0 | | Force users to set up an authenticator app before they can log in.
[auth_user_case_insensitive](auth_user_case_insensitive/) | 18.0.1.0.0 | | Makes the user login field case insensitive
[base_group_subcontractor](base_group_subcontractor/) | 18.0.1.0.0 | <a href='https://github.com/pedrobaeza'><img src='https://github.com/pedrobaeza.png' width='32' height='32' style='border-radius:50%;' alt='pedrobaeza'/></a> | Extra user type group for restricted access to backend
[base_user_empty_password](base_user_empty_password/) | 18.0.1.0.0 | <a href='https://github.com/grindtildeath'><img src='https://github.com/grindtildeath.png' width='32' height='32' style='border-radius:50%;' alt='grindtildeath'/></a> | Allows to empty password of users
Expand Down
104 changes: 104 additions & 0 deletions auth_totp_enforce/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

=================================
Enforce Two-Factor Authentication
=================================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:559d09d726e6723855e6e9933788c2145fe668a22a6b6f4be309095f69e8e22c
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--auth-lightgray.png?logo=github
:target: https://github.com/OCA/server-auth/tree/18.0/auth_totp_enforce
:alt: OCA/server-auth
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/server-auth-18-0/server-auth-18-0-auth_totp_enforce
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/server-auth&target_branch=18.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module forces internal users to configure two-factor authentication
(a TOTP authenticator app) before they log in.

Enforcement works like the password expiry flow of
``password_security``: nobody is logged out when the module is
installed, but on their next login users are held in a mandatory step
and cannot reach the backend until they have enabled an authenticator
app.

Members of the **Exempt from 2FA enforcement** group are never forced.
Portal and public users are out of scope at the moment.

**Table of contents**

.. contents::
:local:

Usage
=====

Once installed, any enforced internal user without an authenticator app
configured will, on their next login:

1. Enter their login and password as usual.
2. Be redirected to a mandatory setup page showing a QR code and secret
key.
3. Scan the code with an authenticator app and enter the generated code.
4. Be handed over to the standard two-factor step to complete the login.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-auth/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/server-auth/issues/new?body=module:%20auth_totp_enforce%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* ForgeFlow

Contributors
------------

- ForgeFlow S.L. <contact@forgeflow.com>

- Laura Cazorla <laura.cazorla@forgeflow.com>

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/server-auth <https://github.com/OCA/server-auth/tree/18.0/auth_totp_enforce>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
5 changes: 5 additions & 0 deletions auth_totp_enforce/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright 2026 ForgeFlow S.L.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import models
from . import controllers
21 changes: 21 additions & 0 deletions auth_totp_enforce/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2026 ForgeFlow S.L.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
"name": "Enforce Two-Factor Authentication",
"summary": "Force users to set up an authenticator app before they can log in.",
"version": "18.0.1.0.0",
"author": "ForgeFlow, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/server-auth",
"category": "Authentication",
"license": "AGPL-3",
"depends": ["auth_totp"],
"data": [
"security/res_groups.xml",
"views/templates.xml",
],
"demo": [
"demo/res_groups_demo.xml",
],
"installable": True,
}
4 changes: 4 additions & 0 deletions auth_totp_enforce/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2026 ForgeFlow S.L.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from . import home
71 changes: 71 additions & 0 deletions auth_totp_enforce/controllers/home.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copyright 2026 ForgeFlow S.L.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import _, http
from odoo.http import request

import odoo.addons.auth_totp.controllers.home

SESSION_KEY = "totp_enforce_setup"


class Home(odoo.addons.auth_totp.controllers.home.Home):
@http.route(
"/web/login/totp/setup",
type="http",
auth="public",
methods=["GET", "POST"],
sitemap=False,
website=True,
multilang=False,
)
def web_totp_setup(self, redirect=None, **kwargs):
if request.session.uid:
return request.redirect(
self._login_redirect(request.session.uid, redirect=redirect)
)
if not request.session.pre_uid:
return request.redirect("/web/login")
user = request.env["res.users"].sudo().browse(request.session.pre_uid)
if not user._mfa_enforced() or user.totp_enabled:
return request.redirect("/web/login/totp")
stored = request.session.get(SESSION_KEY)
if not stored or stored.get("uid") != user.id:
secret = user._generate_totp_setup_secret()
request.session[SESSION_KEY] = {"uid": user.id, "secret": secret}
else:
secret = stored["secret"]
error = None
if request.httprequest.method == "POST" and kwargs.get("totp_token"):
if user._totp_enforce_setup(secret, kwargs["totp_token"]):
request.session.pop(SESSION_KEY, None)
# Persist secret, session stays partial and delegate to standard MFA
request.env.flush_all()
user.invalidate_recordset(["totp_secret", "totp_enabled"])
request.session.touch()
return request.redirect(
self._login_redirect(user.id, redirect=redirect)
)
error = _("Verification failed, please double-check the 6-digit code")
wizard = (
request.env["auth_totp.wizard"]
.sudo()
.new(
{
"user_id": user.id,
"secret": secret,
}
)
)
request.session.touch()
return request.render(
"auth_totp_enforce.auth_totp_setup_form",
{
"user": user,
"error": error,
"redirect": redirect,
"secret": secret,
"qrcode": wizard.qrcode,
"url": wizard.url,
},
)
9 changes: 9 additions & 0 deletions auth_totp_enforce/demo/res_groups_demo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="group_mfa_exempt" model="res.groups">
<field
name="users"
eval="[(4, ref('base.user_admin')), (4, ref('base.user_root'))]"
/>
</record>
</odoo>
92 changes: 92 additions & 0 deletions auth_totp_enforce/i18n/auth_totp_enforce.pot
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * auth_totp_enforce
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"

#. module: auth_totp_enforce
#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form
msgid "Activate &amp; Log in"
msgstr ""

#. module: auth_totp_enforce
#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form
msgid "Cancel"
msgstr ""

#. module: auth_totp_enforce
#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form
msgid "Cannot scan it?"
msgstr ""

#. module: auth_totp_enforce
#: model:res.groups,name:auth_totp_enforce.group_mfa_exempt
msgid "Exempt from 2FA enforcement"
msgstr ""

#. module: auth_totp_enforce
#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form
msgid ""
"Install an authenticator app on your device\n"
" (Authy, Google Authenticator, Microsoft Authenticator...)."
msgstr ""

#. module: auth_totp_enforce
#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form
msgid "Look for an \"Add an account\" button."
msgstr ""

#. module: auth_totp_enforce
#: model:res.groups,comment:auth_totp_enforce.group_mfa_exempt
msgid "Members of this group are NOT forced to configure MFA at login."
msgstr ""

#. module: auth_totp_enforce
#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form
msgid "QR Code"
msgstr ""

#. module: auth_totp_enforce
#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form
msgid "Scan the QR code below, or enter the key manually."
msgstr ""

#. module: auth_totp_enforce
#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form
msgid "Set up MFA"
msgstr ""

#. module: auth_totp_enforce
#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form
msgid "Two-factor authentication is required for your account."
msgstr ""

#. module: auth_totp_enforce
#: model:ir.model,name:auth_totp_enforce.model_res_users
msgid "User"
msgstr ""

#. module: auth_totp_enforce
#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form
msgid "Verification Code"
msgstr ""

#. module: auth_totp_enforce
#. odoo-python
#: code:addons/auth_totp_enforce/controllers/home.py:0
msgid "Verification failed, please double-check the 6-digit code"
msgstr ""

#. module: auth_totp_enforce
#: model_terms:ir.ui.view,arch_db:auth_totp_enforce.auth_totp_setup_form
msgid "e.g. 123456"
msgstr ""
4 changes: 4 additions & 0 deletions auth_totp_enforce/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2026 ForgeFlow S.L.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import res_users
Loading
Loading