/** * Required External Modules and Interfaces */ import express, {Request, Response} from 'express'; import * as UserService from './users.service.js'; import {Session} from './session.interface.js'; import {User} from './user.interface.js'; import {Guid} from 'guid-typescript'; import logger from '../../../middleware/logger.js'; /** * Router Definition */ export const usersRouter = express.Router(); /** * Controller Definitions */ /** * @swagger * /calendar/users/register: * post: * summary: Register a new user * description: Creates a new user account with the provided email, password, and full name. Only accepts official Nachklang email addresses. * tags: * - calendar * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - email * - password * - fullName * properties: * email: * type: string * format: email * example: john.doe@nachklang.art * description: Must be an official Nachklang email address * password: * type: string * format: password * example: securePassword123 * fullName: * type: string * example: John Doe * responses: * 201: * description: User registered successfully * content: * application/json: * schema: * type: object * properties: * sessionId: * type: integer * example: 123 * sessionKey: * type: string * example: abc123def456 * 400: * description: Bad request - missing or invalid parameters * content: * application/json: * schema: * type: object * properties: * message: * type: string * example: Missing parameters * 500: * description: Server error * content: * application/json: * schema: * type: object * properties: * status: * type: string * example: PROCESSING_ERROR * message: * type: string * example: Internal Server Error. Try again later. * reference: * type: string * example: 6ec1361c-4175-4e81-b2ef-a0792a9a1dc3 */ // POST users/register usersRouter.post('/register', async (req: Request, res: Response) => { try { const password: string = req.body.password; const email: string = req.body.email; const fullName: string = req.body.fullName; const ip: string = req.socket.remoteAddress ?? ''; if (!password || !email || !fullName) { // Missing res.status(400).send(JSON.stringify({message: 'Missing parameters'})); return; } const emailRegex = /^[a-zA-Z0-9\_\-\.]+@nachklang\.art$/; if(!emailRegex.test(email)) { res.status(400).send(JSON.stringify({message: 'Must use an official Nachklang email address'})); return; } // Create the user and a session const session: Session = await UserService.createUser(email, password, fullName, ip); // Send the session details back to the user res.status(201).send({ sessionId: session.sessionId, sessionKey: session.sessionKey }); } catch (e: any) { let errorGuid = Guid.create().toString(); logger.error('Error handling a request: ' + e.message, {reference: errorGuid}); res.status(500).send({ 'status': 'PROCESSING_ERROR', 'message': 'Internal Server Error. Try again later.', 'reference': errorGuid }); } }); /** * @swagger * /calendar/users/activate: * get: * summary: Activate a user account * description: Activates a user account using the provided user ID and activation token. * tags: * - calendar * parameters: * - in: query * name: id * required: true * schema: * type: integer * description: The ID of the user to activate * - in: query * name: token * required: true * schema: * type: string * description: The activation token sent to the user's email * responses: * 200: * description: User activated successfully * content: * application/json: * schema: * type: object * properties: * status: * type: string * example: OK * message: * type: string * example: User activated * 400: * description: Bad request - missing parameters or activation failed * content: * application/json: * schema: * type: object * properties: * status: * type: string * example: PROCESSING_ERROR * message: * type: string * example: Error activating user. Please contact your administrator. * 500: * description: Server error * content: * application/json: * schema: * type: object * properties: * status: * type: string * example: PROCESSING_ERROR * message: * type: string * example: Internal Server Error. Try again later. * reference: * type: string * example: 6ec1361c-4175-4e81-b2ef-a0792a9a1dc3 */ // GET /users/activate usersRouter.get('/activate', async (req: Request, res: Response) => { try { const userId: number = parseInt(req.query.id as string ?? '-1', 10); const token: string = req.query.token as string ?? ''; if (!userId || !token) { // Missing res.status(400).send(JSON.stringify({message: 'Missing parameters'})); return; } // Create the user and a session const success: boolean = await UserService.activateUser(userId, token); // Send the session details back to the user if(success) { res.status(200).send({ 'status': 'OK', 'message': 'User activated' }); return; } res.status(400).send({'status': 'PROCESSING_ERROR','message': 'Error activating user. Please contact your administrator.'}); } catch (e: any) { let errorGuid = Guid.create().toString(); logger.error('Error handling a request: ' + e.message, {reference: errorGuid}); res.status(500).send({ 'status': 'PROCESSING_ERROR', 'message': 'Internal Server Error. Try again later.', 'reference': errorGuid }); } }); /** * @swagger * /calendar/users/login: * post: * summary: Login a user * description: Authenticates a user with the provided email and password and returns a session. * tags: * - calendar * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - email * - password * properties: * email: * type: string * format: email * example: john.doe@nachklang.art * password: * type: string * format: password * example: securePassword123 * responses: * 200: * description: Login successful * content: * application/json: * schema: * type: object * properties: * sessionId: * type: integer * example: 123 * sessionKey: * type: string * example: abc123def456 * 400: * description: Bad request - missing parameters * content: * application/json: * schema: * type: object * properties: * message: * type: string * example: Missing parameters * 401: * description: Unauthorized - invalid credentials * content: * application/json: * schema: * type: object * properties: * message: * type: string * example: Wrong username and / or password * sessionId: * type: integer * example: -1 * sessionKey: * type: string * example: "" * 500: * description: Server error * content: * application/json: * schema: * type: object * properties: * status: * type: string * example: PROCESSING_ERROR * message: * type: string * example: Internal Server Error. Try again later. * reference: * type: string * example: 6ec1361c-4175-4e81-b2ef-a0792a9a1dc3 */ // POST users/login usersRouter.post('/login', async (req: Request, res: Response) => { try { const password: string = req.body.password; const email: string = req.body.email; const ip: string = req.socket.remoteAddress ?? ''; if (!password || !email) { // Missing res.status(400).send(JSON.stringify({message: 'Missing parameters'})); return; } // Create a session const session: Session | null = await UserService.login(email, password, ip); if (!session || !session.sessionId) { // Error logging in, probably wrong username / password res.status(401).send(JSON.stringify({message: 'Wrong username and / or password', sessionId: -1, sessionKey: ''})); return; } // Send the session details back to the user res.status(200).send({ sessionId: session.sessionId, sessionKey: session.sessionKey }); } catch (e: any) { let errorGuid = Guid.create().toString(); logger.error('Error handling a request: ' + e.message, {reference: errorGuid}); res.status(500).send({ 'status': 'PROCESSING_ERROR', 'message': 'Internal Server Error. Try again later.', 'reference': errorGuid }); } }); /** * @swagger * /calendar/users/checkSessionValid: * post: * summary: Check if a session is valid * description: Checks if the provided session is valid and returns the user information if it is. * tags: * - calendar * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - sessionId * - sessionKey * properties: * sessionId: * type: integer * example: 123 * sessionKey: * type: string * example: abc123def456 * responses: * 200: * description: Session is valid * content: * application/json: * schema: * $ref: '#/components/schemas/User' * 401: * description: Unauthorized - invalid session * content: * application/json: * schema: * type: object * properties: * messages: * type: array * items: * type: string * example: ["Invalid session"] * 500: * description: Server error * content: * application/json: * schema: * type: object * properties: * status: * type: string * example: PROCESSING_ERROR * message: * type: string * example: Internal Server Error. Try again later. * reference: * type: string * example: 6ec1361c-4175-4e81-b2ef-a0792a9a1dc3 */ // POST users/checkSessionValid usersRouter.post('/checkSessionValid', async (req: Request, res: Response) => { try { const ip: string = req.socket.remoteAddress ?? ''; const session_id = req.body.sessionId; const session_key = req.body.sessionKey; if (!session_id || !session_key) { // Error logging in, probably wrong username / password res.status(401).send(JSON.stringify({messages: ['No session detected']})); return; } const user: User | null = await UserService.checkSession(session_id, session_key, ip); if (!user || !user.userId) { // Error logging in, probably wrong username / password res.status(401).send(JSON.stringify({messages: ['Invalid session']})); return; } res.status(200).send(user); } catch (e: any) { let errorGuid = Guid.create().toString(); logger.error('Error handling a request: ' + e.message, {reference: errorGuid}); res.status(500).send({ 'status': 'PROCESSING_ERROR', 'message': 'Internal Server Error. Try again later.', 'reference': errorGuid }); } }); /** * @swagger * /calendar/users/initiatePasswordReset: * post: * summary: Initiates a password reset * description: Checks if the user exists and if so, initiates a password reset by sending an email to the user. * tags: * - calendar * responses: * 200: * description: Success * content: * application/json: * schema: * type: object * properties: * messages: * type: array * items: * type: string * example: Success * description: A list of status messages * 400: * description: Problem with the request. Please consider the returned detailed error. * content: * application/json: * schema: * type: object * properties: * messages: * type: array * items: * type: string * example: Missing parameters * description: A list of error messages * 401: * description: Problem with authorizing the user. Please check the provided credentials. * content: * application/json: * schema: * type: object * properties: * messages: * type: array * items: * type: string * example: Invalid session * description: A list of error messages * 500: * description: A server error occurred. Please try again. If this issue persists, contact the admin. * content: * application/json: * schema: * type: object * properties: * status: * type: string * description: The response status * example: PROCESSING_ERROR * message: * type: string * description: The detailed error message * example: Internal Server Error. Try again later. * reference: * type: string * description: An error reference for getting support concerning this error. * example: 6ec1361c-4175-4e81-b2ef-a0792a9a1dc3 * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * email: * type: string * example: patrick@nachklang.art */ usersRouter.post('/initiatePasswordReset', async(req: Request, res: Response) => { try { const username = req.body.username; if (!username) { // Error logging in, probably wrong username / password res.status(400).send(JSON.stringify({messages: ['No username given']})); return; } const success: boolean = await UserService.initiatePasswordReset(username); if (!success) { // Error logging in, probably wrong username / password res.status(401).send(JSON.stringify({messages: ['Error']})); return; } res.status(200).send(JSON.stringify({messages: ['Success']})); } catch (e: any) { let errorGuid = Guid.create().toString(); logger.error('Error handling a request: ' + e.message, {reference: errorGuid}); res.status(500).send({ 'status': 'PROCESSING_ERROR', 'message': 'Internal Server Error. Try again later.', 'reference': errorGuid }); } }); /** * @swagger * /calendar/users/finalizePasswordReset: * post: * summary: Finalizes the password reset * description: Checks if the given token is valid and if so, finalizes the password reset by setting the new password. * tags: * - calendar * responses: * 200: * description: Success * content: * application/json: * schema: * type: object * properties: * messages: * type: array * items: * type: string * example: Success * description: A list of status messages * 400: * description: Problem with the request. Please consider the returned detailed error. * content: * application/json: * schema: * type: object * properties: * messages: * type: array * items: * type: string * example: Missing parameters * description: A list of error messages * 401: * description: Problem with authorizing the user. Please check the provided credentials. * content: * application/json: * schema: * type: object * properties: * messages: * type: array * items: * type: string * example: Invalid session * description: A list of error messages * 500: * description: A server error occurred. Please try again. If this issue persists, contact the admin. * content: * application/json: * schema: * type: object * properties: * status: * type: string * description: The response status * example: PROCESSING_ERROR * message: * type: string * description: The detailed error message * example: Internal Server Error. Try again later. * reference: * type: string * description: An error reference for getting support concerning this error. * example: 6ec1361c-4175-4e81-b2ef-a0792a9a1dc3 * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * email: * type: string * example: patrick@nachklang.art * token: * type: string * example: 3ccd147f-720b-4e29-a8b7-46b63de31555 * password: * type: string * example: ExtremelyBadPassword */ usersRouter.post('/finalizePasswordReset', async(req: Request, res: Response) => { try { const username = req.body.username; const token = req.body.token; const newPassword = req.body.password; if (!username) { // Error logging in, probably wrong username / password res.status(400).send(JSON.stringify({messages: ['No username, token or password given']})); return; } const success: boolean = await UserService.finalizePasswordReset(username, token, newPassword); if (!success) { // Error logging in, probably wrong username / password res.status(401).send(JSON.stringify({messages: ['Error']})); return; } res.status(200).send(JSON.stringify({messages: ['Success']})); } catch (e: any) { let errorGuid = Guid.create().toString(); logger.error('Error handling a request: ' + e.message, {reference: errorGuid}); res.status(500).send({ 'status': 'PROCESSING_ERROR', 'message': 'Internal Server Error. Try again later.', 'reference': errorGuid }); } });