feat(auth): add authPlugin.ts

This commit is contained in:
2026-06-22 05:40:59 +00:00
parent 57be3afcf8
commit ca6fb783c7
+162
View File
@@ -0,0 +1,162 @@
import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import AuthService from './AuthService';
import { z } from 'zod';
import fastifyCookie from '@fastify/cookie';
import fastifyCsrf from '@fastify/csrf';
// Zod schemas for validation
const registerSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
displayName: z.string().min(1)
});
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(1)
});
const resetPasswordSchema = z.object({
email: z.string().email()
});
const confirmPasswordResetSchema = z.object({
token: z.string(),
newPassword: z.string().min(8)
});
export default async function authPlugin(fastify: FastifyInstance, opts: any) {
// Register cookie plugin
await fastify.register(fastifyCookie);
// Register CSRF protection
await fastify.register(fastifyCsrf);
// Initialize AuthService
const authService = new AuthService(fastify);
// Rate limiting
fastify.addHook('onRequest', async (request, reply) => {
// In a real implementation, you would implement proper rate limiting
// This is just a placeholder
console.log('Rate limiting check');
});
// Register endpoint
fastify.post('/api/auth/register', {
schema: {
body: registerSchema
}
}, async (request: FastifyRequest, reply: FastifyReply) => {
try {
const { email, password, displayName } = registerSchema.parse(request.body);
const user = await authService.register(email, password, displayName);
reply.code(201).send({ user });
} catch (error) {
if (error instanceof z.ZodError) {
reply.code(400).send({ error: 'Validation error', details: error.errors });
} else {
reply.code(400).send({ error: error.message });
}
}
});
// Login endpoint
fastify.post('/api/auth/login', {
schema: {
body: loginSchema
}
}, async (request: FastifyRequest, reply: FastifyReply) => {
try {
const { email, password } = loginSchema.parse(request.body);
const token = await authService.login(email, password);
// Set JWT in HTTP-only cookie
reply.setCookie('token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000, // 24 hours
path: '/'
});
reply.send({ token });
} catch (error) {
reply.code(401).send({ error: 'Invalid credentials' });
}
});
// Logout endpoint
fastify.post('/api/auth/logout', {
onRequest: [fastify.authenticate]
}, async (request: FastifyRequest, reply: FastifyReply) => {
try {
const token = request.cookies.token;
if (!token) {
reply.code(401).send({ error: 'No token provided' });
return;
}
await authService.logout(token);
// Clear cookie
reply.clearCookie('token');
reply.send({ message: 'Logged out successfully' });
} catch (error) {
reply.code(400).send({ error: error.message });
}
});
// Request password reset endpoint
fastify.post('/api/auth/reset-password', {
schema: {
body: resetPasswordSchema
}
}, async (request: FastifyRequest, reply: FastifyReply) => {
try {
const { email } = resetPasswordSchema.parse(request.body);
await authService.requestPasswordReset(email);
reply.send({ message: 'Password reset email sent' });
} catch (error) {
reply.code(400).send({ error: error.message });
}
});
// Confirm password reset endpoint
fastify.post('/api/auth/reset-password/confirm', {
schema: {
body: confirmPasswordResetSchema
}
}, async (request: FastifyRequest, reply: FastifyReply) => {
try {
const { token, newPassword } = confirmPasswordResetSchema.parse(request.body);
await authService.confirmPasswordReset(token, newPassword);
reply.send({ message: 'Password reset successfully' });
} catch (error) {
reply.code(400).send({ error: error.message });
}
});
// Get current user endpoint
fastify.get('/api/auth/me', {
onRequest: [fastify.authenticate]
}, async (request: FastifyRequest, reply: FastifyReply) => {
try {
// Extract user ID from token
const token = request.cookies.token;
if (!token) {
reply.code(401).send({ error: 'No token provided' });
return;
}
const decoded = fastify.jwt.verify(token);
const userId = decoded.userId;
const user = await authService.getMe(userId);
reply.send({ user });
} catch (error) {
reply.code(401).send({ error: 'Invalid token' });
}
});
}