import _ from 'lodash' import * as z from 'zod' import type { RouteHandler } from 'fastify' import config from '../../../config.ts' import UserQueries from '../../../services/users/queries.ts' import emitter from '../../../lib/emitter.ts' import knex from '../../../lib/knex.ts' import StatusError from '../../../lib/status_error.ts' import { StatusErrorSchema } from '../../../schemas/status_error.ts' import { verifyPassword } from '../helpers.ts' const userQueries = UserQueries({ knex, emitter }) 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 }> = async function (request, reply) { try { const user = await userQueries.findOne({ email: request.body.email }) if (!user) { throw new StatusError(...errors.noUserFound) } else if (!user.password) { throw new StatusError(...errors.notLocal) } else if (user.loginAttempts >= maxLoginAttempts) { throw new StatusError(...errors.tooManyLoginAttempts) } const result = await verifyPassword(request.body.password, user.password) if (!result) { await userQueries.onLoginAttempt(user.id) 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 userQueries.onLogin(user.id) 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, }, }