293 lines
9.1 KiB
TypeScript
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();
|
|
}
|
|
};
|