From c09c305647c9b428305e6afac27138704ac07e2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCller?= Date: Fri, 20 Aug 2021 20:45:39 +0200 Subject: [PATCH] API-9: Adding PartyPlaner login endpoint - Also some reformatting and comments --- src/common/common.routes.config.ts | 3 + src/middleware/logger.ts | 8 +- src/models/dhbw-service/DHBWService.router.ts | 4 +- .../generalInfo/GeneralInfo.router.ts | 6 +- src/models/partyplaner/PartyPlaner.router.ts | 6 +- src/models/partyplaner/data/Data.router.ts | 6 +- src/models/partyplaner/login/Login.router.ts | 51 +++++++++ .../partyplaner/register/Register.router.ts | 4 +- .../partyplaner/userService/user.service.ts | 104 +++++++++++++++++- .../HighlightMarker.router.ts | 4 +- .../addHighlight/AddHighlight.router.ts | 6 +- .../addHighlight/addHighlights.service.ts | 6 +- 12 files changed, 184 insertions(+), 24 deletions(-) create mode 100644 src/models/partyplaner/login/Login.router.ts diff --git a/src/common/common.routes.config.ts b/src/common/common.routes.config.ts index c4c3cbd..8da5581 100644 --- a/src/common/common.routes.config.ts +++ b/src/common/common.routes.config.ts @@ -1,4 +1,5 @@ import express from 'express'; + export abstract class CommonRoutesConfig { app: express.Application; name: string; @@ -8,8 +9,10 @@ export abstract class CommonRoutesConfig { this.name = name; this.configureRoutes(); } + getName() { return this.name; } + abstract configureRoutes(): express.Application; } diff --git a/src/middleware/logger.ts b/src/middleware/logger.ts index f2076e1..c5d4e30 100644 --- a/src/middleware/logger.ts +++ b/src/middleware/logger.ts @@ -1,5 +1,5 @@ -import * as appRoot from "app-root-path"; -import * as winston from "winston"; +import * as appRoot from 'app-root-path'; +import * as winston from 'winston'; const options = { file_info: { @@ -34,7 +34,7 @@ const options = { handleExceptions: true, json: false, colorize: true - }, + } }; const logger: winston.Logger = winston.createLogger({ format: winston.format.combine( @@ -47,6 +47,6 @@ const logger: winston.Logger = winston.createLogger({ new winston.transports.File(options.file_debug), new winston.transports.Console(options.console) ], - exitOnError: false, // do not exit on handled exceptions + exitOnError: false // do not exit on handled exceptions }); export default logger; diff --git a/src/models/dhbw-service/DHBWService.router.ts b/src/models/dhbw-service/DHBWService.router.ts index 12ecf01..6a2e05b 100644 --- a/src/models/dhbw-service/DHBWService.router.ts +++ b/src/models/dhbw-service/DHBWService.router.ts @@ -3,7 +3,7 @@ */ import express, {Request, Response} from 'express'; import {generalInfoRouter} from './generalInfo/GeneralInfo.router'; -import logger from "../../middleware/logger"; +import logger from '../../middleware/logger'; /** * Router Definition @@ -20,4 +20,4 @@ dhbwServiceRouter.get('/', async (req: Request, res: Response) => { logger.error('Error handling a request: ' + e.message); res.status(500).send({'message': 'Internal Server Error. Try again later.'}); } -}) +}); diff --git a/src/models/dhbw-service/generalInfo/GeneralInfo.router.ts b/src/models/dhbw-service/generalInfo/GeneralInfo.router.ts index b6e3ea3..0667943 100644 --- a/src/models/dhbw-service/generalInfo/GeneralInfo.router.ts +++ b/src/models/dhbw-service/generalInfo/GeneralInfo.router.ts @@ -2,7 +2,7 @@ * Required External Modules and Interfaces */ import express, {Request, Response} from 'express'; -import logger from "../../../middleware/logger"; +import logger from '../../../middleware/logger'; /** * Router Definition @@ -16,7 +16,7 @@ generalInfoRouter.get('/', async (req: Request, res: Response) => { logger.error('Error handling a request: ' + e.message); res.status(500).send({'message': 'Internal Server Error. Try again later.'}); } -}) +}); generalInfoRouter.post('/', async (req: Request, res: Response) => { try { @@ -25,4 +25,4 @@ generalInfoRouter.post('/', async (req: Request, res: Response) => { logger.error('Error handling a request: ' + e.message); res.status(500).send({'message': 'Internal Server Error. Try again later.'}); } -}) +}); diff --git a/src/models/partyplaner/PartyPlaner.router.ts b/src/models/partyplaner/PartyPlaner.router.ts index 13a9606..ffabc40 100644 --- a/src/models/partyplaner/PartyPlaner.router.ts +++ b/src/models/partyplaner/PartyPlaner.router.ts @@ -4,7 +4,8 @@ import express, {Request, Response} from 'express'; import {dataRouter} from './data/Data.router'; import {registerRouter} from './register/Register.router'; -import logger from "../../middleware/logger"; +import logger from '../../middleware/logger'; +import {loginRouter} from './login/Login.router'; /** * Router Definition @@ -14,6 +15,7 @@ export const partyPlanerRouter = express.Router(); // Sub-Endpoints partyPlanerRouter.use('/data', dataRouter); partyPlanerRouter.use('/register', registerRouter); +partyPlanerRouter.use('/login', loginRouter); partyPlanerRouter.get('/', async (req: Request, res: Response) => { try { @@ -22,4 +24,4 @@ partyPlanerRouter.get('/', async (req: Request, res: Response) => { logger.error('Error handling a request: ' + e.message); res.status(500).send({'message': 'Internal Server Error. Try again later.'}); } -}) +}); diff --git a/src/models/partyplaner/data/Data.router.ts b/src/models/partyplaner/data/Data.router.ts index d4ba172..19d34cd 100644 --- a/src/models/partyplaner/data/Data.router.ts +++ b/src/models/partyplaner/data/Data.router.ts @@ -2,7 +2,7 @@ * Required External Modules and Interfaces */ import express, {Request, Response} from 'express'; -import logger from "../../../middleware/logger"; +import logger from '../../../middleware/logger'; /** * Router Definition @@ -16,7 +16,7 @@ dataRouter.get('/', async (req: Request, res: Response) => { logger.error('Error handling a request: ' + e.message); res.status(500).send({'message': 'Internal Server Error. Try again later.'}); } -}) +}); dataRouter.post('/', async (req: Request, res: Response) => { try { @@ -25,4 +25,4 @@ dataRouter.post('/', async (req: Request, res: Response) => { logger.error('Error handling a request: ' + e.message); res.status(500).send({'message': 'Internal Server Error. Try again later.'}); } -}) +}); diff --git a/src/models/partyplaner/login/Login.router.ts b/src/models/partyplaner/login/Login.router.ts new file mode 100644 index 0000000..351f5b4 --- /dev/null +++ b/src/models/partyplaner/login/Login.router.ts @@ -0,0 +1,51 @@ +/** + * Required External Modules and Interfaces + */ +import express, {Request, Response} from 'express'; +import * as UserService from '../userService/user.service'; +import logger from '../../../middleware/logger'; + +/** + * Router Definition + */ +export const loginRouter = express.Router(); + +loginRouter.post('/:isDevCall', async (req: Request, res: Response) => { + try { + let username: string = ''; + let email: string = ''; + let firstName: string = ''; + let lastName: string = ''; + let password: string = ''; + let useDev: boolean = (req.params.isDevCall ?? '') === 'dev'; // TBD + + // API accepts both JSON in body and HTTP parameters + if (req.headers['content-type'] === 'application/json') { + username = req.body.username; + email = req.body.email; + password = req.body.password; + } else if (req.headers['content-type'] === 'application/x-www-form-urlencoded') { + username = (req.query.username ?? '').toString(); + email = (req.query.email ?? '').toString(); + password = (req.query.password ?? '').toString(); + } + let userIP = req.socket.remoteAddress ?? ''; + let deviceInfo = req.headers['user-agent'] ?? ''; + + if ((username === '' && email === '') || password === '') { + res.status(400).send({ + 'status': 'WRONG_PARAMS', + 'message': 'Missing or wrong parameters' + }); + return; + } + + // Check password and create session + let session = await UserService.loginUser(useDev, username, email, password, userIP, deviceInfo); + + res.status(200).send(session); + } catch (e) { + logger.error('Error handling a request: ' + e.message); + res.status(500).send({'message': 'Internal Server Error. Try again later.'}); + } +}); diff --git a/src/models/partyplaner/register/Register.router.ts b/src/models/partyplaner/register/Register.router.ts index 025f8c2..25ddd21 100644 --- a/src/models/partyplaner/register/Register.router.ts +++ b/src/models/partyplaner/register/Register.router.ts @@ -3,7 +3,7 @@ */ import express, {Request, Response} from 'express'; import * as UserService from '../userService/user.service'; -import logger from "../../../middleware/logger"; +import logger from '../../../middleware/logger'; /** * Router Definition @@ -65,4 +65,4 @@ registerRouter.post('/:isDevCall', async (req: Request, res: Response) => { logger.error('Error handling a request: ' + e.message); res.status(500).send({'message': 'Internal Server Error. Try again later.'}); } -}) +}); diff --git a/src/models/partyplaner/userService/user.service.ts b/src/models/partyplaner/userService/user.service.ts index 97c0753..c6e85a8 100644 --- a/src/models/partyplaner/userService/user.service.ts +++ b/src/models/partyplaner/userService/user.service.ts @@ -20,6 +20,11 @@ const dev_pool = mariadb.createPool({ connectionLimit: 5 }); +/** + * Fetches all usernames and emails from the database and returns them in an object + * @param useDev If the dev or prod database should be used + * @return any An object with a list of usernames and emails + */ export const getExistingUsernamesAndEmails = async (useDev: boolean): Promise => { let conn; try { @@ -52,7 +57,7 @@ export const getExistingUsernamesAndEmails = async (useDev: boolean): Promise => { let conn; try { @@ -122,7 +138,91 @@ export const registerUser = async (useDev: boolean, username: string, email: str conn.end(); } } -} +}; + +/** + * Checks credentials of a user and creates a new session if they are correct + * @param useDev If the dev or prod database should be used + * @param username The username of the new user + * @param email The email address of the new user + * @param password The password of the new user + * @param ip The IP Address of the new user + * @param deviceInfo The user agent of the new user + */ +export const loginUser = async (useDev: boolean, username: string, email: string, password: string, ip: string, deviceInfo: string): Promise => { + let conn; + try { + if (useDev) { + conn = await dev_pool.getConnection(); + } else { + conn = await prod_pool.getConnection(); + } + + let query_result; + + // Get the saved hash + if (username !== '') { + query_result = await conn.query('SELECT user_id, password_hash FROM users WHERE username = ?', username); + } else { + query_result = await conn.query('SELECT user_id, password_hash FROM users WHERE email = ?', email); + } + + let passwordHash: string = ''; + let userId: string = ''; + + for (let row in query_result) { + if (row !== 'meta') { + passwordHash = query_result[row].password_hash; + userId = query_result[row].user_id; + } + } + + // Wrong password + if (!bcrypt.compareSync(password, passwordHash)) { + return {} as Session; + } + + // Update user last login + await conn.query('UPDATE users SET last_login = NOW() WHERE user_id = ?', userId); + await conn.commit(); + + // Create session + const sessionKey = Guid.create().toString(); + const sessionKeyHash = bcrypt.hashSync(sessionKey, 10); + + let deviceType = 'unknown'; + if (deviceInfo.includes('PartyPlaner') || deviceInfo.includes('Dart')) { + deviceType = 'mobile'; + } else { + deviceType = 'desktop'; + } + + const sessionQuery = 'INSERT INTO sessions (user_id, session_key_hash, created_Date, last_login, valid_until, valid_days, last_ip, device_info, type) VALUES (?,?,NOW(),NOW(),DATE_ADD(NOW(), INTERVAL 365 DAY),365,?, ?, ?) RETURNING session_id'; + const sessionIdRes = await conn.query(sessionQuery, [userId, sessionKeyHash, ip, deviceInfo, deviceType]); + await conn.commit(); + + // Get session id of the created session + let sessionId: number = -1; + for (const row in sessionIdRes) { + if (row !== 'meta' && sessionIdRes[row].session_id != null) { + sessionId = sessionIdRes[row].session_id; + } + } + + return { + 'userId': userId.toString(), + 'sessionId': sessionId.toString(), + 'sessionKey': sessionKey + }; + + } catch (err) { + throw err; + } finally { + if (conn) { + conn.end(); + } + } +}; /** * Used in the checkUsernameAndEmail method as return value diff --git a/src/models/twitch-highlight-marker/HighlightMarker.router.ts b/src/models/twitch-highlight-marker/HighlightMarker.router.ts index 2723cdf..8dabce3 100644 --- a/src/models/twitch-highlight-marker/HighlightMarker.router.ts +++ b/src/models/twitch-highlight-marker/HighlightMarker.router.ts @@ -3,7 +3,7 @@ */ import express, {Request, Response} from 'express'; import {addHighlightRouter} from './addHighlight/AddHighlight.router'; -import logger from "../../middleware/logger"; +import logger from '../../middleware/logger'; /** * Router Definition @@ -19,4 +19,4 @@ highlightMarkerRouter.get('/', async (req: Request, res: Response) => { logger.error('Error handling a request: ' + e.message); res.status(500).send({'message': 'Internal Server Error. Try again later.'}); } -}) +}); diff --git a/src/models/twitch-highlight-marker/addHighlight/AddHighlight.router.ts b/src/models/twitch-highlight-marker/addHighlight/AddHighlight.router.ts index 916c5ac..3637ac0 100644 --- a/src/models/twitch-highlight-marker/addHighlight/AddHighlight.router.ts +++ b/src/models/twitch-highlight-marker/addHighlight/AddHighlight.router.ts @@ -3,7 +3,7 @@ */ import express, {Request, Response} from 'express'; import * as AddHighlightService from './addHighlights.service'; -import logger from "../../../middleware/logger"; +import logger from '../../../middleware/logger'; /** * Router Definition @@ -17,7 +17,7 @@ addHighlightRouter.get('/', (req: Request, res: Response) => { logger.error('Error handling a request: ' + e.message); res.status(500).send({'message': 'Internal Server Error. Try again later.'}); } -}) +}); addHighlightRouter.post('/', (req: Request, res: Response) => { try { @@ -43,4 +43,4 @@ addHighlightRouter.post('/', (req: Request, res: Response) => { logger.error('Error handling a request: ' + e.message); res.status(500).send({'message': 'Internal Server Error. Try again later.'}); } -}) +}); diff --git a/src/models/twitch-highlight-marker/addHighlight/addHighlights.service.ts b/src/models/twitch-highlight-marker/addHighlight/addHighlights.service.ts index cd58c8f..ea6c7b6 100644 --- a/src/models/twitch-highlight-marker/addHighlight/addHighlights.service.ts +++ b/src/models/twitch-highlight-marker/addHighlight/addHighlights.service.ts @@ -11,6 +11,10 @@ const pool = mariadb.createPool({ connectionLimit: 5 }); +/** + * Creates a new highlight entry in SQL + * @param req_body The request body + */ export const createHighlightEntry = async (req_body: any) => { let conn; try { @@ -35,4 +39,4 @@ export const createHighlightEntry = async (req_body: any) => { conn.end(); } } -} +};