first commit
This commit is contained in:
commit
3c0479faf0
169 files changed
+27617
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user