add features
This commit is contained in:
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user