first commit

This commit is contained in:
Rutra committed 2026-10-12 14:25:22 +02:00
commit 3c0479faf0
169 files changed
+27617

No files matched your search

+56
View File
@@ -0,0 +1,56 @@
import { Secret, TOTP } from 'otpauth'
import encryption from '@adonisjs/core/services/encryption'
import type User from '#models/user'
const ISSUER = 'Big Brother'
const PERIOD = 30
function build(secret: Secret, username: string) {
return new TOTP({ issuer: ISSUER, label: username, secret, period: PERIOD, digits: 6 })
}
/**
* Two-factor authentication with a 6-digit code (Google Authenticator,
* Aegis, 2FAS…). The secret is stored encrypted with the APP_KEY.
*/
const totpService = {
/**
* New secret, its encrypted form for the database and the otpauth://
* URI shown as a QR code.
*/
generate(username: string) {
const secret = new Secret({ size: 20 })
return {
encrypted: encryption.encrypt(secret.base32),
base32: secret.base32,
uri: build(secret, username).toString(),
}
},
/**
* Checks a code against the given encrypted secret, accepting the
* previous and next periods (clock drift). Returns the time step used.
*/
check(encryptedSecret: string, username: string, code: string): number | null {
const base32 = encryption.decrypt<string>(encryptedSecret)
if (!base32 || !/^\d{6}$/.test(code)) return null
const totp = build(Secret.fromBase32(base32), username)
const delta = totp.validate({ token: code, window: 1 })
if (delta === null) return null
return Math.floor(Date.now() / 1000 / PERIOD) + delta
},
/**
* Checks the code of a user and refuses a code already used (replay).
*/
async verify(user: User, code: string) {
const step = this.check(user.totpSecret, user.username, code)
if (step === null) return false
if (user.totpLastStep !== null && step <= user.totpLastStep) return false
user.totpLastStep = step
await user.save()
return true
},
}
export default totpService