switched to imports, debugged sessions
This commit is contained in:
+1
-2
@@ -1,4 +1,4 @@
|
||||
function corsResolver(req, res, next) {
|
||||
export function corsResolver(req, res, next) {
|
||||
|
||||
if(1==0) { // allow browser / postman / world
|
||||
// Allow only from Mike & Nike devs
|
||||
@@ -22,4 +22,3 @@ function corsResolver(req, res, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = corsResolver;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import argon2 from 'argon2'
|
||||
const argon2 = require('argon2')
|
||||
|
||||
// --- Hash a password (e.g. at signup) ---
|
||||
export async function hashPassword(plainPassword) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+58
-145
@@ -1,29 +1,21 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
|
||||
class P42ApiEndpoints{
|
||||
constructor(app) {
|
||||
this.db = null
|
||||
import { Utils } from './helpers/utils.js'
|
||||
export class P42ApiEndpoints{
|
||||
constructor(app, db) {
|
||||
this.db = db
|
||||
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))
|
||||
}
|
||||
|
||||
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);
|
||||
this.app.get('/checkauth', this.checkauth.bind(this))
|
||||
this.app.post('/login', this.login.bind(this))
|
||||
|
||||
}
|
||||
|
||||
err(req, res, msg, debug, status=500) {
|
||||
@@ -56,55 +48,7 @@ class P42ApiEndpoints{
|
||||
}
|
||||
|
||||
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)) {
|
||||
this.err(req, res, 'Not authenticated !')
|
||||
this.userinfos = null
|
||||
return(false)
|
||||
} else {
|
||||
@@ -125,87 +69,56 @@ class P42ApiEndpoints{
|
||||
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.../////////////////////////////
|
||||
async hw(req, res) {
|
||||
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;
|
||||
@@ -1,44 +1,59 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict'
|
||||
const p42apiConfig = require("./p42api.json");
|
||||
const http = require('http');
|
||||
const express = require("express");
|
||||
const bodyParser = require('body-parser');
|
||||
const session = require('express-session')
|
||||
const MySQLStore = require('express-mysql-session')(session);
|
||||
import p42apiConfig from './p42api.json' with { type: 'json' }
|
||||
import mysql from 'mysql2/promise'
|
||||
import http from 'http'
|
||||
import express from 'express'
|
||||
import session from 'express-session'
|
||||
import connectMySQL from 'express-mysql-session'
|
||||
import { corsResolver } from './corsMiddleware.js'
|
||||
import { P42ApiEndpoints } from './p42ApiEndpoints.js'
|
||||
|
||||
const corsResolver = require('./corsMiddleware')
|
||||
const P42ApiEndpoints = require('./p42ApiEndpoints')
|
||||
const MySQLStore = connectMySQL(session)
|
||||
|
||||
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 = {
|
||||
// host: '127.0.0.1',
|
||||
// port: 3306,
|
||||
socketPath: '/var/run/mysqld/mysqld.sock',
|
||||
user: 'p42',
|
||||
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,
|
||||
clearExpired: true,
|
||||
schema: {
|
||||
tableName: 'p42_sessions',
|
||||
columnNames: {
|
||||
session_id: 'session_id',
|
||||
expires: 'expires',
|
||||
data: 'data'
|
||||
}
|
||||
}
|
||||
});
|
||||
tableName: 'p42_sessions',
|
||||
columnNames: {
|
||||
session_id: 'session_id',
|
||||
expires: 'expires',
|
||||
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({
|
||||
name: 'p42.api.sid',
|
||||
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)
|
||||
.listen(p42apiConfig.listenPort, p42apiConfig.listenHost, function (req, res) {
|
||||
|
||||
+11
-10
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"argon2": "^0.44.0",
|
||||
"body-parser": "^2.2.0",
|
||||
"express": "^5.1.0",
|
||||
"express-mysql-session": "^3.0.3",
|
||||
"express-session": "^1.18.2",
|
||||
"mysql": "^2.18.1",
|
||||
"mysql2": "^3.14.3",
|
||||
"swagger-ui-express": "^5.0.1"
|
||||
}
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"argon2": "^0.44.0",
|
||||
"body-parser": "^2.2.0",
|
||||
"express": "^5.1.0",
|
||||
"express-mysql-session": "^3.0.3",
|
||||
"express-session": "^1.18.2",
|
||||
"mysql": "^2.18.1",
|
||||
"mysql2": "^3.14.3",
|
||||
"swagger-ui-express": "^5.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user