Implement complete JWT-based authentication system with comprehensive security features:
Database:
- Migration 005: Add 4 new tables (refresh_tokens, password_reset_tokens, audit_log, entity_permissions)
- Enhanced users table with email verification, account status, lockout protection
Services:
- auth.service.js: Full authentication lifecycle (register, login, refresh, logout, password reset, email verification)
- audit.service.js: Comprehensive security event logging and tracking
Routes:
- auth.routes.js: 9 authentication endpoints (register, login, refresh, logout, profile, password operations, email verification)
Middleware:
- auth.middleware.js: Token authentication, email verification, account status checks
Security Features:
- bcrypt password hashing (cost 12)
- JWT access tokens (15-minute expiry)
- Refresh tokens (7-day expiry, SHA256 hashed, revocable)
- Account lockout (5 failed attempts = 15 minutes)
- Token rotation on password reset
- Email verification workflow
- Comprehensive audit logging
Scripts:
- run-migration.js: Automated database migration runner
- test-auth.js: Comprehensive test suite (10 tests)
- check-audit-log.js: Audit log verification tool
All tests passing. Production-ready implementation.
🤖 Generated with Claude Code
371 lines
8.1 KiB
JavaScript
371 lines
8.1 KiB
JavaScript
/**
|
|
* Authentication Routes
|
|
*
|
|
* POST /api/auth/register - Register new user
|
|
* POST /api/auth/login - Login user
|
|
* POST /api/auth/refresh - Refresh access token
|
|
* POST /api/auth/logout - Logout (revoke refresh token)
|
|
* POST /api/auth/logout-all - Logout all devices
|
|
* POST /api/auth/password/reset-request - Request password reset
|
|
* POST /api/auth/password/reset - Reset password with token
|
|
* POST /api/auth/email/verify - Verify email with token
|
|
* GET /api/auth/me - Get current user info
|
|
*/
|
|
|
|
import express from 'express';
|
|
import * as authService from '../services/auth.service.js';
|
|
import { logAuditEvent } from '../services/audit.service.js';
|
|
import { authenticateToken } from '../middleware/auth.middleware.js';
|
|
|
|
const router = express.Router();
|
|
|
|
/**
|
|
* Register new user
|
|
*/
|
|
router.post('/register', async (req, res) => {
|
|
try {
|
|
const { email, password, name } = req.body;
|
|
const ipAddress = req.ip || req.connection.remoteAddress;
|
|
|
|
const result = await authService.register({ email, password, name });
|
|
|
|
// Log audit event
|
|
await logAuditEvent({
|
|
userId: result.userId,
|
|
eventType: 'user.register',
|
|
status: 'success',
|
|
ipAddress,
|
|
userAgent: req.headers['user-agent']
|
|
});
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'User registered successfully. Please verify your email.',
|
|
userId: result.userId,
|
|
email: result.email
|
|
});
|
|
|
|
} catch (error) {
|
|
await logAuditEvent({
|
|
eventType: 'user.register',
|
|
status: 'failure',
|
|
ipAddress: req.ip,
|
|
userAgent: req.headers['user-agent'],
|
|
metadata: JSON.stringify({ error: error.message, email: req.body.email })
|
|
});
|
|
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Login user
|
|
*/
|
|
router.post('/login', async (req, res) => {
|
|
try {
|
|
const { email, password } = req.body;
|
|
const ipAddress = req.ip || req.connection.remoteAddress;
|
|
const deviceInfo = req.headers['user-agent'];
|
|
|
|
const result = await authService.login({
|
|
email,
|
|
password,
|
|
deviceInfo,
|
|
ipAddress
|
|
});
|
|
|
|
// Log audit event
|
|
await logAuditEvent({
|
|
userId: result.user.id,
|
|
eventType: 'user.login',
|
|
status: 'success',
|
|
ipAddress,
|
|
userAgent: deviceInfo
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
accessToken: result.accessToken,
|
|
refreshToken: result.refreshToken,
|
|
user: result.user
|
|
});
|
|
|
|
} catch (error) {
|
|
await logAuditEvent({
|
|
eventType: 'user.login',
|
|
status: 'failure',
|
|
ipAddress: req.ip,
|
|
userAgent: req.headers['user-agent'],
|
|
metadata: JSON.stringify({ error: error.message, email: req.body.email })
|
|
});
|
|
|
|
res.status(401).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Refresh access token
|
|
*/
|
|
router.post('/refresh', async (req, res) => {
|
|
try {
|
|
const { refreshToken } = req.body;
|
|
|
|
if (!refreshToken) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Refresh token is required'
|
|
});
|
|
}
|
|
|
|
const result = await authService.refreshAccessToken(refreshToken);
|
|
|
|
await logAuditEvent({
|
|
userId: result.user.id,
|
|
eventType: 'token.refresh',
|
|
status: 'success',
|
|
ipAddress: req.ip,
|
|
userAgent: req.headers['user-agent']
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
accessToken: result.accessToken,
|
|
user: result.user
|
|
});
|
|
|
|
} catch (error) {
|
|
await logAuditEvent({
|
|
eventType: 'token.refresh',
|
|
status: 'failure',
|
|
ipAddress: req.ip,
|
|
userAgent: req.headers['user-agent'],
|
|
metadata: JSON.stringify({ error: error.message })
|
|
});
|
|
|
|
res.status(401).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Logout (revoke refresh token)
|
|
*/
|
|
router.post('/logout', async (req, res) => {
|
|
try {
|
|
const { refreshToken } = req.body;
|
|
|
|
await authService.revokeRefreshToken(refreshToken);
|
|
|
|
await logAuditEvent({
|
|
userId: req.user?.id,
|
|
eventType: 'user.logout',
|
|
status: 'success',
|
|
ipAddress: req.ip,
|
|
userAgent: req.headers['user-agent']
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Logged out successfully'
|
|
});
|
|
|
|
} catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Logout all devices (revoke all refresh tokens)
|
|
*/
|
|
router.post('/logout-all', authenticateToken, async (req, res) => {
|
|
try {
|
|
await authService.revokeAllUserTokens(req.user.userId);
|
|
|
|
await logAuditEvent({
|
|
userId: req.user.userId,
|
|
eventType: 'user.logout_all',
|
|
status: 'success',
|
|
ipAddress: req.ip,
|
|
userAgent: req.headers['user-agent']
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Logged out from all devices successfully'
|
|
});
|
|
|
|
} catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Request password reset
|
|
*/
|
|
router.post('/password/reset-request', async (req, res) => {
|
|
try {
|
|
const { email } = req.body;
|
|
const ipAddress = req.ip || req.connection.remoteAddress;
|
|
|
|
if (!email) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Email is required'
|
|
});
|
|
}
|
|
|
|
const result = await authService.requestPasswordReset(email, ipAddress);
|
|
|
|
await logAuditEvent({
|
|
eventType: 'password.reset_request',
|
|
status: 'success',
|
|
ipAddress,
|
|
userAgent: req.headers['user-agent'],
|
|
metadata: JSON.stringify({ email })
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'If your email exists, you will receive a password reset link'
|
|
});
|
|
|
|
} catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Reset password with token
|
|
*/
|
|
router.post('/password/reset', async (req, res) => {
|
|
try {
|
|
const { token, newPassword } = req.body;
|
|
|
|
if (!token || !newPassword) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Token and new password are required'
|
|
});
|
|
}
|
|
|
|
await authService.resetPassword(token, newPassword);
|
|
|
|
await logAuditEvent({
|
|
eventType: 'password.reset',
|
|
status: 'success',
|
|
ipAddress: req.ip,
|
|
userAgent: req.headers['user-agent']
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Password reset successfully'
|
|
});
|
|
|
|
} catch (error) {
|
|
await logAuditEvent({
|
|
eventType: 'password.reset',
|
|
status: 'failure',
|
|
ipAddress: req.ip,
|
|
userAgent: req.headers['user-agent'],
|
|
metadata: JSON.stringify({ error: error.message })
|
|
});
|
|
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Verify email with token
|
|
*/
|
|
router.post('/email/verify', async (req, res) => {
|
|
try {
|
|
const { token } = req.body;
|
|
|
|
if (!token) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Verification token is required'
|
|
});
|
|
}
|
|
|
|
const result = await authService.verifyEmail(token);
|
|
|
|
await logAuditEvent({
|
|
eventType: 'email.verify',
|
|
status: 'success',
|
|
ipAddress: req.ip,
|
|
userAgent: req.headers['user-agent'],
|
|
metadata: JSON.stringify({ email: result.email })
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Email verified successfully',
|
|
email: result.email
|
|
});
|
|
|
|
} catch (error) {
|
|
await logAuditEvent({
|
|
eventType: 'email.verify',
|
|
status: 'failure',
|
|
ipAddress: req.ip,
|
|
userAgent: req.headers['user-agent'],
|
|
metadata: JSON.stringify({ error: error.message })
|
|
});
|
|
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Get current user info
|
|
*/
|
|
router.get('/me', authenticateToken, async (req, res) => {
|
|
try {
|
|
const user = await authService.getUserById(req.user.userId);
|
|
|
|
if (!user) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'User not found'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
user
|
|
});
|
|
|
|
} catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
export default router;
|