brf/server/plugins/auth/routes/login.ts

86 lines
2.4 KiB
TypeScript

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<typeof BodySchema> }> = 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,
},
}