feat(auth): add authPlugin.ts
This commit is contained in:
@@ -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' });
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user