144 lines
3.2 KiB
TypeScript
144 lines
3.2 KiB
TypeScript
import { Router, Request, Response } from 'express';
|
|
import { z } from 'zod';
|
|
import db from '../db';
|
|
import {
|
|
hashPassword,
|
|
comparePassword,
|
|
generateToken,
|
|
validateEmail,
|
|
validatePassword,
|
|
} from '../utils/auth';
|
|
|
|
const router = Router();
|
|
|
|
const registerSchema = z.object({
|
|
email: z.string().email(),
|
|
password: z.string().min(8),
|
|
});
|
|
|
|
const loginSchema = z.object({
|
|
email: z.string().email(),
|
|
password: z.string(),
|
|
});
|
|
|
|
// Register
|
|
router.post('/register', async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { email, password } = registerSchema.parse(req.body);
|
|
|
|
if (!validateEmail(email)) {
|
|
res.status(400).json({
|
|
error: 'invalid_email',
|
|
message: 'Invalid email format',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const passwordValidation = validatePassword(password);
|
|
if (!passwordValidation.valid) {
|
|
res.status(400).json({
|
|
error: 'invalid_password',
|
|
message: 'Password does not meet requirements',
|
|
details: passwordValidation.errors,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const existingUser = await db.users.findByEmail(email);
|
|
if (existingUser) {
|
|
res.status(409).json({
|
|
error: 'user_exists',
|
|
message: 'User with this email already exists',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const passwordHash = await hashPassword(password);
|
|
const user = await db.users.create(email, passwordHash);
|
|
|
|
const token = generateToken(user);
|
|
|
|
res.status(201).json({
|
|
token,
|
|
user: {
|
|
id: user.id,
|
|
email: user.email,
|
|
plan: user.plan,
|
|
createdAt: user.createdAt,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
res.status(400).json({
|
|
error: 'validation_error',
|
|
message: 'Invalid input',
|
|
details: error.errors,
|
|
});
|
|
return;
|
|
}
|
|
|
|
console.error('Register error:', error);
|
|
res.status(500).json({
|
|
error: 'server_error',
|
|
message: 'Failed to register user',
|
|
});
|
|
}
|
|
});
|
|
|
|
// Login
|
|
router.post('/login', async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { email, password } = loginSchema.parse(req.body);
|
|
|
|
const user = await db.users.findByEmail(email);
|
|
if (!user) {
|
|
res.status(401).json({
|
|
error: 'invalid_credentials',
|
|
message: 'Invalid email or password',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const isValidPassword = await comparePassword(password, user.passwordHash);
|
|
if (!isValidPassword) {
|
|
res.status(401).json({
|
|
error: 'invalid_credentials',
|
|
message: 'Invalid email or password',
|
|
});
|
|
return;
|
|
}
|
|
|
|
await db.users.updateLastLogin(user.id);
|
|
|
|
const token = generateToken(user);
|
|
|
|
res.json({
|
|
token,
|
|
user: {
|
|
id: user.id,
|
|
email: user.email,
|
|
plan: user.plan,
|
|
createdAt: user.createdAt,
|
|
lastLoginAt: new Date(),
|
|
},
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
res.status(400).json({
|
|
error: 'validation_error',
|
|
message: 'Invalid input',
|
|
details: error.errors,
|
|
});
|
|
return;
|
|
}
|
|
|
|
console.error('Login error:', error);
|
|
res.status(500).json({
|
|
error: 'server_error',
|
|
message: 'Failed to login',
|
|
});
|
|
}
|
|
});
|
|
|
|
export default router;
|