1st
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
# package directories
|
||||||
|
node_modules
|
||||||
|
jspm_packages
|
||||||
|
|
||||||
|
# Nike stuff
|
||||||
|
package-lock.json
|
||||||
|
/node_modules
|
||||||
|
*.log
|
||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
function corsResolver(req, res, next) {
|
||||||
|
|
||||||
|
if(1==0) { // allow browser / postman / world
|
||||||
|
// Allow only from Mike & Nike devs
|
||||||
|
//if(['https://steinni.dev.eismea.eu','https://fallimi.dev.eismea.eu'].indexOf(req.headers.origin)<0) {
|
||||||
|
console.log('Bad origin for CORS : ',req.headers.origin)
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Website you wish to allow to connect
|
||||||
|
// running front-end application on port 3000
|
||||||
|
res.setHeader('Access-Control-Allow-Origin', req.headers.origin ? req.headers.origin : '' );
|
||||||
|
// Request methods you wish to allow
|
||||||
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
|
||||||
|
// Request headers you wish to allow
|
||||||
|
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type,Authorization');
|
||||||
|
// Set to true if you need the website to include cookies in the requests sent
|
||||||
|
// to the API (e.g. in case you use sessions)
|
||||||
|
res.setHeader('Access-Control-Allow-Credentials', true);
|
||||||
|
// Pass to next layer of middleware
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = corsResolver;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import argon2 from 'argon2'
|
||||||
|
|
||||||
|
// --- Hash a password (e.g. at signup) ---
|
||||||
|
export async function hashPassword(plainPassword) {
|
||||||
|
try {
|
||||||
|
const hash = await argon2.hash(plainPassword, {
|
||||||
|
type: argon2.argon2id, // recommended variant
|
||||||
|
memoryCost: 2 ** 16, // ~64 MB
|
||||||
|
timeCost: 3, // iterations
|
||||||
|
parallelism: 1 // threads
|
||||||
|
})
|
||||||
|
return hash // store this string in MySQL
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error hashing password:', err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Verify a password (e.g. at login) ---
|
||||||
|
export async function verifyPassword(plainPassword, storedHash) {
|
||||||
|
try {
|
||||||
|
const match = await argon2.verify(storedHash, plainPassword)
|
||||||
|
return match // true or false
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error verifying password:', err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
const mysql = require('mysql2/promise');
|
||||||
|
|
||||||
|
class P42ApiEndpoints{
|
||||||
|
constructor(app) {
|
||||||
|
this.db = null
|
||||||
|
this.app = app
|
||||||
|
this.userinfos = null
|
||||||
|
this.registerPaths()
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
err(req, res, msg, debug, status=500) {
|
||||||
|
if(!debug) debug = msg
|
||||||
|
let jsonResp = {'success':false,
|
||||||
|
'payload': null,
|
||||||
|
'error': {
|
||||||
|
'displayMessage' : msg,
|
||||||
|
'debugMessage' : debug
|
||||||
|
}
|
||||||
|
};
|
||||||
|
res.set('Content-Type', 'application/json');
|
||||||
|
res.status(status)
|
||||||
|
res.send(JSON.stringify(jsonResp));
|
||||||
|
}
|
||||||
|
|
||||||
|
ok(req, res, payload) {
|
||||||
|
let jsonResp = { "success": true,
|
||||||
|
"payload": payload,
|
||||||
|
};
|
||||||
|
res.set('Content-Type', 'application/json');
|
||||||
|
res.send(JSON.stringify(jsonResp));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async makeSession(req, res) {
|
||||||
|
req.session.userinfo = req.body
|
||||||
|
console.log('REQ body:', req.body)
|
||||||
|
this.ok(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)) {
|
||||||
|
this.err(req, res, 'Not authenticated !')
|
||||||
|
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))
|
||||||
|
else if(Array.isArray(roles)) {
|
||||||
|
for(let role of roles) {
|
||||||
|
if(this.userinfos.userRoles.includes(role)) return(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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'})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
module.exports = P42ApiEndpoints;
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#!/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);
|
||||||
|
|
||||||
|
const corsResolver = require('./corsMiddleware')
|
||||||
|
const P42ApiEndpoints = require('./p42ApiEndpoints')
|
||||||
|
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionStore = new MySQLStore({ ...mysqlCreds,
|
||||||
|
createDatabaseTable: false,
|
||||||
|
clearExpired: true,
|
||||||
|
schema: {
|
||||||
|
tableName: 'p42_sessions',
|
||||||
|
columnNames: {
|
||||||
|
session_id: 'session_id',
|
||||||
|
expires: 'expires',
|
||||||
|
data: 'data'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
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',
|
||||||
|
store: sessionStore,
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: false,
|
||||||
|
cookie: {
|
||||||
|
maxAge: 1000 * 60 * 60 * 4,
|
||||||
|
secure: true, //See trust proxy above
|
||||||
|
sameSite: 'lax',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
|
let eps = new P42ApiEndpoints(app)
|
||||||
|
eps.connectDB(mysqlCreds)
|
||||||
|
|
||||||
|
const server = http.createServer(app)
|
||||||
|
.listen(p42apiConfig.listenPort, p42apiConfig.listenHost, function (req, res) {
|
||||||
|
console.log("p42api now listening on %j:%j ", p42apiConfig.listenHost, p42apiConfig.listenPort);
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
cd /opt/p42api/
|
||||||
|
|
||||||
|
pid=`ps -ef | grep p42api |grep -v grep | awk '{print $2}'`
|
||||||
|
if [ -z "$pid" ]
|
||||||
|
then
|
||||||
|
node p42api.js > p42api.log 2>&1 &
|
||||||
|
else
|
||||||
|
echo ''
|
||||||
|
echo 'Already running PID='"$pid"' (use stopApi.sh to stop it)'
|
||||||
|
echo ''
|
||||||
|
fi
|
||||||
Executable
+6
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
pid=`ps -ef | grep p42api |grep -v grep | awk '{print $2}'`
|
||||||
|
kill -9 $pid
|
||||||
|
|
||||||
|
|
||||||
+6712
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user