Compare commits

..

No commits in common. "95983021edbcee5c2ac6d0af50ae24603768edf6" and "93c70b0e1d1305f6f15c62f9031707975d64b9b8" have entirely different histories.

9 changed files with 32 additions and 393 deletions

View File

@ -5,7 +5,6 @@ import express, {Request, Response} from 'express';
import {Guid} from 'guid-typescript'; import {Guid} from 'guid-typescript';
import logger from '../../middleware/logger'; import logger from '../../middleware/logger';
import {eventsRouter} from './events/events.router'; import {eventsRouter} from './events/events.router';
import {usersRouter} from './users/users.router';
/** /**
* Router Definition * Router Definition
@ -13,7 +12,6 @@ import {usersRouter} from './users/users.router';
export const calendarRouter = express.Router(); export const calendarRouter = express.Router();
calendarRouter.use('/events', eventsRouter); calendarRouter.use('/events', eventsRouter);
calendarRouter.use('/users', usersRouter);
calendarRouter.get('/', async (req: Request, res: Response) => { calendarRouter.get('/', async (req: Request, res: Response) => {

View File

@ -1,5 +1,4 @@
import * as dotenv from 'dotenv'; import * as dotenv from 'dotenv';
import * as UserService from '../users/users.service';
dotenv.config(); dotenv.config();
@ -8,48 +7,34 @@ dotenv.config();
* Checks if the password gives admin privileges (view / create / edit / delete) * Checks if the password gives admin privileges (view / create / edit / delete)
* @param password * @param password
*/ */
export const checkAdminPrivileges = async (sessionId: string, sessionKey: string, ip: string) => { export const checkAdminPrivileges = (password: string) => {
if(sessionId) { return password == process.env.ADMIN_CREDENTIAL;
let user = await UserService.checkSession(sessionId, sessionKey, ip);
return user.isActive;
}
return false;
} }
/** /**
* Checks if the password gives member view privileges * Checks if the password gives member view privileges
* @param password * @param password
*/ */
export const checkMemberPrivileges = async (sessionId: string, sessionKey: string, password: string, ip: string) => { export const checkMemberPrivileges = (password: string) => {
if(sessionId) { return password == process.env.MEMBER_CREDENTIAL || password == process.env.ADMIN_CREDENTIAL;
let user = await UserService.checkSession(sessionId, sessionKey, ip);
return user.isActive;
}
return password == process.env.MEMBER_CREDENTIAL;
} }
/** /**
* Checks if the password gives management view privileges * Checks if the password gives management view privileges
* @param password * @param password
*/ */
export const checkManagementPrivileges = async (sessionId: string, sessionKey: string, password: string, ip: string) => { export const checkManagementPrivileges = (password: string) => {
if(sessionId) { return password == process.env.MANAGEMENT_CREDENTIAL || password == process.env.ADMIN_CREDENTIAL;
let user = await UserService.checkSession(sessionId, sessionKey, ip);
return user.isActive;
} }
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) { switch (calendarName) {
case 'public': case 'public':
return true; return true;
case 'members': case 'members':
return await checkMemberPrivileges(sessionId, sessionKey, password, ip); return checkMemberPrivileges(password);
case 'management': case 'management':
return await checkManagementPrivileges(sessionId, sessionKey, password, ip); return checkManagementPrivileges(password);
default: default:
return false; return false;
} }

View File

@ -8,8 +8,7 @@ export interface Event {
endDateTime: Date; endDateTime: Date;
createdDate: Date; createdDate: Date;
location: string; location: string;
createdBy?: string; createdBy: string;
createdById: number,
url: string; url: string;
wholeDay: boolean; wholeDay: boolean;
} }

View File

