diff --git a/helpers/mysqlClient.js b/helpers/mysqlClient.js new file mode 100644 index 0000000..d021755 --- /dev/null +++ b/helpers/mysqlClient.js @@ -0,0 +1,70 @@ +import mysql from 'mysql2/promise' + +export class MySQLClient { + constructor(db, kal = 0) { + this.db = db + this.lastInsertedId = -1 + this.kalIntervalId = null + if(kal > 0){ + this.kalIntervalId = setInterval(() => { + this.db.query('SELECT 1') + }, 1000 * kal); + } + } + + async close(){ + if(this.db) await this.db.end() + if(this.kalIntervalId) clearInterval(this.kalIntervalId) + this.db = null + } + + escape(x) { return(mysql.escape(x)) } + + async execute(query, values = []){ + if(!this.db) return([]) + if(query.search(RegExp('(^|;) *(INSERT|REPLACE) +INTO','i'))>-1) this.lastInsertedId = -1 + const [result] = await this.db.execute(query, values) + + if('insertId' in result) { + this.lastInsertedId = result.insertId + } + return(result) + } + + /* Makes a multi-line VALUES insert, with values for an array of objects { columns:value } + */ + async InsertObjectsStatement(table, rows){ + this.lastInsertedId = -1 + if((!this.db) || (rows.length==0)) return([]) + const keys = Object.keys(rows[0]).map((key) => `\`${key}\``).join(', ') + let valuesArr = [] + for(let row of rows){ + valuesArr.push(Object.values(row)) + } + const query = `INSERT INTO ${table} (${keys}) VALUES ?` + const [result] = await this.db.query(query, [valuesArr]) //Note the wierd array in array of arrays, also, query, not execute + if('insertId' in result) { + this.lastInsertedId = result.insertId + } + return(result) + } + + /* Makes a multi-line VALUES insert, with values for an array of objects { columns:value } + */ + async ReplaceObjectsStatement(table, rows){ + this.lastInsertedId = -1 + if((!this.db) || (rows.length==0)) return([]) + const keys = Object.keys(rows[0]).map((key) => `\`${key}\``).join(', ') + let valuesArr = [] + for(let row of rows){ + valuesArr.push(Object.values(row)) + } + const query = `REPLACE INTO ${table} (${keys}) VALUES ?` + const [result] = await this.db.query(query, [valuesArr]) //Note the wierd array in array of arrays, also, query, not execute + if('insertId' in result) { + this.lastInsertedId = result.insertId + } + return(result) + } + +} diff --git a/helpers/pwd.js b/helpers/pwd.js index d524ba6..0c4ddfd 100644 --- a/helpers/pwd.js +++ b/helpers/pwd.js @@ -1,4 +1,4 @@ -const argon2 = require('argon2') +import argon2 from 'argon2' // --- Hash a password (e.g. at signup) --- export async function hashPassword(plainPassword) { diff --git a/p42ApiEndpoints.js b/p42ApiEndpoints.js index cf8f4bc..09232d3 100644 --- a/p42ApiEndpoints.js +++ b/p42ApiEndpoints.js @@ -1,20 +1,20 @@ import { Utils } from './helpers/utils.js' +import { verifyPassword } from './helpers/pwd.js' +import { MySQLClient } from './helpers/mysqlClient.js' export class P42ApiEndpoints{ constructor(app, db) { - this.db = db + this.db = new MySQLClient(db, 60) this.app = app this.userinfos = null this.utils = new Utils() this.registerPaths() - setInterval(() => { - this.db.query('SELECT 1'); - }, 5000); } registerPaths(){ this.app.get('/hw', this.hw.bind(this)) this.app.get('/checkauth', this.checkauth.bind(this)) this.app.post('/login', this.login.bind(this)) + this.app.get('/logout', this.logout.bind(this)) } @@ -47,17 +47,6 @@ export class P42ApiEndpoints{ this.ok(req, res, {}) } - getSession(req, res) { - if((!req.session.userinfo) || (!req.session.userinfo.isAuthenticated)) { - this.userinfos = null - return(false) - } else { - req.session.touch() - this.userinfos = req.session.userinfo - return(true) - } - } - hasRole(roles) { if(!this.userinfos.userRoles) return(false) if(typeof(roles) == 'string') return(this.userinfos.userRoles.includes(roles)) @@ -74,51 +63,121 @@ export class P42ApiEndpoints{ this.ok(req, res, {hello:'world'}) } - async checkauth(req, res) - if(req.session.userInfos && req.session.userInfos.authenticated && req.session.userInfos.username) { + async checkauth(req, res) { + if(req.session.userInfos && req.session.authenticated && req.session.userInfos.username) { this.ok(req, res, { authenticated: true, userInfos: this.userInfos, + trials: 3, + locked: false, }) } else { + let trials = 3 + let locked = false + if(req.session.userInfos && req.session.userInfos.username) { + const results = await this.db.execute('SELECT usr_trials, usr_locked FROM users WHERE usr_name = ?', [req.session.userInfos.username]) + if(results.length==1){ + trials = results[0].usr_trials + locked = results[0].usr_locked + } + } this.ok(req, res, { authenticated: false, userInfos: null, + trials: trials, + locked: locked, }) } } + async setUserLock(username, locked, trials){ + await this.db.execute('UPDATE users SET usr_locked=?, usr_trials=? WHERE usr_name = ?', [locked, trials, username]) + } + async login(req, res) { let [isValid, payload, errors] = this.utils.validateMapObject(req.body, { - username: ((val, obj) => (typeof(val)=='string') && (val.length>3) ), - passwd: ((val, obj) => (typeof(val)=='string') && (val.length>7) ), + username: ((val, obj) => (typeof(val)=='string') && (val.length>0) && (/^\w+$/.test(val))), + passwd: ((val, obj) => (typeof(val)=='string') && (val.length>0) ), },{ 'username': 'username', 'passwd': 'passwd', }) if((!isValid)){ - this.err(req, res, `Invalid request', 'Invalid login payload:: ${errors}`, 401) + this.err(req, res, `Invalid request`, `Invalid login payload:: ${errors}`, 401) return } - if((payload.username=='toto') && (payload.passwd=='azertyuiop')){ - req.session.userInfos = { - authenticated: true, - username: payload.username, - roles: ['admin'] + const results = await this.db.execute('SELECT * FROM users WHERE usr_name = ?', [payload.username]) + let pwdCheck = false + let userLocked = false + let trials = 3 + if(results.length==1){ + userLocked = results[0].usr_locked + trials = results[0].usr_trials + if(userLocked) { + this.ok(req, res, { + authenticated: false, + userInfos: null, + trials: 0, + locked: true, + }) + return } + } + + pwdCheck = await verifyPassword(payload.passwd, results[0].usr_pwd) + if(pwdCheck){ + req.session.userInfos = { + username: payload.username, + roles: ['admin'], + } + req.session.authenticated = true + + await this.setUserLock(payload.username, false, 3) this.ok(req, res, { authenticated: true, userInfos: req.session.userInfos, + trials: 3, + locked: false, }) } else { + let newtrials = (trials>0) ? trials-1 : 0 + if(newtrials == 0){ + await this.setUserLock(payload.username, true, 0) + this.ok(req, res, { + authenticated: false, + userInfos: null, + trials: 0, + locked: true, + }) + return + } else { + await this.setUserLock(payload.username, false, newtrials) + } + + req.session.authenticated = false + req.session.userInfos = null this.ok(req, res, { authenticated: false, userInfos: null, + trials: newtrials, + locked: false, }) } } + async logout(req, res) { + if(req.session.userInfos && req.session.authenticated) { + req.session.authenticated = false + this.ok(req, res, { + authenticated: false, + userInfos: null, + trials: 3, + locked: false, + }) + } + + } }