Fix security issues
Jenkins Production Deployment

This commit is contained in:
2022-06-25 13:26:16 +02:00
parent eeace68b7b
commit fc65474930
4 changed files with 336 additions and 328 deletions
+195 -199
View File
@@ -21,51 +21,49 @@ dotenv.config();
* Creates a user record in the database, also creates a session. Returns the session if successful.
*/
export const createUser = async (username: string, password: string, email: string, ip: string): Promise<Session> => {
let conn = BetterzonDB.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);
let conn = BetterzonDB.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 (username, email, bcrypt_password_hash) VALUES (?, ?, ?) RETURNING user_id';
const userIdRes = await conn.query(userQuery, [username, email, pwHash]);
await conn.commit();
// Create user entry in SQL
const userQuery = 'INSERT INTO users (username, email, bcrypt_password_hash) VALUES (?, ?, ?) RETURNING user_id';
const userIdRes = await conn.query(userQuery, [username, email, pwHash]);
await conn.commit();
// Get user id of the created user
let userId: number = -1;
for (const row in userIdRes) {
if (row !== 'meta' && userIdRes[row].user_id != null) {
userId = userIdRes[row].user_id;
}
}
// Get user id of the created user
let userId: number = -1;
for (const row in userIdRes) {
if (row !== 'meta' && userIdRes[row].user_id != null) {
userId = userIdRes[row].user_id;
}
}
// Create session
const sessionQuery = 'INSERT INTO sessions (user_id, session_key_hash, createdDate, lastLogin, validUntil, validDays, last_IP) VALUES (?,?,NOW(),NOW(),DATE_ADD(NOW(), INTERVAL 30 DAY),30,?) RETURNING session_id';
const sessionIdRes = await conn.query(sessionQuery, [userId, sessionKeyHash, ip]);
await conn.commit();
// Create session
const sessionQuery = 'INSERT INTO sessions (user_id, session_key_hash, createdDate, lastLogin, validUntil, validDays, last_IP) VALUES (?,?,NOW(),NOW(),DATE_ADD(NOW(), INTERVAL 30 DAY),30,?) 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 in sessionIdRes) {
if (row !== 'meta' && sessionIdRes[row].session_id != null) {
sessionId = sessionIdRes[row].session_id;
}
}
// Get session id of the created session
let sessionId: number = -1;
for (const row in sessionIdRes) {
if (row !== 'meta' && sessionIdRes[row].session_id != null) {
sessionId = sessionIdRes[row].session_id;
}
}
return {
session_id: sessionId,
session_key: sessionKey,
session_key_hash: 'HIDDEN',
last_IP: ip
};
return {
session_id: sessionId,
session_key: sessionKey,
session_key_hash: 'HIDDEN',
last_IP: ip
};
} catch (err) {
throw err;
}
return {} as Session;
} catch (err) {
throw err;
}
};
/**
@@ -73,136 +71,134 @@ export const createUser = async (username: string, password: string, email: stri
* Returns the session information in case of a successful login
*/
export const login = async (username: string, password: string, ip: string): Promise<Session> => {
let conn = BetterzonDB.getConnection();
try {
// Get saved password hash
const query = 'SELECT user_id, bcrypt_password_hash FROM users WHERE username = ?';
const userRows = await conn.query(query, username);
let savedHash = '';
let userId = -1;
for (const row in userRows) {
if (row !== 'meta' && userRows[row].user_id != null) {
savedHash = userRows[row].bcrypt_password_hash;
userId = userRows[row].user_id;
}
}
let conn = BetterzonDB.getConnection();
try {
// Get saved password hash
const query = 'SELECT user_id, bcrypt_password_hash FROM users WHERE username = ?';
const userRows = await conn.query(query, username);
let savedHash = '';
let userId = -1;
for (const row in userRows) {
if (row !== 'meta' && userRows[row].user_id != null) {
savedHash = userRows[row].bcrypt_password_hash;
userId = userRows[row].user_id;
}
}
// Check for correct password
if (!bcrypt.compareSync(password, savedHash)) {
// Wrong password, return invalid
return {} as Session;
}
// Password is valid, continue
// Check for correct password
if (!bcrypt.compareSync(password, savedHash)) {
// Wrong password, return invalid
return {} as Session;
}
// Password is valid, continue
// Generate + hash session key
const sessionKey = Guid.create().toString();
const sessionKeyHash = bcrypt.hashSync(sessionKey, 10);
// Generate + hash session key
const sessionKey = Guid.create().toString();
const sessionKeyHash = bcrypt.hashSync(sessionKey, 10);
// Update user entry in SQL
const userQuery = 'UPDATE users SET last_login_date = NOW() WHERE user_id = ?';
const userIdRes = await conn.query(userQuery, userId);
await conn.commit();
// Update user entry in SQL
const userQuery = 'UPDATE users SET last_login_date = NOW() WHERE user_id = ?';
const userIdRes = await conn.query(userQuery, userId);
await conn.commit();
// Create session
const sessionQuery = 'INSERT INTO sessions (user_id, session_key_hash, createdDate, lastLogin, validUntil, validDays, last_IP) VALUES (?,?,NOW(),NOW(),DATE_ADD(NOW(), INTERVAL 30 DAY),30,?) RETURNING session_id';
const sessionIdRes = await conn.query(sessionQuery, [userId, sessionKeyHash, ip]);
await conn.commit();
// Create session
const sessionQuery = 'INSERT INTO sessions (user_id, session_key_hash, createdDate, lastLogin, validUntil, validDays, last_IP) VALUES (?,?,NOW(),NOW(),DATE_ADD(NOW(), INTERVAL 30 DAY),30,?) 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 in sessionIdRes) {
if (row !== 'meta' && sessionIdRes[row].session_id != null) {
sessionId = sessionIdRes[row].session_id;
}
}
// Get session id of the created session
let sessionId: number = -1;
for (const row in sessionIdRes) {
if (row !== 'meta' && sessionIdRes[row].session_id != null) {
sessionId = sessionIdRes[row].session_id;
}
}
return {
session_id: sessionId,
session_key: sessionKey,
session_key_hash: 'HIDDEN',
last_IP: ip
};
return {
session_id: sessionId,
session_key: sessionKey,
session_key_hash: 'HIDDEN',
last_IP: ip
};
} catch (err) {
throw err;
}
return {} as Session;
} catch (err) {
throw err;
}
};
/**
* 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 = BetterzonDB.getConnection();
try {
// Get saved session key hash
const query = 'SELECT user_id, session_key_hash, validUntil FROM sessions WHERE session_id = ?';
const sessionRows = await conn.query(query, sessionId);
let savedHash = '';
let userId = -1;
let validUntil = new Date();
for (const row in sessionRows) {
if (row !== 'meta' && sessionRows[row].user_id != null) {
savedHash = sessionRows[row].session_key_hash;
userId = sessionRows[row].user_id;
validUntil = sessionRows[row].validUntil;
}
}
let conn = BetterzonDB.getConnection();
try {
// Get saved session key hash
const query = 'SELECT user_id, session_key_hash, validUntil FROM sessions WHERE session_id = ?';
const sessionRows = await conn.query(query, sessionId);
let savedHash = '';
let userId = -1;
let validUntil = new Date();
for (const row in sessionRows) {
if (row !== 'meta' && sessionRows[row].user_id != null) {
savedHash = sessionRows[row].session_key_hash;
userId = sessionRows[row].user_id;
validUntil = sessionRows[row].validUntil;
}
}
// Check for correct key
if (!bcrypt.compareSync(sessionKey, savedHash)) {
// Wrong key, return invalid
return {} as User;
}
// Key is valid, continue
// Check for correct key
if (!bcrypt.compareSync(sessionKey, savedHash)) {
// Wrong key, return invalid
return {} as User;
}
// Key is valid, continue
// Check if the session is still valid
if (validUntil <= new Date()) {
// Session expired, return invalid
return {} as User;
}
// Session still valid, continue
// Check if the session is still valid
if (validUntil <= new Date()) {
// Session expired, return invalid
return {} as User;
}
// Session still valid, continue
// Update session entry in SQL
const updateSessionsQuery = 'UPDATE sessions SET lastLogin = NOW(), last_IP = ? WHERE session_id = ?';
const updateUsersQuery = 'UPDATE users SET last_login_date = NOW() WHERE user_id = ?';
const userIdRes = await conn.query(updateSessionsQuery, [ip, sessionId]);
await conn.query(updateUsersQuery, userId);
await conn.commit();
// Update session entry in SQL
const updateSessionsQuery = 'UPDATE sessions SET lastLogin = NOW(), last_IP = ? WHERE session_id = ?';
const updateUsersQuery = 'UPDATE users SET last_login_date = NOW() WHERE user_id = ?';
const userIdRes = await conn.query(updateSessionsQuery, [ip, sessionId]);
await conn.query(updateUsersQuery, userId);
await conn.commit();
// Get the other required user information and update the user
const userQuery = 'SELECT user_id, username, email, registration_date, last_login_date, is_admin FROM users WHERE user_id = ?';
const userRows = await conn.query(userQuery, userId);
let username = '';
let email = '';
let registrationDate = new Date();
let lastLoginDate = new Date();
let is_admin = false;
for (const row in userRows) {
if (row !== 'meta' && userRows[row].user_id != null) {
username = userRows[row].username;
email = userRows[row].email;
registrationDate = userRows[row].registration_date;
lastLoginDate = userRows[row].last_login_date;
is_admin = userRows[row].is_admin;
}
}
// Get the other required user information and update the user
const userQuery = 'SELECT user_id, username, email, registration_date, last_login_date, is_admin FROM users WHERE user_id = ?';
const userRows = await conn.query(userQuery, userId);
let username = '';
let email = '';
let registrationDate = new Date();
let lastLoginDate = new Date();
let is_admin = false;
for (const row in userRows) {
if (row !== 'meta' && userRows[row].user_id != null) {
username = userRows[row].username;
email = userRows[row].email;
registrationDate = userRows[row].registration_date;
lastLoginDate = userRows[row].last_login_date;
is_admin = userRows[row].is_admin;
}
}
// Everything is fine, return user information
return {
user_id: userId,
username: username,
email: email,
password_hash: 'HIDDEN',
registration_date: registrationDate,
last_login_date: lastLoginDate,
is_admin: is_admin
};
// Everything is fine, return user information
return {
user_id: userId,
username: username,
email: email,
password_hash: 'HIDDEN',
registration_date: registrationDate,
last_login_date: lastLoginDate,
is_admin: is_admin
};
} catch (err) {
throw err;
}
} catch (err) {
throw err;
}
};
/**
@@ -211,21 +207,21 @@ export const checkSession = async (sessionId: string, sessionKey: string, ip: st
* @param ip The users IP address
*/
export const checkSessionWithCookie = async (cookie: any, ip: string): Promise<User> => {
const parsedCookie = JSON.parse(cookie);
const session_id = parsedCookie.id;
const session_key = parsedCookie.key;
const parsedCookie = JSON.parse(cookie);
const session_id = parsedCookie.id;
const session_key = parsedCookie.key;
return checkSession(session_id, session_key, '');
return checkSession(session_id, session_key, '');
};
/**
* Used in the checkUsernameAndEmail method as return value
*/
export interface Status {
hasProblems: boolean;
messages: string[];
codes: number[]; // 0 = all good, 1 = wrong username, 2 = wrong email, 3 = server error, 4 = wrong password, 5 = wrong session
hasProblems: boolean;
messages: string[];
codes: number[]; // 0 = all good, 1 = wrong username, 2 = wrong email, 3 = server error, 4 = wrong password, 5 = wrong session
}
/**
@@ -234,53 +230,53 @@ export interface Status {
* @param email The email to check
*/
export const checkUsernameAndEmail = async (username: string, email: string): Promise<Status> => {
let conn = BetterzonDB.getConnection();
try {
// Create user entry in SQL
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 conn = BetterzonDB.getConnection();
try {
// Create user entry in SQL
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: [],
codes: []
};
let res: Status = {
hasProblems: false,
messages: [],
codes: []
};
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('Invalid username');
res.codes.push(1);
}
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('Invalid username');
res.codes.push(1);
}
const emailRegex = RegExp('^[a-zA-Z0-9\\-\\_.]{1,30}\\@[a-zA-Z0-9\\-.]{1,20}\\.[a-z]{1,20}$'); // Normal email regex, user@betterzon.xyz
if (!emailRegex.test(email)) {
// Username doesn't match requirements
res.hasProblems = true;
res.messages.push('Invalid email');
res.codes.push(2);
}
const emailRegex = RegExp('^[a-zA-Z0-9\\-\\_.]{1,30}\\@[a-zA-Z0-9\\-.]{1,20}\\.[a-z]{1,20}$'); // Normal email regex, user@betterzon.xyz
if (!emailRegex.test(email)) {
// Username doesn't match requirements
res.hasProblems = true;
res.messages.push('Invalid email');
res.codes.push(2);
}
if (usernameRes.length > 0) {
// Username is a duplicate
res.hasProblems = true;
res.messages.push('Duplicate username');
res.codes.push(1);
}
if (usernameRes.length > 0) {
// Username is a duplicate
res.hasProblems = true;
res.messages.push('Duplicate username');
res.codes.push(1);
}
if (emailRes.length > 0) {
// Email is a duplicate
res.hasProblems = true;
res.messages.push('Duplicate email');
res.codes.push(2);
}
if (emailRes.length > 0) {
// Email is a duplicate
res.hasProblems = true;
res.messages.push('Duplicate email');
res.codes.push(2);
}
return res;
return res;
} catch (err) {
throw err;
}
} catch (err) {
throw err;
}
};
+97 -101
View File
@@ -18,41 +18,41 @@ dotenv.config();
* Fetches and returns all known vendors
*/
export const findAll = async (): Promise<Vendors> => {
let conn = BetterzonDB.getConnection();
let vendorRows = [];
try {
const rows = await conn.query('SELECT vendor_id, name, streetname, zip_code, city, country_code, phone, website FROM vendors WHERE isActive = true');
for (let row in rows) {
if (row !== 'meta') {
let vendor: Vendor = {
city: '',
country_code: '',
name: '',
phone: '',
streetname: '',
vendor_id: 0,
website: '',
zip_code: ''
};
const sqlVendor = rows[row];
let conn = BetterzonDB.getConnection();
let vendorRows = [];
try {
const rows = await conn.query('SELECT vendor_id, name, streetname, zip_code, city, country_code, phone, website FROM vendors WHERE isActive = true');
for (let row in rows) {
if (row !== 'meta') {
let vendor: Vendor = {
city: '',
country_code: '',
name: '',
phone: '',
streetname: '',
vendor_id: 0,
website: '',
zip_code: ''
};
const sqlVendor = rows[row];
vendor.vendor_id = sqlVendor.vendor_id;
vendor.name = sqlVendor.name;
vendor.streetname = sqlVendor.streetname;
vendor.zip_code = sqlVendor.zip_code;
vendor.city = sqlVendor.city;
vendor.country_code = sqlVendor.country_code;
vendor.phone = sqlVendor.phone;
vendor.website = sqlVendor.website;
vendorRows.push(vendor);
}
}
vendor.vendor_id = sqlVendor.vendor_id;
vendor.name = sqlVendor.name;
vendor.streetname = sqlVendor.streetname;
vendor.zip_code = sqlVendor.zip_code;
vendor.city = sqlVendor.city;
vendor.country_code = sqlVendor.country_code;
vendor.phone = sqlVendor.phone;
vendor.website = sqlVendor.website;
vendorRows.push(vendor);
}
}
} catch (err) {
throw err;
}
} catch (err) {
throw err;
}
return vendorRows;
return vendorRows;
};
/**
@@ -60,21 +60,21 @@ export const findAll = async (): Promise<Vendors> => {
* @param id The id of the vendor to fetch
*/
export const find = async (id: number): Promise<Vendor> => {
let conn = BetterzonDB.getConnection();
let vendor: any;
try {
const rows = await conn.query('SELECT vendor_id, name, streetname, zip_code, city, country_code, phone, website FROM vendors WHERE vendor_id = ? AND isActive = true', id);
for (let row in rows) {
if (row !== 'meta') {
vendor = rows[row];
}
}
let conn = BetterzonDB.getConnection();
let vendor: any;
try {
const rows = await conn.query('SELECT vendor_id, name, streetname, zip_code, city, country_code, phone, website FROM vendors WHERE vendor_id = ? AND isActive = true', id);
for (let row in rows) {
if (row !== 'meta') {
vendor = rows[row];
}
}
} catch (err) {
throw err;
}
} catch (err) {
throw err;
}
return vendor;
return vendor;
};
/**
@@ -82,22 +82,22 @@ export const find = async (id: number): Promise<Vendor> => {
* @param term the term to match
*/
export const findBySearchTerm = async (term: string): Promise<Vendors> => {
let conn = BetterzonDB.getConnection();
let vendorRows = [];
try {
term = '%' + term + '%';
const rows = await conn.query('SELECT vendor_id, name, streetname, zip_code, city, country_code, phone, website FROM vendors WHERE name LIKE ? AND isActive = true', term);
for (let row in rows) {
if (row !== 'meta') {
vendorRows.push(rows[row]);
}
}
let conn = BetterzonDB.getConnection();
let vendorRows = [];
try {
term = '%' + term + '%';
const rows = await conn.query('SELECT vendor_id, name, streetname, zip_code, city, country_code, phone, website FROM vendors WHERE name LIKE ? AND isActive = true', term);
for (let row in rows) {
if (row !== 'meta') {
vendorRows.push(rows[row]);
}
}
} catch (err) {
throw err;
}
} catch (err) {
throw err;
}
return vendorRows;
return vendorRows;
};
/**
@@ -105,21 +105,21 @@ export const findBySearchTerm = async (term: string): Promise<Vendors> => {
* @param user The user to return the managed shops for
*/
export const getManagedShops = async (user_id: number): Promise<Vendors> => {
let conn = BetterzonDB.getConnection();
let vendorRows = [];
try {
const rows = await conn.query('SELECT vendor_id, name, streetname, zip_code, city, country_code, phone, website FROM vendors WHERE admin_id LIKE ?', user_id);
for (let row in rows) {
if (row !== 'meta') {
vendorRows.push(rows[row]);
}
}
let conn = BetterzonDB.getConnection();
let vendorRows = [];
try {
const rows = await conn.query('SELECT vendor_id, name, streetname, zip_code, city, country_code, phone, website FROM vendors WHERE admin_id LIKE ?', user_id);
for (let row in rows) {
if (row !== 'meta') {
vendorRows.push(rows[row]);
}
}
} catch (err) {
throw err;
}
} catch (err) {
throw err;
}
return vendorRows;
return vendorRows;
};
/**
@@ -129,22 +129,20 @@ export const getManagedShops = async (user_id: number): Promise<Vendors> => {
* @param product_id The product id of the product to deactivate the listing for
*/
export const deactivateListing = async (user_id: number, vendor_id: number, product_id: number): Promise<Boolean> => {
let conn = BetterzonDB.getConnection();
try {
// Check if the user is authorized to manage the requested vendor
const user_vendor_rows = await conn.query('SELECT vendor_id FROM vendors WHERE vendor_id = ? AND admin_id = ?', [vendor_id, user_id]);
if (user_vendor_rows.length !== 1) {
return false;
}
let conn = BetterzonDB.getConnection();
try {
// Check if the user is authorized to manage the requested vendor
const user_vendor_rows = await conn.query('SELECT vendor_id FROM vendors WHERE vendor_id = ? AND admin_id = ?', [vendor_id, user_id]);
if (user_vendor_rows.length !== 1) {
return false;
}
const status = await conn.query('UPDATE prices SET active_listing = false WHERE vendor_id = ? and product_id = ?', [vendor_id, product_id]);
const status = await conn.query('UPDATE prices SET active_listing = false WHERE vendor_id = ? and product_id = ?', [vendor_id, product_id]);
return status.affectedRows > 0;
} catch (err) {
throw err;
}
return false;
return status.affectedRows > 0;
} catch (err) {
throw err;
}
};
/**
@@ -154,21 +152,19 @@ export const deactivateListing = async (user_id: number, vendor_id: number, prod
* @param isActive The new active state
*/
export const setShopStatus = async (user_id: number, vendor_id: number, isActive: boolean): Promise<Boolean> => {
let conn = BetterzonDB.getConnection();
try {
// Check if the user is authorized to manage the requested vendor
const user_vendor_rows = await conn.query('SELECT vendor_id FROM vendors WHERE vendor_id = ? AND admin_id = ?', [vendor_id, user_id]);
if (user_vendor_rows.length !== 1) {
return false;
}
let conn = BetterzonDB.getConnection();
try {
// Check if the user is authorized to manage the requested vendor
const user_vendor_rows = await conn.query('SELECT vendor_id FROM vendors WHERE vendor_id = ? AND admin_id = ?', [vendor_id, user_id]);
if (user_vendor_rows.length !== 1) {
return false;
}
// Update the vendor state
const status = await conn.query('UPDATE vendors SET isActive = ? WHERE vendor_id = ?', [isActive, vendor_id]);
// Update the vendor state
const status = await conn.query('UPDATE vendors SET isActive = ? WHERE vendor_id = ?', [isActive, vendor_id]);
return status.affectedRows > 0;
} catch (err) {
throw err;
}
return false;
return status.affectedRows > 0;
} catch (err) {
throw err;
}
};
@@ -14,7 +14,6 @@ export const eventRouter = express.Router();
eventRouter.get('/:isDevCall', async (req: Request, res: Response) => {
try {
throw new Error('Test');
let userId = (req.query.userId ?? '').toString();
let sessionId = (req.query.sessionId ?? '').toString();
let sessionKey = (req.query.sessionKey ?? '').toString();