Kraft102's picture
Initial deployment - WidgeTDC Cortex Backend v2.1.0
529090e
/**
* ╔═══════════════════════════════════════════════════════════════════════════╗
* β•‘ AUTHENTICATION ROUTES β•‘
* ╠═══════════════════════════════════════════════════════════════════════════╣
* β•‘ Endpoints for user authentication and token management β•‘
* β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
*/
import { Router, Request, Response } from 'express';
import {
generateToken,
generateRefreshToken,
verifyRefreshToken,
AuthenticatedRequest,
authenticate
} from '../middleware/authMiddleware.js';
import { hyperLog } from '../services/HyperLog.js';
const router = Router();
// ═══════════════════════════════════════════════════════════════════════════
// Types
// ═══════════════════════════════════════════════════════════════════════════
interface LoginRequest {
email: string;
password: string;
}
interface TokenResponse {
accessToken: string;
refreshToken: string;
expiresIn: number;
user: {
id: string;
email: string;
name: string;
roles: string[];
};
}
// ═══════════════════════════════════════════════════════════════════════════
// Demo Users (Replace with database in production)
// ═══════════════════════════════════════════════════════════════════════════
const DEMO_USERS: Record<string, { password: string; name: string; roles: string[] }> = {
'admin@widgetdc.local': {
password: 'admin123',
name: 'Admin User',
roles: ['admin', 'user']
},
'user@widgetdc.local': {
password: 'user123',
name: 'Regular User',
roles: ['user']
},
'agent@widgetdc.local': {
password: 'agent123',
name: 'AI Agent',
roles: ['agent', 'service']
}
};
// ═══════════════════════════════════════════════════════════════════════════
// Routes
// ═══════════════════════════════════════════════════════════════════════════
/**
* POST /api/auth/login
* Login with email and password
*/
router.post('/login', async (req: Request, res: Response) => {
const { email, password } = req.body as LoginRequest;
if (!email || !password) {
return res.status(400).json({
error: 'Email and password required',
code: 'MISSING_CREDENTIALS'
});
}
// Check user exists
const user = DEMO_USERS[email.toLowerCase()];
if (!user || user.password !== password) {
hyperLog.logEvent('AUTH_LOGIN_FAILED', { email, reason: 'Invalid credentials' });
return res.status(401).json({
error: 'Invalid email or password',
code: 'INVALID_CREDENTIALS'
});
}
// Generate tokens
const userId = email.toLowerCase().replace(/[^a-z0-9]/g, '_');
const accessToken = generateToken({
sub: userId,
email: email.toLowerCase(),
name: user.name,
roles: user.roles
});
const refreshToken = generateRefreshToken(userId);
hyperLog.logEvent('AUTH_LOGIN_SUCCESS', {
email,
userId,
roles: user.roles
});
const response: TokenResponse = {
accessToken,
refreshToken,
expiresIn: 86400, // 24 hours
user: {
id: userId,
email: email.toLowerCase(),
name: user.name,
roles: user.roles
}
};
res.json(response);
});
/**
* POST /api/auth/refresh
* Refresh access token using refresh token
*/
router.post('/refresh', async (req: Request, res: Response) => {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(400).json({
error: 'Refresh token required',
code: 'MISSING_REFRESH_TOKEN'
});
}
const userId = verifyRefreshToken(refreshToken);
if (!userId) {
hyperLog.logEvent('AUTH_REFRESH_FAILED', { reason: 'Invalid refresh token' });
return res.status(401).json({
error: 'Invalid or expired refresh token',
code: 'INVALID_REFRESH_TOKEN'
});
}
// Find user by ID (in demo, derive from userId)
const email = Object.keys(DEMO_USERS).find(
e => e.toLowerCase().replace(/[^a-z0-9]/g, '_') === userId
);
const user = email ? DEMO_USERS[email] : null;
if (!user || !email) {
return res.status(401).json({
error: 'User not found',
code: 'USER_NOT_FOUND'
});
}
// Generate new tokens
const newAccessToken = generateToken({
sub: userId,
email,
name: user.name,
roles: user.roles
});
const newRefreshToken = generateRefreshToken(userId);
hyperLog.logEvent('AUTH_REFRESH_SUCCESS', { userId });
res.json({
accessToken: newAccessToken,
refreshToken: newRefreshToken,
expiresIn: 86400
});
});
/**
* POST /api/auth/logout
* Invalidate refresh token (in production, add to blacklist)
*/
router.post('/logout', authenticate, async (req: AuthenticatedRequest, res: Response) => {
const userId = req.user?.sub;
// In production: Add refresh token to blacklist in Redis
hyperLog.logEvent('AUTH_LOGOUT', { userId });
res.json({ success: true, message: 'Logged out successfully' });
});
/**
* GET /api/auth/me
* Get current user info
*/
router.get('/me', authenticate, async (req: AuthenticatedRequest, res: Response) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
res.json({
id: req.user.sub,
email: req.user.email,
name: req.user.name,
roles: req.user.roles,
authMethod: req.authMethod
});
});
/**
* GET /api/auth/status
* Check authentication status (public endpoint)
*/
router.get('/status', (req: Request, res: Response) => {
const authEnabled = process.env.AUTH_ENABLED !== 'false';
const devBypass = process.env.NODE_ENV === 'development' && process.env.AUTH_BYPASS === 'true';
res.json({
authEnabled,
devBypass,
mode: devBypass ? 'bypass' : (authEnabled ? 'enforced' : 'disabled'),
hint: authEnabled ? 'Use POST /api/auth/login to obtain tokens' : 'Authentication is disabled'
});
});
export default router;