inital
This commit is contained in:
246
backend/src/controllers/auth.controller.ts
Normal file
246
backend/src/controllers/auth.controller.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
import { Request, Response } from 'express';
|
||||
import authentikService from '../services/authentik.service';
|
||||
import tokenService from '../services/token.service';
|
||||
import userService from '../services/user.service';
|
||||
import logger from '../utils/logger';
|
||||
import { AuthRequest } from '../types/auth.types';
|
||||
|
||||
class AuthController {
|
||||
/**
|
||||
* Handle OAuth callback
|
||||
* POST /api/auth/callback
|
||||
*/
|
||||
async handleCallback(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { code } = req.body as AuthRequest;
|
||||
|
||||
// Validate code
|
||||
if (!code) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'Authorization code is required',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('Processing OAuth callback', { hasCode: !!code });
|
||||
|
||||
// Step 1: Exchange code for tokens
|
||||
const tokens = await authentikService.exchangeCodeForTokens(code);
|
||||
|
||||
// Step 2: Get user info from Authentik
|
||||
const userInfo = await authentikService.getUserInfo(tokens.access_token);
|
||||
|
||||
// Step 3: Verify ID token if present
|
||||
if (tokens.id_token) {
|
||||
try {
|
||||
authentikService.verifyIdToken(tokens.id_token);
|
||||
} catch (error) {
|
||||
logger.warn('ID token verification failed', { error });
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Find or create user in database
|
||||
let user = await userService.findByAuthentikSub(userInfo.sub);
|
||||
|
||||
if (!user) {
|
||||
// User doesn't exist, create new user
|
||||
logger.info('Creating new user from Authentik', {
|
||||
sub: userInfo.sub,
|
||||
email: userInfo.email,
|
||||
});
|
||||
|
||||
user = await userService.createUser({
|
||||
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,
|
||||
profile_picture_url: userInfo.picture,
|
||||
});
|
||||
} else {
|
||||
// User exists, update last login
|
||||
logger.info('Existing user logging in', {
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
});
|
||||
|
||||
await userService.updateLastLogin(user.id);
|
||||
}
|
||||
|
||||
// Check if user is active
|
||||
if (!user.is_active) {
|
||||
logger.warn('Inactive user attempted login', { userId: user.id });
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: 'User account is inactive',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 5: Generate internal JWT token
|
||||
const accessToken = tokenService.generateToken({
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
authentikSub: user.authentik_sub,
|
||||
});
|
||||
|
||||
// Generate refresh token
|
||||
const refreshToken = tokenService.generateRefreshToken({
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
});
|
||||
|
||||
logger.info('User authenticated successfully', {
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
});
|
||||
|
||||
// Step 6: Return tokens and user info
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'Authentication successful',
|
||||
data: {
|
||||
accessToken,
|
||||
refreshToken,
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
preferredUsername: user.preferred_username,
|
||||
givenName: user.given_name,
|
||||
familyName: user.family_name,
|
||||
profilePictureUrl: user.profile_picture_url,
|
||||
isActive: user.is_active,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('OAuth callback error', { error });
|
||||
|
||||
const message =
|
||||
error instanceof Error ? error.message : 'Authentication failed';
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle logout
|
||||
* 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
|
||||
|
||||
if (req.user) {
|
||||
logger.info('User logged out', {
|
||||
userId: req.user.id,
|
||||
email: req.user.email,
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'Logout successful',
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Logout error', { error });
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Logout failed',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle token refresh
|
||||
* POST /api/auth/refresh
|
||||
*/
|
||||
async handleRefresh(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { refreshToken } = req.body;
|
||||
|
||||
if (!refreshToken) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'Refresh token is required',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify refresh token
|
||||
let decoded;
|
||||
try {
|
||||
decoded = tokenService.verifyRefreshToken(refreshToken);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Invalid refresh token';
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Get user from database
|
||||
const user = await userService.findById(decoded.userId);
|
||||
|
||||
if (!user) {
|
||||
logger.warn('Refresh 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('Inactive user attempted token refresh', {
|
||||
userId: user.id,
|
||||
});
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: 'User account is inactive',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate new access token
|
||||
const accessToken = tokenService.generateToken({
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
authentikSub: user.authentik_sub,
|
||||
});
|
||||
|
||||
logger.info('Token refreshed successfully', {
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'Token refreshed successfully',
|
||||
data: {
|
||||
accessToken,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Token refresh error', { error });
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Token refresh failed',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new AuthController();
|
||||
63
backend/src/controllers/user.controller.ts
Normal file
63
backend/src/controllers/user.controller.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Request, Response } from 'express';
|
||||
import userService from '../services/user.service';
|
||||
import logger from '../utils/logger';
|
||||
|
||||
class UserController {
|
||||
/**
|
||||
* Get current user
|
||||
* GET /api/user/me
|
||||
*/
|
||||
async getCurrentUser(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
// User is attached by auth middleware
|
||||
if (!req.user) {
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
message: 'Not authenticated',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Get full user details from database
|
||||
const user = await userService.findById(req.user.id);
|
||||
|
||||
if (!user) {
|
||||
logger.warn('Authenticated user not found in database', {
|
||||
userId: req.user.id,
|
||||
});
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: 'User not found',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug('Fetched current user', { userId: user.id });
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
preferredUsername: user.preferred_username,
|
||||
givenName: user.given_name,
|
||||
familyName: user.family_name,
|
||||
profilePictureUrl: user.profile_picture_url,
|
||||
isActive: user.is_active,
|
||||
lastLoginAt: user.last_login_at,
|
||||
createdAt: user.created_at,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Get current user error', { error });
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to fetch user information',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new UserController();
|
||||
Reference in New Issue
Block a user