brf/server/plugins/auth/routes/login.ts
2025-12-18 10:20:02 +01:00

93 lines
2.6 KiB
TypeScript

import _ from 'lodash'
import type { RouteHandler } from 'fastify'
import * as z from 'zod'
import { sql } from 'kysely'
import config from '../../../config.ts'
import StatusError from '../../../lib/status_error.ts'
import { StatusErrorSchema } from '../../../schemas/status_error.ts'
import { verifyPassword } from '../helpers.ts'
const { errors, maxLoginAttempts, requireVerification } = config.auth
const BodySchema = z.object({
email: z.string(),
password: z.string(),
remember: z.boolean().optional(),
})
const ResponseSchema = {
200: z.object({
id: { type: 'integer' },
email: { type: 'string' },
password: { type: 'string' },
emailVerifiedAt: { type: 'string' },
bannedAt: { type: 'string' },
blockedAt: { type: 'string' },
loginAttempts: { type: 'integer' },
}),
'4XX': StatusErrorSchema,
}
const login: RouteHandler<{ Body: z.infer<typeof BodySchema> }> = async function (request, reply) {
const { db } = request.server
try {
const user = await db.selectFrom('user').selectAll().where('email', '=', request.body.email).executeTakeFirst()
if (!user) {
throw new StatusError(...errors.noUserFound)
} else if (!user.password) {
throw new StatusError(...errors.notLocal)
} else if (user.loginAttempts && user.loginAttempts >= maxLoginAttempts) {
throw new StatusError(...errors.tooManyLoginAttempts)
}
const result = await verifyPassword(request.body.password, user.password)
if (!result) {
await db
.updateTable('user')
.set({ lastLoginAttemptAt: sql`now()`, loginAttempts: sql`"loginAttempts" + 1` })
.where('id', '=', user.id)
.execute()
throw new StatusError(...errors.wrongPassword)
}
if (requireVerification && !user.emailVerifiedAt) {
throw new StatusError(...errors.emailNotVerified)
} else if (user.blockedAt) {
throw new StatusError(...errors.blocked)
} else if (user.bannedAt) {
throw new StatusError(...errors.banned)
}
if (request.body.remember) {
request.session.cookie.expires = new Date('2038-01-19T03:14:07.000Z')
}
await request.login(user)
await db
.updateTable('user')
.set({ lastLoginAt: sql`now()`, loginAttempts: 0, lastLoginAttemptAt: null })
.where('id', '=', user.id)
.execute()
return reply.status(200).send(_.omit(user, 'password'))
} catch (err) {
this.log.error(err)
if (err instanceof StatusError) return reply.status(err.status).send(err.toJSON())
else throw err
}
}
export default {
handler: login,
schema: {
body: BodySchema,
schema: ResponseSchema,
},
}