Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | /**
* Auth API Routes
*
* Public routes for login, refresh, and auth status check.
* Protected routes for logout and password change.
* These are registered BEFORE the auth middleware in the pipeline.
*/
import { Router, type Request, type Response } from 'express';
import type { AuthProvider } from '../../auth/types.js';
import type { TokenService } from '../../auth/token-service.js';
import { createAuthMiddleware } from '../middleware/auth-middleware.js';
export function createAuthRoutes(authProvider: AuthProvider, tokenService: TokenService): Router {
const router = Router();
const authMiddleware = createAuthMiddleware(tokenService);
// GET /api/auth/check - public, tells frontend if auth is enabled
router.get('/api/auth/check', (_req: Request, res: Response) => {
res.json({ authEnabled: true });
});
// POST /api/auth/login - public
router.post('/api/auth/login', async (req: Request, res: Response) => {
try {
const { email, password } = req.body;
if (!email || !password) {
res.status(400).json({ error: 'Email and password are required' });
return;
}
const result = await authProvider.authenticate({ email, password });
if (!result.success || !result.user) {
res.status(401).json({ error: result.error || 'Invalid credentials' });
return;
}
const token = tokenService.issueAccessToken(result.user.id, result.user.email);
const refreshToken = tokenService.issueRefreshToken(result.user.id, result.user.email);
res.json({
token,
refreshToken,
user: result.user,
});
} catch (error) {
const message = error instanceof Error ? error.message : 'Login failed';
res.status(500).json({ error: message });
}
});
// POST /api/auth/refresh - public
router.post('/api/auth/refresh', async (req: Request, res: Response) => {
try {
const { refreshToken } = req.body;
if (!refreshToken) {
res.status(400).json({ error: 'Refresh token is required' });
return;
}
const payload = tokenService.verifyToken(refreshToken, 'refresh');
const user = await authProvider.getUserById(payload.userId);
if (!user) {
res.status(401).json({ error: 'User not found' });
return;
}
// Blacklist old refresh token
tokenService.blacklistToken(refreshToken);
const newToken = tokenService.issueAccessToken(user.id, user.email);
const newRefreshToken = tokenService.issueRefreshToken(user.id, user.email);
res.json({
token: newToken,
refreshToken: newRefreshToken,
});
} catch {
res.status(401).json({ error: 'Invalid refresh token' });
}
});
// POST /api/auth/logout - requires valid token
router.post('/api/auth/logout', authMiddleware, (req: Request, res: Response) => {
const token = req.headers.authorization?.replace('Bearer ', '') || (req.query.token as string);
if (token) {
tokenService.blacklistToken(token);
}
res.json({ success: true });
});
// POST /api/auth/change-password - requires valid token
router.post('/api/auth/change-password', authMiddleware, async (req: Request, res: Response) => {
try {
const { oldPassword, newPassword } = req.body;
const userId = req.auth?.userId;
if (!userId) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
if (!oldPassword || !newPassword) {
res.status(400).json({ error: 'Old and new passwords are required' });
return;
}
await authProvider.changePassword(userId, oldPassword, newPassword);
res.json({ success: true });
} catch (error) {
const message = error instanceof Error ? error.message : 'Password change failed';
res.status(400).json({ error: message });
}
});
return router;
}
|