switched to imports, debugged sessions

This commit is contained in:
STEINNI
2025-09-03 20:49:15 +00:00
parent f90a1f5065
commit 4cb56c8cbd
6 changed files with 225 additions and 184 deletions
+1 -2
View File
@@ -1,4 +1,4 @@
function corsResolver(req, res, next) { export function corsResolver(req, res, next) {
if(1==0) { // allow browser / postman / world if(1==0) { // allow browser / postman / world
// Allow only from Mike & Nike devs // Allow only from Mike & Nike devs
@@ -22,4 +22,3 @@ function corsResolver(req, res, next) {
next(); next();
} }
module.exports = corsResolver;
+1 -1
View File
@@ -1,4 +1,4 @@
import argon2 from 'argon2' const argon2 = require('argon2')
// --- Hash a password (e.g. at signup) --- // --- Hash a password (e.g. at signup) ---
export async function hashPassword(plainPassword) { export async function hashPassword(plainPassword) {
+113
View File
@@ -0,0 +1,113 @@
export class Utils {
constructor(options){
}
isValidIsoTimestamp(val) {
const isoTimestampRegex = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]|60)(\.\d+)?(Z|([+-])(0[0-9]|1[0-3]):([0-5][0-9])|(14):00)?$/
return(isoTimestampRegex.test(val))
}
isValidMail(val) {
const mailRFC5322Regex = /^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*$/
return(mailRFC5322Regex.test(val))
}
isValidUUID(val) {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
return(uuidRegex.test(val))
}
isIterable(obj) {
if (obj == null) {
return false;
}
return typeof obj[Symbol.iterator] === "function";
}
FilterMapArray(rows, condition) {
if(!rows || (typeof(rows[Symbol.iterator]) != 'function')) return([])
let filteredRows = []
for(let row of rows) {
if(condition(row)) filteredRows.push(row)
}
return(filteredRows)
}
CheckMapArray(rows, remap, transformers) {
if(!rows || (typeof(rows[Symbol.iterator]) != 'function')) return([])
let filteredRows = []
for(let row of rows) {
filteredRows.push(this.CheckMapObject(row, remap, transformers))
}
return(filteredRows)
}
CheckMapObject(row, remap, transformers) {
let filteredRow = {}
let tempTransformers = { ...transformers }
for(let key of Object.keys(row)){
if(Object.keys(remap).indexOf(key)>-1) {
if(tempTransformers && tempTransformers[key] && (typeof(tempTransformers[key])=='function')) {
this.remap(filteredRow, remap[key], tempTransformers[key](row))
delete(tempTransformers[remap[key]])
} else this.remap(filteredRow, remap[key], row[key]) //filteredRow[remap[key]] = row[key]
}
}
// now use remaining transformers for other virtual, non-original keys
for(let virtkey of Object.keys(tempTransformers)){
filteredRow[virtkey] = tempTransformers[virtkey](row)
}
return(filteredRow)
}
validateMapObject(row, validators, remap){
let isok = true
let badkeys = []
for(let key of Object.keys(validators)){
if(typeof(validators[key])=='function') {
let result = validators[key](row[key], row)
if(result!==true) {
isok = false
badkeys.push(key + ((typeof(result)=='string') ? ' - '+result : '') )
}
}
}
let remappedRow = {}
if(!isok) return([false, {}, badkeys])
for(let key of Object.keys(row)){
if(Object.keys(remap).indexOf(key)>-1){
this.remap(remappedRow, remap[key], row[key])
}
}
return([true, remappedRow, badkeys])
}
validateMapArray(rows, validators, remap){
if(!rows || (typeof(rows[Symbol.iterator]) != 'function')) return([false, [], ['First parameter of validateMapArray is not an array']])
let remappedRows = []
let allErrors = []
let isOk = true
for(let row of rows){
let isValid,remappedRow, errors
[isValid, remappedRow, errors] = this.validateMapObject(row, validators, remap)
isOk = isOk && isValid
remappedRows.push(remappedRow)
allErrors = allErrors.concat(errors)
}
return([isOk, remappedRows, allErrors])
}
remap(obj, newKey, value){
let ref = obj
// create sub-struct as needed
newKey.split('.').slice(0,-1).forEach( (keyPart) => {
if(!ref.hasOwnProperty(keyPart)) ref[keyPart]={}
ref = ref[keyPart]
})
ref[newKey.split('.').slice(-1)[0]] = value
}
}
+57 -144
View File
@@ -1,29 +1,21 @@
const mysql = require('mysql2/promise'); import { Utils } from './helpers/utils.js'
export class P42ApiEndpoints{
class P42ApiEndpoints{ constructor(app, db) {
constructor(app) { this.db = db
this.db = null
this.app = app this.app = app
this.userinfos = null this.userinfos = null
this.utils = new Utils()
this.registerPaths() this.registerPaths()
setInterval(() => {
this.db.query('SELECT 1');
}, 5000);
} }
registerPaths(){ registerPaths(){
this.app.get('/hw', this.hw.bind(this)) this.app.get('/hw', this.hw.bind(this))
} this.app.get('/checkauth', this.checkauth.bind(this))
this.app.post('/login', this.login.bind(this))
async connectDB(mysqlCreds) {
this.db = await mysql.createConnection({
host: mysqlCreds.host,
port: mysqlCreds.port,
socketPath: mysqlCreds.socketPath,
database: mysqlCreds.database,
user: mysqlCreds.user,
password: mysqlCreds.password
});
setInterval(() => {
this.db.query('SELECT 1');
}, 5000);
} }
err(req, res, msg, debug, status=500) { err(req, res, msg, debug, status=500) {
@@ -56,55 +48,7 @@ class P42ApiEndpoints{
} }
getSession(req, res) { getSession(req, res) {
this.userinfos = {
"at_hash": "fhaNqJbWprmseino7D7vQhdEIWzlss6a08DvgY_Y7ik",
"sub": "steinic",
"amr": [
"pwd"
],
"iss": "https://ecas.acceptance.ec.europa.eu/cas/oauth2",
// Impersonate here
"preferred_username": "fallimi", //"steinic",
"locale": "en",
"https://ecas.ec.europa.eu/claims/domain": "eu.europa.ec",
"acr": "https://ecas.ec.europa.eu/loa/basic",
"auth_time": 1686415198,
"nickname": "steinic",
"https://ecas.ec.europa.eu/claims/teleworking_priority": false,
"exp": 1686415501,
"iat": 1686415201,
"email": "Nicolas.STEIN@ext.ec.europa.eu",
"https://ecas.ec.europa.eu/claims/employee_number": "90218167",
"email_verified": true,
"https://ecas.ec.europa.eu/claims/department_number": "EISMEA.C.02.2",
"https://ecas.ec.europa.eu/claims/employee_type": "x",
"given_name": "Nicolas",
"https://ecas.ec.europa.eu/claims/org_id": "232619",
"aud": "zjDAOobFg2JJzMxhzfoTyPg1BrOzPzG4EMUJOoqUbF1mYTkwddaZwL4o9YzzK3unIZAEunze7fQAfOoOgXnq9Xhr-NaAc23CqASenqizgfAeUl6",
"c_hash": "8pzkBbmGEZW48yLZYoEoR_H3QC0GIeWYxlzUCfRMElg",
"https://ecas.ec.europa.eu/claims/sso": false,
"https://ecas.ec.europa.eu/claims/authentication_factors": [
{
"username": "steinic"
}
],
"name": "Nicolas STEIN",
"https://ecas.ec.europa.eu/claims/uid": "steinic",
"family_name": "STEIN",
"userRoles": [
"BP_PO",
"APPLICANT",
]
}
return(true)
if((!req.session.userinfo) || (!req.session.userinfo.isAuthenticated)) { if((!req.session.userinfo) || (!req.session.userinfo.isAuthenticated)) {
this.err(req, res, 'Not authenticated !')
this.userinfos = null this.userinfos = null
return(false) return(false)
} else { } else {
@@ -125,87 +69,56 @@ class P42ApiEndpoints{
return(false) return(false)
} }
CheckMapOutput(data, remap, transformers) {
if(!data) return(null)
let rows = Array.isArray(data) ? data : [data]
let filteredRows = []
for(let row of rows) {
let filteredRow = {}
Object.keys(row).forEach((key, index) => {
if(Object.keys(remap).indexOf(key)>-1) {
if(transformers && transformers[key] && (typeof(transformers[key])=='function')) {
filteredRow[remap[key]] = transformers[key](row[key])
} else filteredRow[remap[key]] = row[key]
}
});
filteredRows.push(filteredRow)
}
if(Array.isArray(data)) return(filteredRows)
else return(filteredRows[0])
}
CheckMapInput(dataIn, remap, checks) {
let dataOut = {}
for(let field in checks) {
let dbName = checks[field](dataIn[field])
if(dbName && (dataIn[field]!=null)) dataOut[remap[field]] = dataIn[field]
}
return(dataOut)
}
async isMemberOf(pic) {
let [rows, fields] = await this.db.query(`
SELECT count(*) as cnt FROM organisation_members
WHERE (om_pic=?)
AND (om_uid=?)
`,
[pic, this.userinfos.preferred_username]);
return(rows[0]['cnt']>0)
}
async isOrgAdminOf(pic) {
let [rows, fields] = await this.db.query(`
SELECT count(*) as cnt FROM organisation_members
WHERE (om_pic=?)
AND (om_uid=?)
AND om_administrator=1
`,
[pic, this.userinfos.preferred_username]);
return(rows[0]['cnt']>0)
}
async isPropAdminOf(pid) {
let [rows, fields] = await this.db.query(`
SELECT count(*) as cnt FROM shortprops_members
WHERE (spm_prop_id=?)
AND (spm_uid=?)
AND spm_administrator=1
`,
[pid, this.userinfos.preferred_username]);
return(rows[0]['cnt']>0)
}
async isPropMemberOf(pid) {
let [rows, fields] = await this.db.query(`
SELECT count(*) as cnt FROM shortprops_members
WHERE (spm_prop_id=?)
AND (spm_uid=?)
`,
[pid, this.userinfos.preferred_username]);
return(rows[0]['cnt']>0)
}
async merge(table, where, whereVals, data) {
let [rows, field] = await this.db.query(`SELECT * FROM ${table} WHERE ${where}`, whereVals)
if(rows.length==0) return(data)
else return(Object.assign(rows[0], data))
}
///////////////////////////API starts here...///////////////////////////// ///////////////////////////API starts here.../////////////////////////////
async hw(req, res) { async hw(req, res) {
this.ok(req, res, {hello:'world'}) this.ok(req, res, {hello:'world'})
} }
async checkauth(req, res) {
if(this.getSession(req, res)) {
this.ok(req, res, {
authenticated: true,
userInfos: this.userinfos,
})
} else {
this.ok(req, res, {
authenticated: false,
userInfos: null,
})
}
}
async login(req, res) {
console.log('====>req.json', req.body)
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': 'username',
'passwd': 'passwd',
})
if((!isValid)){
this.err(req, res, `Invalid request', 'Invalid login payload:: ${errors}`, 401)
return
}
if((payload.username=='toto') && (payload.passwd=='azertyuiop')){
req.session.userinfo = {
username: payload.username,
roles: ['admin']
}
this.ok(req, res, {
authenticated: true,
userInfos: req.session.userinfo,
})
} else {
this.ok(req, res, {
authenticated: false,
userInfos: null,
})
}
}
} }
module.exports = P42ApiEndpoints;
+34 -19
View File
@@ -1,25 +1,46 @@
#!/usr/bin/env node #!/usr/bin/env node
'use strict' 'use strict'
const p42apiConfig = require("./p42api.json"); import p42apiConfig from './p42api.json' with { type: 'json' }
const http = require('http'); import mysql from 'mysql2/promise'
const express = require("express"); import http from 'http'
const bodyParser = require('body-parser'); import express from 'express'
const session = require('express-session') import session from 'express-session'
const MySQLStore = require('express-mysql-session')(session); import connectMySQL from 'express-mysql-session'
import { corsResolver } from './corsMiddleware.js'
import { P42ApiEndpoints } from './p42ApiEndpoints.js'
const corsResolver = require('./corsMiddleware') const MySQLStore = connectMySQL(session)
const P42ApiEndpoints = require('./p42ApiEndpoints')
const app = express();
app.set('trust proxy', 1) // trust first proxy (nginx), so we serve http to nginx, but we still behave as if we're in https
app.use(express.json())
app.use(corsResolver)
//TOTO: kick this
const mysqlCreds = { const mysqlCreds = {
// host: '127.0.0.1', // host: '127.0.0.1',
// port: 3306, // port: 3306,
socketPath: '/var/run/mysqld/mysqld.sock', socketPath: '/var/run/mysqld/mysqld.sock',
user: 'p42', user: 'p42',
password: 'C3h=V9!r>Mvc>skxPf9?W2P3duJTk', password: 'C3h=V9!r>Mvc>skxPf9?W2P3duJTk',
database: 'p42' database: 'p42',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
} }
const sessionStore = new MySQLStore({ ...mysqlCreds,
const db = await mysql.createConnection(mysqlCreds)
// {
// host: mysqlCreds.host,
// port: mysqlCreds.port,
// socketPath: mysqlCreds.socketPath,
// database: mysqlCreds.database,
// user: mysqlCreds.user,
// password: mysqlCreds.password
// });
const sessionStore = new MySQLStore({
createDatabaseTable: false, createDatabaseTable: false,
clearExpired: true, clearExpired: true,
schema: { schema: {
@@ -30,15 +51,9 @@ const sessionStore = new MySQLStore({ ...mysqlCreds,
data: 'data' data: 'data'
} }
} }
}); }, db)
const app = express();
app.set('trust proxy', 1) // trust first proxy (nginx), so we serve http to nginx, but we still behave as if we're in https
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json())
app.use(corsResolver);
app.use(session({ app.use(session({
name: 'p42.api.sid', name: 'p42.api.sid',
secret: 'qNhy555Y9vyxj?!3yaYA=aKfgk+Wy5eymNtP*?4i', secret: 'qNhy555Y9vyxj?!3yaYA=aKfgk+Wy5eymNtP*?4i',
@@ -53,8 +68,8 @@ app.use(session({
} }
)) ))
let eps = new P42ApiEndpoints(app)
eps.connectDB(mysqlCreds) let eps = new P42ApiEndpoints(app, db)
const server = http.createServer(app) const server = http.createServer(app)
.listen(p42apiConfig.listenPort, p42apiConfig.listenHost, function (req, res) { .listen(p42apiConfig.listenPort, p42apiConfig.listenHost, function (req, res) {
+1
View File
@@ -1,4 +1,5 @@
{ {
"type": "module",
"dependencies": { "dependencies": {
"argon2": "^0.44.0", "argon2": "^0.44.0",
"body-parser": "^2.2.0", "body-parser": "^2.2.0",