@ -7,7 +7,6 @@ import {Event} from './event.interface';
import * as EventService from './events.service'; import * as EventService from './events.service';
import * as iCalService from './icalgenerator.service'; import * as iCalService from './icalgenerator.service';
import * as CredentialService from './credentials.service'; import * as CredentialService from './credentials.service';
import * as UserService from '../users/users.service';
import {Guid} from 'guid-typescript'; import {Guid} from 'guid-typescript';
import logger from '../../../middleware/logger'; import logger from '../../../middleware/logger';
@ -36,10 +35,7 @@ eventsRouter.get('/:calendar/json', async (req: Request, res: Response) => {
try { try {
// Get request params // Get request params
let calendarName: string = req.params.calendar as string ?? ''; 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 password: string = req.query.password as string ?? '';
let ip: string = req.socket.remoteAddress ?? '';
if (calendarName.length < 1) { if (calendarName.length < 1) {
res.status(400).send({'message': 'Please state the name of the calendar you want events from.'}); res.status(400).send({'message': 'Please state the name of the calendar you want events from.'});
@ -53,7 +49,7 @@ eventsRouter.get('/:calendar/json', async (req: Request, res: Response) => {
let calendarId: number = calendarNames.get(calendarName)!.id; let calendarId: number = calendarNames.get(calendarName)!.id;
if (! await CredentialService.hasAccess(calendarName, sessionId, sessionKey, password, ip)) { if (!CredentialService.hasAccess(calendarName, password)) {
res.status(403).send({'message': 'You do not have access to the specified calendar.'}); res.status(403).send({'message': 'You do not have access to the specified calendar.'});
return; return;
} }
@ -73,10 +69,7 @@ eventsRouter.get('/:calendar/ical', async (req: Request, res: Response) => {
try { try {
// Get request params // Get request params
let calendarName: string = req.params.calendar as string ?? ''; 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 password: string = req.query.password as string ?? '';
let ip: string = req.socket.remoteAddress ?? '';
if (calendarName.length < 1) { if (calendarName.length < 1) {
res.status(400).send({'message': 'Please state the name of the calendar you want events from.'}); res.status(400).send({'message': 'Please state the name of the calendar you want events from.'});
@ -90,7 +83,7 @@ eventsRouter.get('/:calendar/ical', async (req: Request, res: Response) => {
let calendarId: number = calendarNames.get(calendarName)!.id; let calendarId: number = calendarNames.get(calendarName)!.id;
if (! await CredentialService.hasAccess(calendarName, sessionId, sessionKey, password, ip)) { if (!CredentialService.hasAccess(calendarName, password)) {
res.status(403).send({'message': 'You do not have access to the specified calendar.'}); res.status(403).send({'message': 'You do not have access to the specified calendar.'});
return; return;
} }
@ -119,14 +112,10 @@ eventsRouter.get('/:calendar/ical', async (req: Request, res: Response) => {
eventsRouter.post('/', async (req: Request, res: Response) => { eventsRouter.post('/', async (req: Request, res: Response) => {
try { try {
// Get params // Get params
let sessionId: string = req.query.sessionId as string ?? ''; let password = req.body.password;
let sessionKey: string = req.query.sessionKey as string ?? '';
let ip: string = req.socket.remoteAddress ?? '';
let user = await UserService.checkSession(sessionId, sessionKey, ip); if (!CredentialService.checkAdminPrivileges(password)) {
res.status(403).send({'message': 'Insufficient privileges.'});
if (!user.isActive) {
res.status(403).send({'message': 'You do not have access to the specified calendar.'});
return; return;
} }
@ -134,7 +123,8 @@ eventsRouter.post('/', async (req: Request, res: Response) => {
req.body.calendarId === undefined || req.body.calendarId === undefined ||
isNullOrBlank(req.body.name) || isNullOrBlank(req.body.name) ||
req.body.startDateTime === undefined || req.body.startDateTime === undefined ||
req.body.endDateTime === undefined req.body.endDateTime === undefined ||
isNullOrBlank(req.body.createdBy)
) { ) {
res.status(400).send({'message': 'Required parameters missing'}); res.status(400).send({'message': 'Required parameters missing'});
return; return;
@ -150,7 +140,7 @@ eventsRouter.post('/', async (req: Request, res: Response) => {
endDateTime: new Date(req.body.endDateTime), endDateTime: new Date(req.body.endDateTime),
createdDate: new Date(), createdDate: new Date(),
location: req.body.location ?? '', location: req.body.location ?? '',
createdById: user.userId ?? -1, createdBy: req.body.createdBy ?? '',
url: req.body.url ?? '', url: req.body.url ?? '',
wholeDay: req.body.wholeDay ?? false wholeDay: req.body.wholeDay ?? false
}; };
@ -175,14 +165,10 @@ eventsRouter.post('/', async (req: Request, res: Response) => {
eventsRouter.put('/:eventId', async (req: Request, res: Response) => { eventsRouter.put('/:eventId', async (req: Request, res: Response) => {
try { try {
// Get params // Get params
let sessionId: string = req.query.sessionId as string ?? ''; let password = req.body.password;
let sessionKey: string = req.query.sessionKey as string ?? '';
let ip: string = req.socket.remoteAddress ?? '';
let user = await UserService.checkSession(sessionId, sessionKey, ip); if (!CredentialService.checkAdminPrivileges(password)) {
res.status(403).send({'message': 'Insufficient privileges.'});
if (!user.isActive) {
res.status(403).send({'message': 'You do not have access to the specified calendar.'});
return; return;
} }
@ -191,7 +177,8 @@ eventsRouter.put('/:eventId', async (req: Request, res: Response) => {
req.body.calendarId === undefined || req.body.calendarId === undefined ||
isNullOrBlank(req.body.name) || isNullOrBlank(req.body.name) ||
req.body.startDateTime === undefined || req.body.startDateTime === undefined ||
req.body.endDateTime === undefined req.body.endDateTime === undefined ||
isNullOrBlank(req.body.createdBy)
) { ) {
res.status(400).send({'message': 'Required parameters missing'}); res.status(400).send({'message': 'Required parameters missing'});
return; return;
@ -208,7 +195,6 @@ eventsRouter.put('/:eventId', async (req: Request, res: Response) => {
createdDate: new Date(), createdDate: new Date(),
location: req.body.location ?? '', location: req.body.location ?? '',
createdBy: req.body.createdBy ?? '', createdBy: req.body.createdBy ?? '',
createdById: user.userId ?? -1,
url: req.body.url ?? '', url: req.body.url ?? '',
wholeDay: req.body.wholeDay ?? false wholeDay: req.body.wholeDay ?? false
}; };
@ -236,14 +222,10 @@ eventsRouter.put('/:eventId', async (req: Request, res: Response) => {
eventsRouter.delete('/:eventId', async (req: Request, res: Response) => { eventsRouter.delete('/:eventId', async (req: Request, res: Response) => {
try { try {
// Get params // Get params
let sessionId: string = req.query.sessionId as string ?? ''; let password = req.body.password;
let sessionKey: string = req.query.sessionKey as string ?? '';
let ip: string = req.socket.remoteAddress ?? '';
let user = await UserService.checkSession(sessionId, sessionKey, ip); if (!CredentialService.checkAdminPrivileges(password)) {
res.status(403).send({'message': 'Insufficient privileges.'});
if (!user.isActive) {
res.status(403).send({'message': 'You do not have access to the specified calendar.'});
return; return;
} }
@ -265,7 +247,6 @@ eventsRouter.delete('/:eventId', async (req: Request, res: Response) => {
createdDate: new Date(), createdDate: new Date(),
location: '', location: '',
createdBy: '', createdBy: '',
createdById: -1,
url: '', url: '',
wholeDay: false wholeDay: false
}; };

View File

@ -14,7 +14,7 @@ export const getAllEvents = async (calendarId: number): Promise<Event[]> => {
let conn = await NachklangCalendarDB.getConnection(); let conn = await NachklangCalendarDB.getConnection();
let eventRows: Event[] = []; let eventRows: Event[] = [];
try { try {
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 eventsQuery = 'SELECT * FROM events WHERE calendar_id = ?';
const eventsRes = await conn.query(eventsQuery, calendarId); const eventsRes = await conn.query(eventsQuery, calendarId);
for (let row of eventsRes) { for (let row of eventsRes) {
@ -28,8 +28,7 @@ export const getAllEvents = async (calendarId: number): Promise<Event[]> => {
endDateTime: row.end_datetime, endDateTime: row.end_datetime,
createdDate: row.created_date, createdDate: row.created_date,
location: row.location, location: row.location,
createdBy: row.full_name, createdBy: row.created_by,
createdById: row.created_by_id,
url: row.url, url: row.url,
wholeDay: row.whole_day wholeDay: row.whole_day
}); });
@ -52,8 +51,8 @@ export const createEvent = async (event: Event): Promise<number> => {
let conn = await NachklangCalendarDB.getConnection(); let conn = await NachklangCalendarDB.getConnection();
try { try {
let eventUUID = Guid.create().toString(); let eventUUID = Guid.create().toString();
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 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.createdById, event.url, event.wholeDay]); 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]);
return eventsRes[0].event_id; return eventsRes[0].event_id;
} catch (err) { } catch (err) {
@ -73,8 +72,8 @@ export const createEvent = async (event: Event): Promise<number> => {
export const updateEvent = async (event: Event): Promise<number> => { export const updateEvent = async (event: Event): Promise<number> => {
let conn = await NachklangCalendarDB.getConnection(); let conn = await NachklangCalendarDB.getConnection();
try { try {
const eventsQuery = 'UPDATE events SET name = ?, description = ?, start_datetime = ?, end_datetime = ?, location = ?, created_by_id = ?, url = ?, whole_day = ? WHERE event_id = ?'; 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.createdById, event.url, event.wholeDay, event.eventId]); const eventsRes = await conn.execute(eventsQuery, [event.name, event.description, event.startDateTime, event.endDateTime, event.location, event.createdBy, event.url, event.wholeDay, event.eventId]);
return eventsRes.affectedRows; return eventsRes.affectedRows;
} catch (err) { } catch (err) {

View File

@ -1,9 +0,0 @@
export interface Session {
sessionId: number;
userId: number;
sessionKey: string;
sessionKeyHash: string;
createdDate?: Date;
validUntil?: Date;
lastIP: string;
}

View File

@ -1,7 +0,0 @@
export interface User {
userId: number;
fullName: string;
passwordHash: string;
email: string;
isActive: boolean;
}

View File

@ -1,125 +0,0 @@
/**
* 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({
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
});
}
});
// 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.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
});
}
});
// 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 = await UserService.checkSession(session_id, session_key, ip);
if (!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
});
}
});

View File

@ -1,182 +0,0 @@
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 {
sessionId: sessionId,
userId: userId,
sessionKey: sessionKey,
sessionKeyHash: 'HIDDEN',
lastIP: 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 {
sessionId: sessionId,
userId: userId,
sessionKey: sessionKey,
sessionKeyHash: 'HIDDEN',
lastIP: 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 {
userId: userId,
email: email,
passwordHash: 'HIDDEN',
fullName: fullName,
isActive: is_active
};
} catch (err) {
throw err;
} finally {
// Return connection
await conn.end();
}
};