Upgrade to proper user management
This commit is contained in:
		
							parent
							
								
									93c70b0e1d
								
							
						
					
					
						commit
						02f7424b56
					
				|  | @ -5,6 +5,7 @@ import express, {Request, Response} from 'express'; | |||
| import {Guid} from 'guid-typescript'; | ||||
| import logger from '../../middleware/logger'; | ||||
| import {eventsRouter} from './events/events.router'; | ||||
| import {usersRouter} from './users/users.router'; | ||||
| 
 | ||||
| /** | ||||
|  * Router Definition | ||||
|  | @ -12,6 +13,7 @@ import {eventsRouter} from './events/events.router'; | |||
| export const calendarRouter = express.Router(); | ||||
| 
 | ||||
| calendarRouter.use('/events', eventsRouter); | ||||
| calendarRouter.use('/users', usersRouter); | ||||
| 
 | ||||
| 
 | ||||
| calendarRouter.get('/', async (req: Request, res: Response) => { | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| import * as dotenv from 'dotenv'; | ||||
| import * as UserService from '../users/users.service'; | ||||
| 
 | ||||
| 
 | ||||
| dotenv.config(); | ||||
|  | @ -7,34 +8,48 @@ dotenv.config(); | |||
|  * Checks if the password gives admin privileges (view / create / edit / delete) | ||||
|  * @param password | ||||
|  */ | ||||
| export const checkAdminPrivileges = (password: string) => { | ||||
| 	return password == process.env.ADMIN_CREDENTIAL; | ||||
| export const checkAdminPrivileges = async (sessionId: string, sessionKey: string, ip: string) => { | ||||
| 	if(sessionId) { | ||||
| 		let user = await UserService.checkSession(sessionId, sessionKey, ip); | ||||
| 		return user.is_active; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Checks if the password gives member view privileges | ||||
|  * @param password | ||||
|  */ | ||||
| export const checkMemberPrivileges = (password: string) => { | ||||
| 	return password == process.env.MEMBER_CREDENTIAL || password == process.env.ADMIN_CREDENTIAL; | ||||
| export const checkMemberPrivileges = async (sessionId: string, sessionKey: string, password: string, ip: string) => { | ||||
| 	if(sessionId) { | ||||
| 		let user = await UserService.checkSession(sessionId, sessionKey, ip); | ||||
| 		return user.is_active; | ||||
| 	} | ||||
| 
 | ||||
| 	return password == process.env.MEMBER_CREDENTIAL; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Checks if the password gives management view privileges | ||||
|  * @param password | ||||
|  */ | ||||
| export const checkManagementPrivileges = (password: string) => { | ||||
| 	return password == process.env.MANAGEMENT_CREDENTIAL || password == process.env.ADMIN_CREDENTIAL; | ||||
| export const checkManagementPrivileges = async (sessionId: string, sessionKey: string, password: string, ip: string) => { | ||||
| 	if(sessionId) { | ||||
| 		let user = await UserService.checkSession(sessionId, sessionKey, ip); | ||||
| 		return user.is_active; | ||||
| 	} | ||||
| 
 | ||||
| 	return password == process.env.MANAGEMENT_CREDENTIAL; | ||||
| } | ||||
| 
 | ||||
| export const hasAccess = (calendarName: string, password: string) => { | ||||
| export const hasAccess = async (calendarName: string, sessionId: string, sessionKey: string, password: string, ip: string) => { | ||||
| 	switch (calendarName) { | ||||
| 		case 'public': | ||||
| 			return true; | ||||
| 		case 'members': | ||||
| 			return checkMemberPrivileges(password); | ||||
| 			return await checkMemberPrivileges(sessionId, sessionKey, password, ip); | ||||
| 		case 'management': | ||||
| 			return checkManagementPrivileges(password); | ||||
| 			return await checkManagementPrivileges(sessionId, sessionKey, password, ip); | ||||
| 		default: | ||||
| 			return false; | ||||
| 	} | ||||
|  |  | |||
|  | @ -8,7 +8,8 @@ export interface Event { | |||
| 	endDateTime: Date; | ||||
| 	createdDate: Date; | ||||
| 	location: string; | ||||
| 	createdBy: string; | ||||
| 	createdBy?: string; | ||||
| 	createdById: number, | ||||
| 	url: string; | ||||
| 	wholeDay: boolean; | ||||
| } | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import {Event} from './event.interface'; | |||
| import * as EventService from './events.service'; | ||||
| import * as iCalService from './icalgenerator.service'; | ||||
| import * as CredentialService from './credentials.service'; | ||||
| import * as UserService from '../users/users.service'; | ||||
| import {Guid} from 'guid-typescript'; | ||||
| import logger from '../../../middleware/logger'; | ||||
| 
 | ||||
|  | @ -35,7 +36,10 @@ eventsRouter.get('/:calendar/json', async (req: Request, res: Response) => { | |||
| 	try { | ||||
| 		// Get request params
 | ||||
| 		let calendarName: string = req.params.calendar as string ?? ''; | ||||
| 		let sessionId: string = req.query.sessionId as string ?? ''; | ||||
| 		let sessionKey: string = req.query.sessionKey as string ?? ''; | ||||
| 		let password: string = req.query.password as string ?? ''; | ||||
| 		let ip: string = req.socket.remoteAddress ?? ''; | ||||
| 
 | ||||
| 		if (calendarName.length < 1) { | ||||
| 			res.status(400).send({'message': 'Please state the name of the calendar you want events from.'}); | ||||
|  | @ -49,7 +53,7 @@ eventsRouter.get('/:calendar/json', async (req: Request, res: Response) => { | |||
| 
 | ||||
| 		let calendarId: number = calendarNames.get(calendarName)!.id; | ||||
| 
 | ||||
| 		if (!CredentialService.hasAccess(calendarName, password)) { | ||||
| 		if (! await CredentialService.hasAccess(calendarName, sessionId, sessionKey, password, ip)) { | ||||
| 			res.status(403).send({'message': 'You do not have access to the specified calendar.'}); | ||||
| 			return; | ||||
| 		} | ||||
|  | @ -69,7 +73,10 @@ eventsRouter.get('/:calendar/ical', async (req: Request, res: Response) => { | |||
| 	try { | ||||
| 		// Get request params
 | ||||
| 		let calendarName: string = req.params.calendar as string ?? ''; | ||||
| 		let sessionId: string = req.query.sessionId as string ?? ''; | ||||
| 		let sessionKey: string = req.query.sessionKey as string ?? ''; | ||||
| 		let password: string = req.query.password as string ?? ''; | ||||
| 		let ip: string = req.socket.remoteAddress ?? ''; | ||||
| 
 | ||||
| 		if (calendarName.length < 1) { | ||||
| 			res.status(400).send({'message': 'Please state the name of the calendar you want events from.'}); | ||||
|  | @ -83,7 +90,7 @@ eventsRouter.get('/:calendar/ical', async (req: Request, res: Response) => { | |||
| 
 | ||||
| 		let calendarId: number = calendarNames.get(calendarName)!.id; | ||||
| 
 | ||||
| 		if (!CredentialService.hasAccess(calendarName, password)) { | ||||
| 		if (! await CredentialService.hasAccess(calendarName, sessionId, sessionKey, password, ip)) { | ||||
| 			res.status(403).send({'message': 'You do not have access to the specified calendar.'}); | ||||
| 			return; | ||||
| 		} | ||||
|  | @ -112,10 +119,14 @@ eventsRouter.get('/:calendar/ical', async (req: Request, res: Response) => { | |||
| eventsRouter.post('/', async (req: Request, res: Response) => { | ||||
| 	try { | ||||
| 		// Get params
 | ||||
| 		let password = req.body.password; | ||||
| 		let sessionId: string = req.query.sessionId as string ?? ''; | ||||
| 		let sessionKey: string = req.query.sessionKey as string ?? ''; | ||||
| 		let ip: string = req.socket.remoteAddress ?? ''; | ||||
| 
 | ||||
| 		if (!CredentialService.checkAdminPrivileges(password)) { | ||||
| 			res.status(403).send({'message': 'Insufficient privileges.'}); | ||||
| 		let user = await UserService.checkSession(sessionId, sessionKey, ip); | ||||
| 
 | ||||
| 		if (!user.is_active) { | ||||
| 			res.status(403).send({'message': 'You do not have access to the specified calendar.'}); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
|  | @ -123,8 +134,7 @@ eventsRouter.post('/', async (req: Request, res: Response) => { | |||
| 			req.body.calendarId === undefined || | ||||
| 			isNullOrBlank(req.body.name) || | ||||
| 			req.body.startDateTime === undefined || | ||||
| 			req.body.endDateTime === undefined || | ||||
| 			isNullOrBlank(req.body.createdBy) | ||||
| 			req.body.endDateTime === undefined | ||||
| 		) { | ||||
| 			res.status(400).send({'message': 'Required parameters missing'}); | ||||
| 			return; | ||||
|  | @ -140,7 +150,7 @@ eventsRouter.post('/', async (req: Request, res: Response) => { | |||
| 			endDateTime: new Date(req.body.endDateTime), | ||||
| 			createdDate: new Date(), | ||||
| 			location: req.body.location ?? '', | ||||
| 			createdBy: req.body.createdBy ?? '', | ||||
| 			createdById: user.user_id ?? -1, | ||||
| 			url: req.body.url ?? '', | ||||
| 			wholeDay: req.body.wholeDay ?? false | ||||
| 		}; | ||||
|  | @ -165,10 +175,14 @@ eventsRouter.post('/', async (req: Request, res: Response) => { | |||
| eventsRouter.put('/:eventId', async (req: Request, res: Response) => { | ||||
| 	try { | ||||
| 		// Get params
 | ||||
| 		let password = req.body.password; | ||||
| 		let sessionId: string = req.query.sessionId as string ?? ''; | ||||
| 		let sessionKey: string = req.query.sessionKey as string ?? ''; | ||||
| 		let ip: string = req.socket.remoteAddress ?? ''; | ||||
| 
 | ||||
| 		if (!CredentialService.checkAdminPrivileges(password)) { | ||||
| 			res.status(403).send({'message': 'Insufficient privileges.'}); | ||||
| 		let user = await UserService.checkSession(sessionId, sessionKey, ip); | ||||
| 
 | ||||
| 		if (!user.is_active) { | ||||
| 			res.status(403).send({'message': 'You do not have access to the specified calendar.'}); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
|  | @ -177,8 +191,7 @@ eventsRouter.put('/:eventId', async (req: Request, res: Response) => { | |||
| 			req.body.calendarId === undefined || | ||||
| 			isNullOrBlank(req.body.name) || | ||||
| 			req.body.startDateTime === undefined || | ||||
| 			req.body.endDateTime === undefined || | ||||
| 			isNullOrBlank(req.body.createdBy) | ||||
| 			req.body.endDateTime === undefined | ||||
| 		) { | ||||
| 			res.status(400).send({'message': 'Required parameters missing'}); | ||||
| 			return; | ||||
|  | @ -195,6 +208,7 @@ eventsRouter.put('/:eventId', async (req: Request, res: Response) => { | |||
| 			createdDate: new Date(), | ||||
| 			location: req.body.location ?? '', | ||||
| 			createdBy: req.body.createdBy ?? '', | ||||
| 			createdById: user.user_id ?? -1, | ||||
| 			url: req.body.url ?? '', | ||||
| 			wholeDay: req.body.wholeDay ?? false | ||||
| 		}; | ||||
|  | @ -222,10 +236,14 @@ eventsRouter.put('/:eventId', async (req: Request, res: Response) => { | |||
| eventsRouter.delete('/:eventId', async (req: Request, res: Response) => { | ||||
| 	try { | ||||
| 		// Get params
 | ||||
| 		let password = req.body.password; | ||||
| 		let sessionId: string = req.query.sessionId as string ?? ''; | ||||
| 		let sessionKey: string = req.query.sessionKey as string ?? ''; | ||||
| 		let ip: string = req.socket.remoteAddress ?? ''; | ||||
| 
 | ||||
| 		if (!CredentialService.checkAdminPrivileges(password)) { | ||||
| 			res.status(403).send({'message': 'Insufficient privileges.'}); | ||||
| 		let user = await UserService.checkSession(sessionId, sessionKey, ip); | ||||
| 
 | ||||
| 		if (!user.is_active) { | ||||
| 			res.status(403).send({'message': 'You do not have access to the specified calendar.'}); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
|  | @ -247,6 +265,7 @@ eventsRouter.delete('/:eventId', async (req: Request, res: Response) => { | |||
| 			createdDate: new Date(), | ||||
| 			location: '', | ||||
| 			createdBy: '', | ||||
| 			createdById: -1, | ||||
| 			url: '', | ||||
| 			wholeDay: false | ||||
| 		}; | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ export const getAllEvents = async (calendarId: number): Promise<Event[]> => { | |||
| 	let conn = await NachklangCalendarDB.getConnection(); | ||||
| 	let eventRows: Event[] = []; | ||||
| 	try { | ||||
| 		const eventsQuery = 'SELECT * FROM events WHERE calendar_id = ?'; | ||||
| 		const eventsQuery = 'SELECT e.*, u.full_name FROM events e LEFT OUTER JOIN users u ON e.created_by_id = u.user_id WHERE calendar_id = ?'; | ||||
| 		const eventsRes = await conn.query(eventsQuery, calendarId); | ||||
| 
 | ||||
| 		for (let row of eventsRes) { | ||||
|  | @ -28,7 +28,8 @@ export const getAllEvents = async (calendarId: number): Promise<Event[]> => { | |||
| 				endDateTime: row.end_datetime, | ||||
| 				createdDate: row.created_date, | ||||
| 				location: row.location, | ||||
| 				createdBy: row.created_by, | ||||
| 				createdBy: row.full_name, | ||||
| 				createdById: row.created_by_id, | ||||
| 				url: row.url, | ||||
| 				wholeDay: row.whole_day | ||||
| 			}); | ||||
|  | @ -51,8 +52,8 @@ export const createEvent = async (event: Event): Promise<number> => { | |||
| 	let conn = await NachklangCalendarDB.getConnection(); | ||||
| 	try { | ||||
| 		let eventUUID = Guid.create().toString(); | ||||
| 		const eventsQuery = 'INSERT INTO events (calendar_id, uuid, name, description, start_datetime, end_datetime, location, created_by, url, whole_day) VALUES (?,?,?,?,?,?,?,?,?,?) RETURNING event_id'; | ||||
| 		const eventsRes = await conn.execute(eventsQuery, [event.calendarId, eventUUID, event.name, event.description, event.startDateTime, event.endDateTime, event.location, event.createdBy, event.url, event.wholeDay]); | ||||
| 		const eventsQuery = 'INSERT INTO events (calendar_id, uuid, name, description, start_datetime, end_datetime, location, created_by_id, url, whole_day) VALUES (?,?,?,?,?,?,?,?,?,?) RETURNING event_id'; | ||||
| 		const eventsRes = await conn.execute(eventsQuery, [event.calendarId, eventUUID, event.name, event.description, event.startDateTime, event.endDateTime, event.location, event.createdById, event.url, event.wholeDay]); | ||||
| 
 | ||||
| 		return eventsRes[0].event_id; | ||||
| 	} catch (err) { | ||||
|  | @ -72,8 +73,8 @@ export const createEvent = async (event: Event): Promise<number> => { | |||
| export const updateEvent = async (event: Event): Promise<number> => { | ||||
| 	let conn = await NachklangCalendarDB.getConnection(); | ||||
| 	try { | ||||
| 		const eventsQuery = 'UPDATE events SET name = ?, description = ?, start_datetime = ?, end_datetime = ?, location = ?, created_by = ?, url = ?, whole_day = ? WHERE event_id = ?'; | ||||
| 		const eventsRes = await conn.execute(eventsQuery, [event.name, event.description, event.startDateTime, event.endDateTime, event.location, event.createdBy, event.url, event.wholeDay, event.eventId]); | ||||
| 		const eventsQuery = 'UPDATE events SET name = ?, description = ?, start_datetime = ?, end_datetime = ?, location = ?, created_by_id = ?, url = ?, whole_day = ? WHERE event_id = ?'; | ||||
| 		const eventsRes = await conn.execute(eventsQuery, [event.name, event.description, event.startDateTime, event.endDateTime, event.location, event.createdById, event.url, event.wholeDay, event.eventId]); | ||||
| 
 | ||||
| 		return eventsRes.affectedRows; | ||||
| 	} catch (err) { | ||||
|  |  | |||
							
								
								
									
										9
									
								
								src/models/calendar/users/session.interface.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/models/calendar/users/session.interface.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| export interface Session { | ||||
| 	session_id: number; | ||||
| 	user_id: number; | ||||
| 	session_key: string; | ||||
| 	session_key_hash: string; | ||||
| 	created_date?: Date; | ||||
| 	valid_until?: Date; | ||||
| 	last_ip: string; | ||||
| } | ||||
							
								
								
									
										7
									
								
								src/models/calendar/users/user.interface.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/models/calendar/users/user.interface.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| export interface User { | ||||
| 	user_id: number; | ||||
| 	full_name: string; | ||||
| 	password_hash: string; | ||||
| 	email: string; | ||||
| 	is_active: boolean; | ||||
| } | ||||
							
								
								
									
										125
									
								
								src/models/calendar/users/users.router.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/models/calendar/users/users.router.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,125 @@ | |||
| /** | ||||
|  * Required External Modules and Interfaces | ||||
|  */ | ||||
| 
 | ||||
| import express, {Request, Response} from 'express'; | ||||
| import * as UserService from './users.service'; | ||||
| import {Session} from './session.interface'; | ||||
| import {User} from './user.interface'; | ||||
| import {Guid} from 'guid-typescript'; | ||||
| import logger from '../../../middleware/logger'; | ||||
| 
 | ||||
| /** | ||||
|  * Router Definition | ||||
|  */ | ||||
| 
 | ||||
| export const usersRouter = express.Router(); | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Controller Definitions | ||||
|  */ | ||||
| 
 | ||||
| // 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) { | ||||
| 			// Missing
 | ||||
| 			res.status(400).send(JSON.stringify({message: 'Missing parameters'})); | ||||
| 			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({ | ||||
| 			session_id: session.session_id, | ||||
| 			session_key: session.session_key | ||||
| 		}); | ||||
| 	} 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 | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
| // 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 = await UserService.login(email, password, ip); | ||||
| 
 | ||||
| 		if (!session.session_id) { | ||||
| 			// Error logging in, probably wrong username / password
 | ||||
| 			res.status(401).send(JSON.stringify({messages: ['Wrong username and / or password']})); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		// Send the session details back to the user
 | ||||
| 		res.status(200).send({ | ||||
| 			session_id: session.session_id, | ||||
| 			session_key: session.session_key | ||||
| 		}); | ||||
| 	} 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 | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
| // POST users/checkSessionValid
 | ||||
| usersRouter.post('/checkSessionValid', async (req: Request, res: Response) => { | ||||
| 	try { | ||||
| 		const ip: string = req.socket.remoteAddress ?? ''; | ||||
| 		const session_id = req.body.session_id; | ||||
| 		const session_key = req.body.session_key; | ||||
| 
 | ||||
| 		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 = await UserService.checkSession(session_id, session_key, ip); | ||||
| 
 | ||||
| 		if (!user.user_id) { | ||||
| 			// 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 | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
							
								
								
									
										182
									
								
								src/models/calendar/users/users.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								src/models/calendar/users/users.service.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,182 @@ | |||
| import * as dotenv from 'dotenv'; | ||||
| import * as bcrypt from 'bcrypt'; | ||||
| import {Guid} from 'guid-typescript'; | ||||
| import {User} from './user.interface'; | ||||
| import {Session} from './session.interface'; | ||||
| import {NachklangCalendarDB} from '../Calendar.db'; | ||||
| 
 | ||||
| dotenv.config(); | ||||
| 
 | ||||
| /** | ||||
|  * Data Model Interfaces | ||||
|  */ | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Service Methods | ||||
|  */ | ||||
| 
 | ||||
| /** | ||||
|  * Creates a user record in the database, also creates a session. Returns the session if successful. | ||||
|  */ | ||||
| export const createUser = async (email: string, password: string, fullName: string, ip: string): Promise<Session> => { | ||||
| 	let conn = await NachklangCalendarDB.getConnection(); | ||||
| 	try { | ||||
| 		// Hash password and generate + hash session key
 | ||||
| 		const pwHash = bcrypt.hashSync(password, 10); | ||||
| 		const sessionKey = Guid.create().toString(); | ||||
| 		const sessionKeyHash = bcrypt.hashSync(sessionKey, 10); | ||||
| 
 | ||||
| 		// Create user entry in SQL
 | ||||
| 		const userQuery = 'INSERT INTO users (email, password_hash, full_name) VALUES (?, ?, ?) RETURNING user_id'; | ||||
| 		const userIdRes = await conn.query(userQuery, [email, pwHash, fullName]); | ||||
| 
 | ||||
| 		// Get user id of the created user
 | ||||
| 		let userId: number = -1; | ||||
| 		for (const row of userIdRes) { | ||||
| 			userId = row.user_id; | ||||
| 		} | ||||
| 
 | ||||
| 		// Create session
 | ||||
| 		const sessionQuery = 'INSERT INTO sessions (user_id, session_key_hash, created_date, valid_until, last_ip) VALUES (?,?,NOW(),DATE_ADD(NOW(), INTERVAL 30 DAY),?) RETURNING session_id'; | ||||
| 		const sessionIdRes = await conn.query(sessionQuery, [userId, sessionKeyHash, ip]); | ||||
| 		await conn.commit(); | ||||
| 
 | ||||
| 		// Get session id of the created session
 | ||||
| 		let sessionId: number = -1; | ||||
| 		for (const row of sessionIdRes) { | ||||
| 			sessionId = row.session_id; | ||||
| 		} | ||||
| 
 | ||||
| 		return { | ||||
| 			session_id: sessionId, | ||||
| 			user_id: userId, | ||||
| 			session_key: sessionKey, | ||||
| 			session_key_hash: 'HIDDEN', | ||||
| 			last_ip: ip | ||||
| 		}; | ||||
| 	} catch (err) { | ||||
| 		throw err; | ||||
| 	} finally { | ||||
| 		// Return connection
 | ||||
| 		await conn.end(); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Checks if the given credentials are valid and creates a new session if they are. | ||||
|  * Returns the session information in case of a successful login | ||||
|  */ | ||||
| export const login = async (email: string, password: string, ip: string): Promise<Session> => { | ||||
| 	let conn = await NachklangCalendarDB.getConnection(); | ||||
| 	try { | ||||
| 		// Get saved password hash
 | ||||
| 		const query = 'SELECT user_id, password_hash FROM users WHERE email = ?'; | ||||
| 		const userRows = await conn.query(query, email); | ||||
| 		let savedHash = ''; | ||||
| 		let userId = -1; | ||||
| 		for (const row of userRows) { | ||||
| 			savedHash = row.password_hash; | ||||
| 			userId = row.user_id; | ||||
| 		} | ||||
| 
 | ||||
| 		// Check for correct password
 | ||||
| 		if (!bcrypt.compareSync(password, savedHash)) { | ||||
| 			// Wrong password, return invalid
 | ||||
| 			return {} as Session; | ||||
| 		} | ||||
| 
 | ||||
| 		// Generate + hash session key
 | ||||
| 		const sessionKey = Guid.create().toString(); | ||||
| 		const sessionKeyHash = bcrypt.hashSync(sessionKey, 10); | ||||
| 
 | ||||
| 		// Create session
 | ||||
| 		const sessionQuery = 'INSERT INTO sessions (user_id, session_key_hash, created_date, valid_until, last_ip) VALUES (?,?,NOW(),DATE_ADD(NOW(), INTERVAL 30 DAY),?) RETURNING session_id'; | ||||
| 		const sessionIdRes = await conn.query(sessionQuery, [userId, sessionKeyHash, ip]); | ||||
| 		await conn.commit(); | ||||
| 
 | ||||
| 		// Get session id of the created session
 | ||||
| 		let sessionId: number = -1; | ||||
| 		for (const row of sessionIdRes) { | ||||
| 			sessionId = row.session_id; | ||||
| 		} | ||||
| 
 | ||||
| 		return { | ||||
| 			session_id: sessionId, | ||||
| 			user_id: userId, | ||||
| 			session_key: sessionKey, | ||||
| 			session_key_hash: 'HIDDEN', | ||||
| 			last_ip: ip | ||||
| 		}; | ||||
| 	} catch (err) { | ||||
| 		throw err; | ||||
| 	} finally { | ||||
| 		// Return connection
 | ||||
| 		await conn.end(); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Checks if the given session information are valid and returns the user information if they are | ||||
|  */ | ||||
| export const checkSession = async (sessionId: string, sessionKey: string, ip: string): Promise<User> => { | ||||
| 	let conn = await NachklangCalendarDB.getConnection(); | ||||
| 	try { | ||||
| 		// Get saved session key hash
 | ||||
| 		const query = 'SELECT user_id, session_key_hash, valid_until FROM sessions WHERE session_id = ?'; | ||||
| 		const sessionRows = await conn.query(query, sessionId); | ||||
| 		let savedHash = ''; | ||||
| 		let userId = -1; | ||||
| 		let validUntil = new Date(); | ||||
| 		for (const row of sessionRows) { | ||||
| 			savedHash = row.session_key_hash; | ||||
| 			userId = row.user_id; | ||||
| 			validUntil = row.valid_until; | ||||
| 		} | ||||
| 
 | ||||
| 		// Check for correct key
 | ||||
| 		if (!bcrypt.compareSync(sessionKey, savedHash)) { | ||||
| 			// Wrong key, return invalid
 | ||||
| 			return {} as User; | ||||
| 		} | ||||
| 
 | ||||
| 		// Check if the session is still valid
 | ||||
| 		if (validUntil <= new Date()) { | ||||
| 			// Session expired, return invalid
 | ||||
| 			return {} as User; | ||||
| 		} | ||||
| 
 | ||||
| 		// Update session entry in SQL
 | ||||
| 		const updateSessionsQuery = 'UPDATE sessions SET last_IP = ? WHERE session_id = ?'; | ||||
| 		const userIdRes = await conn.query(updateSessionsQuery, [ip, sessionId]); | ||||
| 		await conn.commit(); | ||||
| 
 | ||||
| 		// Get the other required user information
 | ||||
| 		const userQuery = 'SELECT user_id, email, full_name, is_active FROM users WHERE user_id = ?'; | ||||
| 		const userRows = await conn.query(userQuery, userId); | ||||
| 		let username = ''; | ||||
| 		let email = ''; | ||||
| 		let fullName = ''; | ||||
| 		let is_active = false; | ||||
| 		for (const row of userRows) { | ||||
| 			username = row.username; | ||||
| 			email = row.email; | ||||
| 			fullName = row.full_name; | ||||
| 			is_active = row.is_active; | ||||
| 		} | ||||
| 
 | ||||
| 		// Everything is fine, return user information
 | ||||
| 		return { | ||||
| 			user_id: userId, | ||||
| 			email: email, | ||||
| 			password_hash: 'HIDDEN', | ||||
| 			full_name: fullName, | ||||
| 			is_active: is_active | ||||
| 		}; | ||||
| 	} catch (err) { | ||||
| 		throw err; | ||||
| 	} finally { | ||||
| 		// Return connection
 | ||||
| 		await conn.end(); | ||||
| 	} | ||||
| }; | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user