This commit is contained in:
72
server/src/routes/auth.ts
Normal file
72
server/src/routes/auth.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import type { FastifyPluginAsync } from 'fastify';
|
||||
import { z } from 'zod';
|
||||
import { hashPassword, verifyPassword } from '../auth/passwords.js';
|
||||
import type { AuthUser } from '../auth/plugin.js';
|
||||
import { pool } from '../db/pool.js';
|
||||
|
||||
const credentialsSchema = z.object({
|
||||
email: z.string().email().transform((email) => email.toLowerCase()),
|
||||
password: z.string().min(12).max(200),
|
||||
});
|
||||
|
||||
const registerSchema = credentialsSchema.extend({
|
||||
name: z.string().trim().min(2).max(120),
|
||||
});
|
||||
|
||||
type UserRow = {
|
||||
id: string;
|
||||
email: string;
|
||||
display_name: string;
|
||||
role: AuthUser['role'];
|
||||
password_hash: string;
|
||||
};
|
||||
|
||||
const serializeUser = (user: UserRow): AuthUser => ({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.display_name,
|
||||
role: user.role,
|
||||
});
|
||||
|
||||
export const authRoutes: FastifyPluginAsync = async (app) => {
|
||||
app.post('/register', async (request, reply) => {
|
||||
const input = registerSchema.parse(request.body);
|
||||
const passwordHash = await hashPassword(input.password);
|
||||
|
||||
try {
|
||||
const result = await pool.query<UserRow>(
|
||||
`insert into users (email, password_hash, display_name)
|
||||
values ($1, $2, $3)
|
||||
returning id, email, display_name, role, password_hash`,
|
||||
[input.email, passwordHash, input.name],
|
||||
);
|
||||
const user = serializeUser(result.rows[0]);
|
||||
const token = await reply.jwtSign(user, { expiresIn: '7d' });
|
||||
return reply.code(201).send({ token, user });
|
||||
} catch (error: unknown) {
|
||||
if (typeof error === 'object' && error && 'code' in error && error.code === '23505') {
|
||||
return reply.code(409).send({ error: 'An account with this email already exists' });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/login', async (request, reply) => {
|
||||
const input = credentialsSchema.parse(request.body);
|
||||
const result = await pool.query<UserRow>(
|
||||
`select id, email, display_name, role, password_hash from users where email = $1`,
|
||||
[input.email],
|
||||
);
|
||||
const account = result.rows[0];
|
||||
|
||||
if (!account || !(await verifyPassword(input.password, account.password_hash))) {
|
||||
return reply.code(401).send({ error: 'Invalid email or password' });
|
||||
}
|
||||
|
||||
const user = serializeUser(account);
|
||||
const token = await reply.jwtSign(user, { expiresIn: '7d' });
|
||||
return { token, user };
|
||||
});
|
||||
|
||||
app.get('/me', { preHandler: app.authenticate }, async (request) => ({ user: request.user }));
|
||||
};
|
||||
Reference in New Issue
Block a user