# Description
Closes #7003 
Implements 2FA with TOTP. 

>[!WARNING]
> This is a draft PR, with only partial changes, made as a mean of
discussion about #7003 (it's easier to reason about real code)

## Behaviour
- a `totpSecret` is stored for each user
- use [`otplib`](https://github.com/yeojz/otplib/tree/master) to create
a QR code and to validate an `otp` against an `totpSecret` (great [demo
website](https://otplib.yeojz.dev/) by `otplib`)
- OTP is asked upon each login attempt

## Source
Inspired by:
- [RFC 6238](https://datatracker.ietf.org/doc/html/rfc6238)
- Cal.com's implementation of 2FA, namely
- [raising a
401](c21ba636d2/packages/features/auth/lib/next-auth-options.ts (L188-L190))
when missing OTP and 2FA is enabled, with a [specific error
code](c21ba636d2/packages/features/auth/lib/ErrorCode.ts (L9))
- [catching the
401](c21ba636d2/apps/web/modules/auth/login-view.tsx (L160))
in the frontend and
[displaying](c21ba636d2/apps/web/modules/auth/login-view.tsx (L276))
the OTP input

## Remaining
- [ ] encrypt `totpSecret` at rest using a symetric algorithm

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Nicolas Rouanne
2025-01-24 18:23:57 +01:00
committed by GitHub
parent f58f84114c
commit 17def223b6
9 changed files with 205 additions and 6 deletions

View File

@ -9847,6 +9847,54 @@ __metadata:
languageName: node
linkType: hard
"@otplib/core@npm:^12.0.1":
version: 12.0.1
resolution: "@otplib/core@npm:12.0.1"
checksum: 10c0/efe703d92d50cf11b39e47fc6ccd10c01d8bfbd9423724e88aecf2470f740562b2422c10b779e0b7d1d29c09f5d3d00de69200f04c8250e2adeb13a15e5dee7f
languageName: node
linkType: hard
"@otplib/plugin-crypto@npm:^12.0.1":
version: 12.0.1
resolution: "@otplib/plugin-crypto@npm:12.0.1"
dependencies:
"@otplib/core": "npm:^12.0.1"
checksum: 10c0/7ad0cda9c643411a13a118bcf0d031ef61a9031f794a21b549fb3f1e38334f83c5481a9f706eb5b25066325759c1a598fbc1f9c05c4620dff5addccbe2ad23ad
languageName: node
linkType: hard
"@otplib/plugin-thirty-two@npm:^12.0.1":
version: 12.0.1
resolution: "@otplib/plugin-thirty-two@npm:12.0.1"
dependencies:
"@otplib/core": "npm:^12.0.1"
thirty-two: "npm:^1.0.2"
checksum: 10c0/971a6f592c0f33cb258dcb750f6f0c058cbfd45e0212ea6f29d0c75dc6c5fcb67dbc8f19c3e8ae710e93292319b8d3d15fd7281714deae9fadaab794f9d44544
languageName: node
linkType: hard
"@otplib/preset-default@npm:^12.0.1":
version: 12.0.1
resolution: "@otplib/preset-default@npm:12.0.1"
dependencies:
"@otplib/core": "npm:^12.0.1"
"@otplib/plugin-crypto": "npm:^12.0.1"
"@otplib/plugin-thirty-two": "npm:^12.0.1"
checksum: 10c0/cd683861b96d04b7581534169a5a130fc049bd3188a850a1642ba4cf2a53866dedafe5201763697e38a3f0726103783baf338448153752c464fff797948dc01c
languageName: node
linkType: hard
"@otplib/preset-v11@npm:^12.0.1":
version: 12.0.1
resolution: "@otplib/preset-v11@npm:12.0.1"
dependencies:
"@otplib/core": "npm:^12.0.1"
"@otplib/plugin-crypto": "npm:^12.0.1"
"@otplib/plugin-thirty-two": "npm:^12.0.1"
checksum: 10c0/5a1bf8198f169182e267ab6ae3115f2441190f34a212e8232ff4b9c2c8a7253fddf3aa185ab9a6246487e9c1052b676395e80b6e0b2825fa218eb8e29bd7b14b
languageName: node
linkType: hard
"@parcel/watcher-android-arm64@npm:2.4.1":
version: 2.4.1
resolution: "@parcel/watcher-android-arm64@npm:2.4.1"
@ -38084,6 +38132,17 @@ __metadata:
languageName: node
linkType: hard
"otplib@npm:^12.0.1":
version: 12.0.1
resolution: "otplib@npm:12.0.1"
dependencies:
"@otplib/core": "npm:^12.0.1"
"@otplib/preset-default": "npm:^12.0.1"
"@otplib/preset-v11": "npm:^12.0.1"
checksum: 10c0/5a6cd332ed38f9fb6915407775bb9b4bf298d4d32c7c5691291add913dc1788064ab4ca353315f8add4ea704af2cb2c970d2f2d354725c6daa0c945cd5d09811
languageName: node
linkType: hard
"outvariant@npm:1.4.0":
version: 1.4.0
resolution: "outvariant@npm:1.4.0"
@ -44756,6 +44815,13 @@ __metadata:
languageName: node
linkType: hard
"thirty-two@npm:^1.0.2":
version: 1.0.2
resolution: "thirty-two@npm:1.0.2"
checksum: 10c0/a6f8c69a153a1aa4d6948eabe004f5a38e45cb869d7d6c535df7460fc44c95c596a60efc56cce56b5e4f0988601db1298be5d1b6ae3fe12dcdeb461c9aeadd17
languageName: node
linkType: hard
"throttle-debounce@npm:^3.0.1":
version: 3.0.1
resolution: "throttle-debounce@npm:3.0.1"
@ -45668,6 +45734,7 @@ __metadata:
monaco-editor: "npm:^0.51.0"
monaco-editor-auto-typings: "npm:^0.4.5"
openid-client: "npm:^5.7.0"
otplib: "npm:^12.0.1"
passport: "npm:^0.7.0"
psl: "npm:^1.9.0"
redis: "npm:^4.7.0"