Upgrade to proper user management

This commit is contained in:
2023-05-14 19:17:30 +02:00
parent 93c70b0e1d
commit 02f7424b56
9 changed files with 393 additions and 32 deletions
+182
View 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();
}
};