346 lines
11 KiB
TypeScript
346 lines
11 KiB
TypeScript
import { Router } from 'express';
|
|
import { authRequired } from '../../Application/Services/AuthMiddleware';
|
|
import { container } from '../../Application/Services/DIContainer';
|
|
import { ErrorResponseService } from '../../Application/Services/ErrorResponseService';
|
|
import { ValidationMiddleware } from '../../Application/Services/ValidationMiddleware';
|
|
import { GeneralSearchService } from '../../Application/Search/Generalsearch';
|
|
import { logRequest, logError, logAuth, logWarning } from '../../Application/Services/Logger';
|
|
|
|
const userRouter = Router();
|
|
|
|
// Create search service that isn't in the container yet
|
|
const searchService = new GeneralSearchService(container.userRepository, container.organizationRepository, container.deckRepository);
|
|
|
|
// Login endpoint
|
|
userRouter.post('/login',
|
|
ValidationMiddleware.combine([
|
|
ValidationMiddleware.validateRequiredFields(['username', 'password']),
|
|
ValidationMiddleware.validateStringLength({
|
|
username: { min: 3, max: 50 },
|
|
password: { min: 6, max: 100 }
|
|
})
|
|
]),
|
|
async (req, res) => {
|
|
try {
|
|
logRequest('Login endpoint accessed', req, res, { username: req.body.username });
|
|
|
|
const { username, password } = req.body;
|
|
|
|
const result = await container.loginCommandHandler.execute({ username, password }, res);
|
|
|
|
if (result) {
|
|
logAuth('User login successful', undefined, { username: result.user.username }, req, res);
|
|
res.json(result);
|
|
} else {
|
|
throw new Error(`Login failed: ${result}`);
|
|
}
|
|
|
|
} catch (error) {
|
|
logError('Login endpoint error', error as Error, req, res);
|
|
|
|
if (error instanceof Error) {
|
|
if (error.message.includes('Invalid username')) {
|
|
return ErrorResponseService.sendUnauthorized(res, 'Invalid username or password');
|
|
}
|
|
if (error.message.includes('Invalid password')) {
|
|
return ErrorResponseService.sendUnauthorized(res, 'Invalid username or password');
|
|
}
|
|
if (error.message.includes('not verified')) {
|
|
return ErrorResponseService.sendUnauthorized(res, 'Please verify your email address');
|
|
}
|
|
if (error.message.includes('restriction')) {
|
|
return ErrorResponseService.sendUnauthorized(res, 'Please verify your email address');
|
|
}
|
|
if (error.message.includes('deactivated')) {
|
|
return ErrorResponseService.sendUnauthorized(res, 'Account has been deactivated');
|
|
}
|
|
}
|
|
|
|
return ErrorResponseService.sendInternalServerError(res);
|
|
}
|
|
});
|
|
|
|
// Create user endpoint
|
|
userRouter.post('/create',
|
|
ValidationMiddleware.combine([
|
|
ValidationMiddleware.validateRequiredFields(['username', 'email', 'password']),
|
|
ValidationMiddleware.validateEmailFormat(['email']),
|
|
ValidationMiddleware.validateStringLength({
|
|
username: { min: 3, max: 50 },
|
|
password: { min: 6, max: 100 }
|
|
})
|
|
]),
|
|
async (req, res) => {
|
|
try {
|
|
logRequest('Create user endpoint accessed', req, res, {
|
|
username: req.body.username,
|
|
email: req.body.email
|
|
});
|
|
|
|
const acceptLanguage = req.header('Accept-Language') || 'en';
|
|
const language : 'hu' | 'de' | 'en' = acceptLanguage.toLowerCase().startsWith('hu') ? 'hu' :
|
|
acceptLanguage.toLowerCase().startsWith('de') ? 'de' : 'en';
|
|
|
|
const result = await container.createUserCommandHandler.execute({ ...req.body, language });
|
|
|
|
logRequest('User created successfully', req, res, {
|
|
username: result.username
|
|
});
|
|
|
|
res.status(201).json(result);
|
|
|
|
} catch (error) {
|
|
// Don't log here since CreateUserCommandHandler already logs system errors
|
|
// Only log validation/user input errors at router level
|
|
|
|
if (error instanceof Error) {
|
|
if (error.message.includes('already exists')) {
|
|
return ErrorResponseService.sendConflict(res, error.message);
|
|
}
|
|
if (error.message.includes('validation')) {
|
|
return ErrorResponseService.sendBadRequest(res, error.message);
|
|
}
|
|
// Log unexpected errors that weren't handled by the command handler
|
|
if (!error.message.includes('Failed to create user')) {
|
|
logError('Unexpected create user endpoint error', error as Error, req, res);
|
|
}
|
|
}
|
|
|
|
return ErrorResponseService.sendInternalServerError(res);
|
|
}
|
|
});
|
|
|
|
// Get user profile (current user)
|
|
userRouter.get('/profile', authRequired, async (req, res) => {
|
|
try {
|
|
const userId = (req as any).user.userId;
|
|
|
|
logRequest('Get user profile endpoint accessed', req, res, { userId });
|
|
|
|
const result = await container.getUserByIdQueryHandler.execute({ id: userId });
|
|
|
|
if (!result) {
|
|
logWarning('User profile not found', { userId }, req, res);
|
|
return ErrorResponseService.sendNotFound(res, 'User not found');
|
|
}
|
|
|
|
logRequest('User profile retrieved successfully', req, res, {
|
|
userId,
|
|
username: result.username
|
|
});
|
|
|
|
res.json(result);
|
|
|
|
} catch (error) {
|
|
logError('Get user profile endpoint error', error as Error, req, res);
|
|
return ErrorResponseService.sendInternalServerError(res);
|
|
}
|
|
});
|
|
|
|
// Update user profile (current user)
|
|
userRouter.patch('/profile', authRequired, async (req, res) => {
|
|
try {
|
|
const userId = (req as any).user.userId;
|
|
|
|
logRequest('Update user profile endpoint accessed', req, res, {
|
|
userId,
|
|
fieldsToUpdate: Object.keys(req.body)
|
|
});
|
|
|
|
const result = await container.updateUserCommandHandler.execute({ id: userId, ...req.body });
|
|
|
|
if (!result) {
|
|
return ErrorResponseService.sendNotFound(res, 'User not found');
|
|
}
|
|
|
|
logRequest('User profile updated successfully', req, res, {
|
|
userId,
|
|
username: result.username
|
|
});
|
|
|
|
res.json(result);
|
|
|
|
} catch (error) {
|
|
logError('Update user profile endpoint error', error as Error, req, res);
|
|
|
|
if (error instanceof Error) {
|
|
if (error.message.includes('already exists')) {
|
|
return ErrorResponseService.sendConflict(res, error.message);
|
|
}
|
|
if (error.message.includes('validation')) {
|
|
return ErrorResponseService.sendBadRequest(res, error.message);
|
|
}
|
|
}
|
|
|
|
return ErrorResponseService.sendInternalServerError(res);
|
|
}
|
|
});
|
|
|
|
//Soft delete user (current user)
|
|
userRouter.delete('/profile', authRequired, async (req, res) => {
|
|
try {
|
|
const userId = (req as any).user.userId;
|
|
const result = await container.deleteUserCommandHandler.execute({ id: userId, soft: true });
|
|
logRequest('User soft deleted successfully', req, res, { userId });
|
|
res.json({ success: result });
|
|
} catch (error) {
|
|
logError('Soft delete user endpoint error', error as Error, req, res);
|
|
return ErrorResponseService.sendInternalServerError(res);
|
|
}
|
|
});
|
|
|
|
//logout user (current user)
|
|
userRouter.post('/logout', authRequired, async (req, res) => {
|
|
try {
|
|
const userId = (req as any).user.userId;
|
|
await container.logoutCommandHandler.execute(userId, res, req);
|
|
logRequest('User logged out successfully', req, res, { userId });
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
logError('Logout user endpoint error', error as Error, req, res);
|
|
return ErrorResponseService.sendInternalServerError(res);
|
|
}
|
|
});
|
|
|
|
// Refresh token endpoint
|
|
userRouter.post('/refresh-token', async (req, res) => {
|
|
try {
|
|
logRequest('Token refresh endpoint accessed', req, res);
|
|
|
|
const jwtService = container.jwtService;
|
|
const newTokenPair = jwtService.attemptTokenRefresh(req, res);
|
|
|
|
if (newTokenPair) {
|
|
logRequest('Token refresh successful', req, res);
|
|
res.json({
|
|
success: true,
|
|
message: 'Tokens refreshed successfully',
|
|
accessToken: newTokenPair.accessToken,
|
|
refreshToken: newTokenPair.refreshToken
|
|
});
|
|
} else {
|
|
logWarning('Token refresh failed - invalid or missing refresh token', undefined, req, res);
|
|
return ErrorResponseService.sendUnauthorized(res, 'Invalid or expired refresh token');
|
|
}
|
|
} catch (error) {
|
|
logError('Refresh token endpoint error', error as Error, req, res);
|
|
return ErrorResponseService.sendInternalServerError(res);
|
|
}
|
|
});
|
|
|
|
// Email verification endpoint
|
|
userRouter.post('/verify-email/:token', async (req, res) => {
|
|
try {
|
|
const { token } = req.params;
|
|
|
|
logRequest('Email verification endpoint accessed', req, res, {
|
|
tokenPrefix: token.substring(0, 8) + '...'
|
|
});
|
|
|
|
if (!token) {
|
|
return ErrorResponseService.sendBadRequest(res, 'Verification token is required');
|
|
}
|
|
|
|
const result = await container.verifyEmailCommandHandler.execute({ token });
|
|
|
|
if (result) {
|
|
logAuth('Email verification successful', undefined, { tokenPrefix: token.substring(0, 8) + '...' }, req, res);
|
|
res.json({ success: true, message: 'Email verified successfully' });
|
|
} else {
|
|
throw new Error('Email verification failed');
|
|
}
|
|
|
|
} catch (error) {
|
|
logError('Email verification endpoint error', error as Error, req, res);
|
|
|
|
if (error instanceof Error) {
|
|
if (error.message.includes('Invalid') || error.message.includes('expired')) {
|
|
return ErrorResponseService.sendBadRequest(res, 'Invalid or expired verification token');
|
|
}
|
|
}
|
|
|
|
return ErrorResponseService.sendInternalServerError(res);
|
|
}
|
|
});
|
|
|
|
// Forgot password request endpoint
|
|
userRouter.post('/forgot-password',
|
|
ValidationMiddleware.combine([
|
|
ValidationMiddleware.validateRequiredFields(['email']),
|
|
ValidationMiddleware.validateEmailFormat(['email'])
|
|
]),
|
|
async (req, res) => {
|
|
try {
|
|
const { email } = req.body;
|
|
const acceptLanguage = req.header('Accept-Language') || 'en';
|
|
const language: 'hu' | 'de' | 'en' = acceptLanguage.toLowerCase().startsWith('hu') ? 'hu' :
|
|
acceptLanguage.toLowerCase().startsWith('de') ? 'de' : 'en';
|
|
|
|
logRequest('Forgot password endpoint accessed', req, res, { email });
|
|
|
|
const result = await container.requestPasswordResetCommandHandler.execute({ language, email });
|
|
|
|
if (result) {
|
|
logAuth('Password reset request successful', undefined, { email }, req, res);
|
|
res.json({
|
|
success: true,
|
|
message: 'If an account with this email exists, a password reset link has been sent'
|
|
});
|
|
} else {
|
|
throw new Error('Password reset request failed');
|
|
}
|
|
|
|
} catch (error) {
|
|
logError('Forgot password endpoint error', error as Error, req, res);
|
|
|
|
// Always return success for security (don't reveal if email exists)
|
|
res.json({
|
|
success: true,
|
|
message: 'If an account with this email exists, a password reset link has been sent'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Reset password endpoint
|
|
userRouter.post('/reset-password',
|
|
ValidationMiddleware.combine([
|
|
ValidationMiddleware.validateRequiredFields(['token', 'newPassword']),
|
|
ValidationMiddleware.validateStringLength({
|
|
newPassword: { min: 6, max: 100 }
|
|
})
|
|
]),
|
|
async (req, res) => {
|
|
try {
|
|
const { token, newPassword } = req.body;
|
|
|
|
logRequest('Reset password endpoint accessed', req, res, {
|
|
tokenPrefix: token.substring(0, 8) + '...'
|
|
});
|
|
|
|
const result = await container.resetPasswordCommandHandler.execute({ token, newPassword });
|
|
|
|
if (result) {
|
|
logAuth('Password reset successful', undefined, { tokenPrefix: token.substring(0, 8) + '...' }, req, res);
|
|
res.json({ success: true, message: 'Password reset successfully' });
|
|
} else {
|
|
throw new Error('Password reset failed');
|
|
}
|
|
|
|
} catch (error) {
|
|
logError('Reset password endpoint error', error as Error, req, res);
|
|
|
|
if (error instanceof Error) {
|
|
if (error.message.includes('Invalid') || error.message.includes('expired')) {
|
|
return ErrorResponseService.sendBadRequest(res, 'Invalid or expired reset token');
|
|
}
|
|
if (error.message.includes('Password validation')) {
|
|
return ErrorResponseService.sendBadRequest(res, error.message);
|
|
}
|
|
}
|
|
|
|
return ErrorResponseService.sendInternalServerError(res);
|
|
}
|
|
});
|
|
|
|
export default userRouter;
|