71 lines
1.8 KiB
TypeScript
71 lines
1.8 KiB
TypeScript
import type { RouteHandler } from 'fastify'
|
|
import * as z from 'zod'
|
|
import config from '../../../config.ts'
|
|
import StatusError from '../../../lib/status_error.ts'
|
|
import { StatusErrorSchema } from '../../../schemas/status_error.ts'
|
|
import { sql } from 'kysely'
|
|
|
|
const { errors, timeouts } = config.auth
|
|
|
|
const QuerystringSchema = z.object({
|
|
email: z.email(),
|
|
token: z.string(),
|
|
})
|
|
|
|
const ResponseSchema = {
|
|
200: {},
|
|
'4XX': StatusErrorSchema,
|
|
}
|
|
|
|
const verifyEmail: RouteHandler<{ Querystring: z.infer<typeof QuerystringSchema> }> = async function (request, reply) {
|
|
const { db } = request.server
|
|
|
|
try {
|
|
const { token, email } = request.query
|
|
|
|
const emailToken = await db
|
|
.selectFrom('emailToken')
|
|
.selectAll()
|
|
.where((eb) => eb.and({ email, token }))
|
|
.executeTakeFirst()
|
|
|
|
if (!emailToken) {
|
|
throw new StatusError(...errors.tokenNotFound)
|
|
} else if (emailToken.consumedAt) {
|
|
throw new StatusError(...errors.tokenConsumed)
|
|
} else if (Date.now() > emailToken.createdAt.getTime() + timeouts.verifyEmail) {
|
|
throw new StatusError(...errors.tokenExpired)
|
|
}
|
|
|
|
await db.transaction().execute((trx) =>
|
|
Promise.all([
|
|
trx
|
|
.updateTable('user')
|
|
.set({ emailVerifiedAt: sql`now()` })
|
|
.where('id', '=', emailToken.userId)
|
|
.execute(),
|
|
trx
|
|
.updateTable('emailToken')
|
|
.set({ consumedAt: sql`now()` })
|
|
.where('id', '=', emailToken.id)
|
|
.execute(),
|
|
]),
|
|
)
|
|
|
|
// TODO better template
|
|
return reply.status(200).type('text/html').send('<div>Email Verified!</div>')
|
|
} catch (err) {
|
|
this.log.error(err)
|
|
|
|
throw err
|
|
}
|
|
}
|
|
|
|
export default {
|
|
handler: verifyEmail,
|
|
schema: {
|
|
querystring: QuerystringSchema,
|
|
response: ResponseSchema,
|
|
},
|
|
}
|