60 lines
2.0 KiB
TypeScript
60 lines
2.0 KiB
TypeScript
import User from '#models/user'
|
|
import limiter from '@adonisjs/limiter/services/main'
|
|
import type { HttpContext } from '@adonisjs/core/http'
|
|
import totpService from '#services/totp_service'
|
|
import { audit } from '#services/audit'
|
|
import { loginValidator } from '#validators/auth'
|
|
|
|
class InvalidLogin extends Error {}
|
|
|
|
/**
|
|
* 5 failed attempts per IP, then blocked for 15 minutes.
|
|
*/
|
|
const loginLimiter = () =>
|
|
limiter.use({ requests: 5, duration: '15 mins', blockDuration: '15 mins' })
|
|
|
|
export default class AuthController {
|
|
async login(ctx: HttpContext) {
|
|
const { request, response, auth } = ctx
|
|
const { username, password, code } = await request.validateUsing(loginValidator)
|
|
|
|
const penalize = () =>
|
|
loginLimiter().penalize(`login_${request.ip()}`, async (): Promise<User> => {
|
|
const user = await User.verifyCredentials(username, password).catch(() => null)
|
|
if (!user || !(await totpService.verify(user, code))) throw new InvalidLogin()
|
|
return user
|
|
})
|
|
|
|
let result: Awaited<ReturnType<typeof penalize>>
|
|
try {
|
|
result = await penalize()
|
|
} catch (error) {
|
|
if (!(error instanceof InvalidLogin)) throw error
|
|
await audit(ctx, { action: 'Connexion refusée', target: username, status: 'error' })
|
|
return response.badRequest({ message: 'Identifiant, mot de passe ou code incorrect.' })
|
|
}
|
|
|
|
const [throttled, user] = result
|
|
if (throttled) {
|
|
const minutes = Math.max(1, Math.ceil(throttled.response.availableIn / 60))
|
|
return response.tooManyRequests({
|
|
message: `Trop de tentatives. Réessaie dans ${minutes} min.`,
|
|
})
|
|
}
|
|
|
|
await auth.use('web').login(user!)
|
|
await audit(ctx, { action: 'Connexion', username: user!.username, status: 'success' })
|
|
return { username: user!.username }
|
|
}
|
|
|
|
async logout(ctx: HttpContext) {
|
|
await audit(ctx, { action: 'Déconnexion', status: 'success' })
|
|
await ctx.auth.use('web').logout()
|
|
return { ok: true }
|
|
}
|
|
|
|
async me({ auth }: HttpContext) {
|
|
return { username: auth.getUserOrFail().username }
|
|
}
|
|
}
|