This commit is contained in:
Matthias Hochmeister
2026-02-23 17:08:58 +01:00
commit f09748f4a1
97 changed files with 17729 additions and 0 deletions

View File

@@ -0,0 +1,155 @@
import { Request, Response, NextFunction } from 'express';
import tokenService from '../services/token.service';
import userService from '../services/user.service';
import logger from '../utils/logger';
import { JwtPayload } from '../types/auth.types';
// Extend Express Request type to include user
declare global {
namespace Express {
interface Request {
user?: {
id: string; // UUID
email: string;
authentikSub: string;
};
}
}
}
/**
* Authentication middleware
* Validates JWT token and attaches user info to request
*/
export const authenticate = async (
req: Request,
res: Response,
next: NextFunction
): Promise<void> => {
try {
// Extract token from Authorization header
const authHeader = req.headers.authorization;
if (!authHeader) {
res.status(401).json({
success: false,
message: 'No authorization token provided',
});
return;
}
// Check for Bearer token format
const parts = authHeader.split(' ');
if (parts.length !== 2 || parts[0] !== 'Bearer') {
res.status(401).json({
success: false,
message: 'Invalid authorization header format. Use: Bearer <token>',
});
return;
}
const token = parts[1];
// Verify token
let decoded: JwtPayload;
try {
decoded = tokenService.verifyToken(token);
} catch (error) {
const message = error instanceof Error ? error.message : 'Invalid token';
res.status(401).json({
success: false,
message,
});
return;
}
// Check if user exists and is active
const user = await userService.findById(decoded.userId);
if (!user) {
logger.warn('Token valid but user not found', { userId: decoded.userId });
res.status(401).json({
success: false,
message: 'User not found',
});
return;
}
if (!user.is_active) {
logger.warn('User account is inactive', { userId: decoded.userId });
res.status(403).json({
success: false,
message: 'User account is inactive',
});
return;
}
// Attach user info to request
req.user = {
id: decoded.userId,
email: decoded.email,
authentikSub: decoded.authentikSub,
};
logger.debug('User authenticated successfully', {
userId: decoded.userId,
email: decoded.email,
});
next();
} catch (error) {
logger.error('Authentication middleware error', { error });
res.status(500).json({
success: false,
message: 'Internal server error during authentication',
});
}
};
/**
* Optional authentication middleware
* Attaches user if token is valid, but doesn't require it
*/
export const optionalAuth = async (
req: Request,
_res: Response,
next: NextFunction
): Promise<void> => {
try {
const authHeader = req.headers.authorization;
if (!authHeader) {
next();
return;
}
const parts = authHeader.split(' ');
if (parts.length !== 2 || parts[0] !== 'Bearer') {
next();
return;
}
const token = parts[1];
try {
const decoded = tokenService.verifyToken(token);
const user = await userService.findById(decoded.userId);
if (user && user.is_active) {
req.user = {
id: decoded.userId,
email: decoded.email,
authentikSub: decoded.authentikSub,
};
}
} catch (error) {
// Invalid token - continue without user
logger.debug('Optional auth: Invalid token', { error });
}
next();
} catch (error) {
logger.error('Optional authentication middleware error', { error });
next();
}
};

View File

@@ -0,0 +1,66 @@
import { Request, Response, NextFunction } from 'express';
import logger from '../utils/logger';
export class AppError extends Error {
statusCode: number;
isOperational: boolean;
constructor(message: string, statusCode: number = 500) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
export const errorHandler = (
err: Error | AppError,
req: Request,
res: Response,
_next: NextFunction
): void => {
if (err instanceof AppError) {
logger.error('Application Error', {
message: err.message,
statusCode: err.statusCode,
stack: err.stack,
path: req.path,
method: req.method,
});
res.status(err.statusCode).json({
status: 'error',
message: err.message,
});
return;
}
// Handle unexpected errors
logger.error('Unexpected Error', {
message: err.message,
stack: err.stack,
path: req.path,
method: req.method,
});
res.status(500).json({
status: 'error',
message: process.env.NODE_ENV === 'production'
? 'Internal server error'
: err.message,
});
};
export const notFoundHandler = (req: Request, res: Response): void => {
res.status(404).json({
status: 'error',
message: `Route ${req.originalUrl} not found`,
});
};
export const asyncHandler = (fn: Function) => {
return (req: Request, res: Response, next: NextFunction) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};