86 lines
2.5 KiB
TypeScript
86 lines
2.5 KiB
TypeScript
import _ from 'lodash'
|
|
import type { RouteHandler } from 'fastify'
|
|
import { Type, type Static } from '@fastify/type-provider-typebox'
|
|
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 = Type.Object({
|
|
email: Type.String(),
|
|
password: Type.String(),
|
|
remember: Type.Optional(Type.Boolean()),
|
|
})
|
|
|
|
const ResponseSchema = {
|
|
200: Type.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: Static<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,
|
|
},
|
|
}
|