Add password reset endpoints and mail service for user activation
Jenkins Production Deployment

This commit is contained in:
2023-12-30 22:50:47 +01:00
parent 34a4a6664f
commit 8f93e1ab7d
5 changed files with 447 additions and 6 deletions
+104 -2
View File
@@ -4,6 +4,7 @@ import {Guid} from 'guid-typescript';
import {User} from './user.interface';
import {Session} from './session.interface';
import {NachklangCalendarDB} from '../Calendar.db';
import {MailService} from "../../../common/common.mail.nodemailer";
dotenv.config();
@@ -27,9 +28,11 @@ export const createUser = async (email: string, password: string, fullName: stri
const sessionKey = Guid.create().toString();
const sessionKeyHash = bcrypt.hashSync(sessionKey, 10);
const activationToken = Guid.create().toString();
// 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]);
const userQuery = 'INSERT INTO users (email, password_hash, full_name, activation_token) VALUES (?, ?, ?, ?) RETURNING user_id';
const userIdRes = await conn.query(userQuery, [email, pwHash, fullName, activationToken]);
// Get user id of the created user
let userId: number = -1;
@@ -37,6 +40,9 @@ export const createUser = async (email: string, password: string, fullName: stri
userId = row.user_id;
}
// Send email with activation link
await MailService.sendMail(email, 'Activate your Nachklang account', `Hi ${fullName},\n\nPlease click on the following link to activate your account:\n\nhttps://api.nachklang.art/calendar/users/activate?id=${userId}&token=${activationToken}`);
// 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]);
@@ -63,6 +69,29 @@ export const createUser = async (email: string, password: string, fullName: stri
}
};
export const activateUser = async (userId: number, token: string): Promise<boolean> => {
let conn = await NachklangCalendarDB.getConnection();
try {
const checkTokenQuery = 'SELECT user_id, activation_token FROM users WHERE user_id = ? AND is_active = 0';
const userNameRes = await conn.query(checkTokenQuery, [userId]);
let storedToken = '';
for (const row of userNameRes) {
storedToken = row.activation_token;
}
if (storedToken!== token) {
return false;
}
const activateQuery = 'UPDATE users SET is_active = 1, activation_token = null WHERE user_id =?';
const activateRes = await conn.execute(activateQuery, [userId]);
return activateRes.affectedRows !== 0;
} 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
@@ -180,3 +209,76 @@ export const checkSession = async (sessionId: string, sessionKey: string, ip: st
await conn.end();
}
};
export const initiatePasswordReset = async (email: string): Promise<boolean> => {
let conn = await NachklangCalendarDB.getConnection();
try {
const checkUsernameQuery = 'SELECT user_id, full_Name FROM users WHERE email = ?';
const userNameRes = await conn.query(checkUsernameQuery, [email]);
if (userNameRes.length === 0) {
return false;
}
let userId: number = -1;
let fullName: string = '';
for(let row of userNameRes) {
userId = row.user_id;
fullName = row.full_Name;
}
let resetToken = Guid.create().toString();
let resetTokenHash = bcrypt.hashSync(resetToken, 10);
const updateQuery = 'UPDATE users SET pw_reset_token_hash = ? WHERE user_id = ?';
const updateRes = await conn.execute(updateQuery, [resetTokenHash, userId]);
if(updateRes.affectedRows === 0) {
return false;
}
await conn.commit();
await MailService.sendMail(email, 'Password Reset', `Hello ${fullName},\n\nYou requested a password reset for your BonkApp account. If you did not request this, please ignore this email.\n\nTo reset your password, please use the following reset token:\n\n${resetToken}`);
return true;
} catch (err) {
throw err;
} finally {
// Return connection
await conn.end();
}
}
export const finalizePasswordReset = async (email: string, token: string, newPassword: string): Promise<boolean> => {
let conn = await NachklangCalendarDB.getConnection();
try {
const checkTokenQuery = 'SELECT user_id, pw_reset_token_hash FROM users WHERE email = ?';
const userNameRes = await conn.query(checkTokenQuery, [email]);
if (userNameRes.length === 0) {
return false;
}
let userId: string = '';
let tokenHash: string = '';
for(let row of userNameRes) {
userId = row.user_id;
tokenHash = row.pw_reset_token_hash;
}
if(!bcrypt.compareSync(token, tokenHash)) {
return false;
}
const pwHash = bcrypt.hashSync(newPassword, 10);
const updatePasswordQuery = 'UPDATE users SET password_hash = ?, pw_reset_token_hash = NULL WHERE user_id = ?';
const updateRes = await conn.execute(updatePasswordQuery, [pwHash, userId]);
if(updateRes.affectedRows > 0) {
await conn.commit();
return true;
}
return false;
} catch (err) {
throw err;
} finally {
// Return connection
await conn.end();
}
}