plutoapi-v2/src/models/partyplaner/user/user.service.ts

293 lines
9.1 KiB
TypeScript

import * as dotenv from 'dotenv';
import * as bcrypt from 'bcrypt';
import {Guid} from 'guid-typescript';
import {UserData} from './UserData.interface';
import {Session} from './Session.interface';
import {Status} from './Status.interface';
import {PartyPlanerDB} from '../PartyPlaner.db';
dotenv.config();
/**
* Returns all data about the given user
* @param useDev If the dev or prod database should be used
* @param userId The userId of the user to return the data for
* @return UserData An object containing the user data
*/
export const getUserData = async (useDev: boolean, userId: string): Promise<UserData> => {
let conn = await PartyPlanerDB.getConnection(useDev);
try {
let rows = await conn.query('SELECT username, email, first_name, last_Name, last_login, email_is_verified, is_premium_user FROM users WHERE user_id = ?', userId);
let user: UserData = {} as UserData;
for (let row of rows) {
user = {
username: row.username,
email: row.email,
firstName: row.first_name,
lastName: row.last_name,
lastLogin: row.last_login,
emailIsVerified: row.email_is_verified,
isPremiumUser: row.is_premium_user
};
}
return user;
} catch (err) {
throw err;
} finally {
// Return connection
await conn.end();
}
};
/**
* 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<any> => {
let conn = await PartyPlanerDB.getConnection(useDev);
try {
const rows = await conn.query('SELECT username, email FROM users');
let usernames: string[] = [];
let emails: string[] = [];
for (let row of rows) {
usernames.push(row.username);
emails.push(row.email);
}
return {
'usernames': usernames,
'emails': emails
};
} catch (err) {
throw err;
} finally {
// Return connection
await conn.end();
}
};
/**
* Creates a new user and a new session
* @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 firstName The first name of the new user
* @param lastName The last name 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 registerUser = async (useDev: boolean, username: string, email: string, firstName: string, lastName: string, password: string, ip: string, deviceInfo: string): Promise<Session> => {
let conn = await PartyPlanerDB.getConnection(useDev);
try {
const pwHash = bcrypt.hashSync(password, 10);
const sessionKey = Guid.create().toString();
const sessionKeyHash = bcrypt.hashSync(sessionKey, 10);
const verifyEmailCode = Guid.create().toString();
// Create user
const userQuery = 'INSERT INTO users (username, email, first_name, last_name, password_hash, verify_email_code, registration_date, last_login) VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW()) RETURNING user_id';
const userIdRes = await conn.query(userQuery, [username, email, firstName, lastName, pwHash, verifyEmailCode]);
await conn.commit();
// Get user id of the created user
let userId: number = -1;
for (const row of userIdRes) {
if (row.user_id != null) {
userId = row.user_id;
}
}
let deviceType = 'unknown';
if (deviceInfo.includes('PartyPlaner') || deviceInfo.includes('Dart')) {
deviceType = 'mobile';
} else {
deviceType = 'desktop';
}
// Create session
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 of sessionIdRes) {
if (row.session_id != null) {
sessionId = row.session_id;
}
}
return {
'userId': userId.toString(),
'sessionId': sessionId.toString(),
'sessionKey': sessionKey
};
} catch (err) {
throw err;
} finally {
// Return connection
await 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<Session> => {
let conn = await PartyPlanerDB.getConnection(useDev);
try {
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 of query_result) {
passwordHash = row.password_hash;
userId = 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 of sessionIdRes) {
if (row.session_id != null) {
sessionId = row.session_id;
}
}
return {
'userId': userId.toString(),
'sessionId': sessionId.toString(),
'sessionKey': sessionKey
};
} catch (err) {
throw err;
} finally {
// Return connection
await conn.end();
}
};
/**
* Checks if the given username and email are valid and not yet used by another user
* @param useDev If the dev database has to be used
* @param username The username to check
* @param email The email to check
*/
export const checkUsernameAndEmail = async (useDev: boolean, username: string, email: string): Promise<Status> => {
let conn = await PartyPlanerDB.getConnection(useDev);
try {
const usernameQuery = 'SELECT username FROM users WHERE username = ?';
const emailQuery = 'SELECT email FROM users WHERE email = ?';
const usernameRes = await conn.query(usernameQuery, username);
const emailRes = await conn.query(emailQuery, email);
let res: Status = {
hasProblems: false,
messages: [],
status: []
};
const usernameRegex = RegExp('^[a-zA-Z0-9\\-\\_]{4,20}$'); // Can contain a-z, A-Z, 0-9, -, _ and has to be 4-20 chars long
if (!usernameRegex.test(username)) {
// Username doesn't match requirements
res.hasProblems = true;
res.messages.push('The username contains illegal characters or is not the allowed size');
res.status.push('INVALID_USERNAME');
}
const emailRegex = RegExp('^[a-zA-Z0-9\\-\\_.]{1,30}\\@[a-zA-Z0-9\\-.]{1,20}\\.[a-z]{1,20}$'); // Normal email regex, user@plutodev.de
if (!emailRegex.test(email)) {
// Username doesn't match requirements
res.hasProblems = true;
res.messages.push('The email contains illegal characters or is not the allowed size');
res.status.push('INVALID_EMAIL');
}
if (usernameRes.length > 0) {
// Username is a duplicate
res.hasProblems = true;
res.messages.push('This username is already in use');
res.status.push('DUPLICATE_USERNAME');
}
if (emailRes.length > 0) {
// Email is a duplicate
res.hasProblems = true;
res.messages.push('This email is already in use');
res.status.push('DUPLICATE_EMAIL');
}
return res;
} catch (err) {
throw err;
} finally {
// Return connection
await conn.end();
}
};
export const checkSession = async (useDev: boolean, userId: string, sessionId: string, sessionKey: string): Promise<boolean> => {
let conn = await PartyPlanerDB.getConnection(useDev);
try {
let rows = await conn.query('SELECT session_key_hash FROM sessions WHERE user_id = ? AND session_id = ?', [userId, sessionId]);
let savedHash = '';
for (let row of rows) {
savedHash = row.session_key_hash;
}
return bcrypt.compareSync(sessionKey, savedHash);
} catch (err) {
throw err;
} finally {
// Return connection
await conn.end();
}
};