add features

This commit is contained in:
Matthias Hochmeister
2026-02-27 19:47:20 +01:00
parent 44e22a9fc6
commit c5e8337a69
11 changed files with 1554 additions and 194 deletions

View File

@@ -4,6 +4,8 @@ import tokenService from '../services/token.service';
import userService from '../services/user.service';
import logger from '../utils/logger';
import { AuthRequest } from '../types/auth.types';
import auditService, { AuditAction, AuditResourceType } from '../services/audit.service';
import { extractIp, extractUserAgent } from '../middleware/audit.middleware';
class AuthController {
/**
@@ -11,6 +13,9 @@ class AuthController {
* POST /api/auth/callback
*/
async handleCallback(req: Request, res: Response): Promise<void> {
const ip = extractIp(req);
const userAgent = extractUserAgent(req);
try {
const { code } = req.body as AuthRequest;
@@ -46,32 +51,75 @@ class AuthController {
if (!user) {
// User doesn't exist, create new user
logger.info('Creating new user from Authentik', {
sub: userInfo.sub,
sub: userInfo.sub,
email: userInfo.email,
});
user = await userService.createUser({
email: userInfo.email,
authentik_sub: userInfo.sub,
email: userInfo.email,
authentik_sub: userInfo.sub,
preferred_username: userInfo.preferred_username,
given_name: userInfo.given_name,
family_name: userInfo.family_name,
name: userInfo.name,
given_name: userInfo.given_name,
family_name: userInfo.family_name,
name: userInfo.name,
profile_picture_url: userInfo.picture,
});
// Audit: first-ever login (user record creation)
auditService.logAudit({
user_id: user.id,
user_email: user.email,
action: AuditAction.LOGIN,
resource_type: AuditResourceType.USER,
resource_id: user.id,
old_value: null,
new_value: { event: 'first_login', email: user.email },
ip_address: ip,
user_agent: userAgent,
metadata: { new_account: true },
});
} else {
// User exists, update last login
logger.info('Existing user logging in', {
userId: user.id,
email: user.email,
email: user.email,
});
await userService.updateLastLogin(user.id);
// Audit: returning user login
auditService.logAudit({
user_id: user.id,
user_email: user.email,
action: AuditAction.LOGIN,
resource_type: AuditResourceType.USER,
resource_id: user.id,
old_value: null,
new_value: null,
ip_address: ip,
user_agent: userAgent,
metadata: {},
});
}
// Check if user is active
if (!user.is_active) {
logger.warn('Inactive user attempted login', { userId: user.id });
// Audit the denied login attempt
auditService.logAudit({
user_id: user.id,
user_email: user.email,
action: AuditAction.PERMISSION_DENIED,
resource_type: AuditResourceType.USER,
resource_id: user.id,
old_value: null,
new_value: null,
ip_address: ip,
user_agent: userAgent,
metadata: { reason: 'account_inactive' },
});
res.status(403).json({
success: false,
message: 'User account is inactive',
@@ -81,20 +129,20 @@ class AuthController {
// Step 5: Generate internal JWT token
const accessToken = tokenService.generateToken({
userId: user.id,
email: user.email,
userId: user.id,
email: user.email,
authentikSub: user.authentik_sub,
});
// Generate refresh token
const refreshToken = tokenService.generateRefreshToken({
userId: user.id,
email: user.email,
email: user.email,
});
logger.info('User authenticated successfully', {
userId: user.id,
email: user.email,
email: user.email,
});
// Step 6: Return tokens and user info
@@ -105,20 +153,37 @@ class AuthController {
accessToken,
refreshToken,
user: {
id: user.id,
email: user.email,
name: user.name,
id: user.id,
email: user.email,
name: user.name,
preferredUsername: user.preferred_username,
givenName: user.given_name,
familyName: user.family_name,
givenName: user.given_name,
familyName: user.family_name,
profilePictureUrl: user.profile_picture_url,
isActive: user.is_active,
isActive: user.is_active,
},
},
});
} catch (error) {
logger.error('OAuth callback error', { error });
// Audit the failed login attempt (user_id unknown at this point)
auditService.logAudit({
user_id: null,
user_email: null,
action: AuditAction.PERMISSION_DENIED,
resource_type: AuditResourceType.SYSTEM,
resource_id: null,
old_value: null,
new_value: null,
ip_address: ip,
user_agent: userAgent,
metadata: {
reason: 'oauth_callback_error',
error: error instanceof Error ? error.message : 'unknown',
},
});
const message =
error instanceof Error ? error.message : 'Authentication failed';
@@ -134,14 +199,29 @@ class AuthController {
* POST /api/auth/logout
*/
async handleLogout(req: Request, res: Response): Promise<void> {
try {
// In a stateless JWT setup, logout is handled client-side by removing the token
// However, we can log the event for audit purposes
const ip = extractIp(req);
const userAgent = extractUserAgent(req);
try {
// In a stateless JWT setup, logout is handled client-side by removing
// the token. We log the event for GDPR accountability.
if (req.user) {
logger.info('User logged out', {
userId: req.user.id,
email: req.user.email,
email: req.user.email,
});
auditService.logAudit({
user_id: req.user.id,
user_email: req.user.email,
action: AuditAction.LOGOUT,
resource_type: AuditResourceType.USER,
resource_id: req.user.id,
old_value: null,
new_value: null,
ip_address: ip,
user_agent: userAgent,
metadata: {},
});
}
@@ -215,14 +295,14 @@ class AuthController {
// Generate new access token
const accessToken = tokenService.generateToken({
userId: user.id,
email: user.email,
userId: user.id,
email: user.email,
authentikSub: user.authentik_sub,
});
logger.info('Token refreshed successfully', {
userId: user.id,
email: user.email,
email: user.email,
});
res.status(200).json({