env + perms + tools

This commit is contained in:
Yanis Rigaudeau 2022-10-25 22:03:11 +02:00
parent 3c5b60e1aa
commit 6b815a6a68
Signed by: yanis
GPG Key ID: 4DD2841DF1C94D83
15 changed files with 216 additions and 114 deletions

View File

@ -10,6 +10,7 @@ User=node
Group=node Group=node
Environment="CONFIGFILE=/home/node/config/config.json" Environment="CONFIGFILE=/home/node/config/config.json"
Environment="NODE_ENV=production"
WorkingDirectory=/home/node/app WorkingDirectory=/home/node/app
ExecStart=/usr/bin/node /home/node/app/dist/api/src/app.js ExecStart=/usr/bin/node /home/node/app/dist/api/src/app.js

122
api/package-lock.json generated
View File

@ -8,6 +8,7 @@
"name": "api", "name": "api",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"connect-mongo": "^4.6.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^4.18.1", "express": "^4.18.1",
"express-session": "^1.17.3", "express-session": "^1.17.3",
@ -279,6 +280,17 @@
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
}, },
"node_modules/asn1.js": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
"dependencies": {
"bn.js": "^4.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"safer-buffer": "^2.1.0"
}
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -313,6 +325,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/bn.js": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
},
"node_modules/body-parser": { "node_modules/body-parser": {
"version": "1.20.0", "version": "1.20.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
@ -451,6 +468,42 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true "dev": true
}, },
"node_modules/connect-mongo": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-4.6.0.tgz",
"integrity": "sha512-8new4Z7NLP3CGP65Aw6ls3xDBeKVvHRSh39CXuDZTQsvpeeU9oNMzfFgvqmHqZ6gWpxIl663RyoVEmCAGf1yOg==",
"dependencies": {
"debug": "^4.3.1",
"kruptein": "^3.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"mongodb": "^4.1.0"
}
},
"node_modules/connect-mongo/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/connect-mongo/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/content-disposition": { "node_modules/content-disposition": {
"version": "0.5.4", "version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@ -921,6 +974,17 @@
"node": ">=0.12.0" "node": ">=0.12.0"
} }
}, },
"node_modules/kruptein": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.5.tgz",
"integrity": "sha512-c1pyg/HKep8y5l+AoiicTs94k4bnzBSiS1b8NQcnQDtv9Yh45rNLuDIUwEwawmuFYpcA5xqhG7k0LqiMhrBPXw==",
"dependencies": {
"asn1.js": "^5.4.1"
},
"engines": {
"node": ">8"
}
},
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@ -989,6 +1053,11 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
},
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@ -1950,6 +2019,17 @@
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
}, },
"asn1.js": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
"requires": {
"bn.js": "^4.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"safer-buffer": "^2.1.0"
}
},
"balanced-match": { "balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -1967,6 +2047,11 @@
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true "dev": true
}, },
"bn.js": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
},
"body-parser": { "body-parser": {
"version": "1.20.0", "version": "1.20.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
@ -2064,6 +2149,30 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true "dev": true
}, },
"connect-mongo": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-4.6.0.tgz",
"integrity": "sha512-8new4Z7NLP3CGP65Aw6ls3xDBeKVvHRSh39CXuDZTQsvpeeU9oNMzfFgvqmHqZ6gWpxIl663RyoVEmCAGf1yOg==",
"requires": {
"debug": "^4.3.1",
"kruptein": "^3.0.0"
},
"dependencies": {
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
"ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"content-disposition": { "content-disposition": {
"version": "0.5.4", "version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@ -2415,6 +2524,14 @@
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true "dev": true
}, },
"kruptein": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.5.tgz",
"integrity": "sha512-c1pyg/HKep8y5l+AoiicTs94k4bnzBSiS1b8NQcnQDtv9Yh45rNLuDIUwEwawmuFYpcA5xqhG7k0LqiMhrBPXw==",
"requires": {
"asn1.js": "^5.4.1"
}
},
"lodash": { "lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@ -2465,6 +2582,11 @@
"mime-db": "1.52.0" "mime-db": "1.52.0"
} }
}, },
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
},
"minimatch": { "minimatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",

View File

@ -6,7 +6,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"dev": "CONFIGFILE=config/config.json ts-node-dev --respawn --transpile-only ./src/app.ts", "dev": "CONFIGFILE=config/config.json NODE_ENV=dev ts-node-dev --respawn --transpile-only ./src/app.ts",
"start": "npm run build && CONFIGFILE=config/config.json node ./dist/api/src/app.js", "start": "npm run build && CONFIGFILE=config/config.json node ./dist/api/src/app.js",
"prettier": "prettier -w ./src" "prettier": "prettier -w ./src"
}, },
@ -23,6 +23,7 @@
"typescript": "^4.0.0" "typescript": "^4.0.0"
}, },
"dependencies": { "dependencies": {
"connect-mongo": "^4.6.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^4.18.1", "express": "^4.18.1",
"express-session": "^1.17.3", "express-session": "^1.17.3",

View File

@ -25,7 +25,7 @@ const services: Services = {
userModel: new UserModel(db), userModel: new UserModel(db),
}; };
const server = new Server(config.server, services); const server = new Server(config.server, config.mongo, services);
server.start(() => server.start(() =>
console.log( console.log(

View File

@ -2,7 +2,6 @@ import { ErrorRequestHandler, Request, RequestHandler } from 'express';
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
import { validationResult } from 'express-validator'; import { validationResult } from 'express-validator';
import { UserInfo, UserRoles } from '@core'; import { UserInfo, UserRoles } from '@core';
import permissions from './permissions';
declare module 'express-session' { declare module 'express-session' {
interface SessionData { interface SessionData {
@ -21,62 +20,42 @@ export function RequestId(): RequestHandler {
}; };
} }
export function checkPermissions(): RequestHandler { export function CheckPermissions(): RequestHandler {
const getRoute = (url: string): string => { function getResourceId(req: Request): string | null {
for (const route in permissions) { console.log(req.url);
if (url.startsWith(route)) return route; if (req.params.uuid) return req.params.uuid;
} if (req.body.uuid) return req.body.uuid;
return ''; return null;
}; }
const canAccess = (req: Request): boolean => {
const user = req.session.user;
if (!user) return false;
//Logout
if (req.url === '/user/logout') {
return true;
}
//User Imself
if (req.params.uuid === user.uuid) {
return true;
}
function canAccessRessource(user: UserInfo, uuid: string): boolean {
if (user.uuid === uuid) return true;
return false; return false;
}; }
return (req, res, next) => { return (req, res, next) => {
const route = getRoute(req.url);
console.log(canAccess(req));
console.log(route);
if (!req.session.user && req.url === '/user/login') {
next();
return;
}
if (!req.session.user) { if (!req.session.user) {
next({ status: 401, messsage: 'Unauthorized' }); next({ status: 401, messsage: 'Unauthorized' });
return; return;
} }
if ( if (req.session.user.role === UserRoles.ADMIN) {
!(route in permissions) || next();
(req.session.user.role !== permissions[route] &&
req.session.user.role !== UserRoles.ADMIN) ||
(!canAccess(req) && req.session.user.role !== UserRoles.ADMIN)
) {
next({ status: 401, messsage: 'Unauthorized' });
return; return;
} }
if ( const ressourceId = getResourceId(req);
req.session.user.role === UserRoles.ADMIN || console.log(ressourceId);
(req.session.user.role === permissions[route] && canAccess(req)) if (!ressourceId) {
) { next({ status: 403, messsage: 'Forbidden' });
return;
}
if (canAccessRessource(req.session.user, ressourceId)) {
next(); next();
return; return;
} else {
next({ status: 403, messsage: 'Forbidden' });
return;
} }
next({ status: 401, messsage: 'Unauthorized' }); next({ status: 401, messsage: 'Unauthorized' });

View File

@ -1,10 +0,0 @@
import { UserRoles } from '@core';
const permissions = {
'/user/login': UserRoles.USER,
'/user/logout': UserRoles.USER,
'/user/read': UserRoles.USER,
'/user/create': UserRoles.ADMIN,
};
export default permissions;

View File

@ -2,10 +2,10 @@ import { Router } from 'express';
import { Services } from '../../app'; import { Services } from '../../app';
import * as user from './user'; import * as user from './user';
export function getRoutes(services: Services) { export function Routes(services: Services) {
const router = Router(); const router = Router();
router.use('/user', user.getRoutes(services)); router.use('/user', user.Routes(services));
return router; return router;
} }

View File

@ -1,17 +1,22 @@
import express, { Express } from 'express'; import express, { Express } from 'express';
import cors from 'cors'; import cors from 'cors';
import session from 'express-session'; import session from 'express-session';
import { getRoutes } from './router'; import { Routes } from './router';
import { RequestId, ErrorHandler, checkPermissions } from './middleware'; import { RequestId, ErrorHandler } from './middleware';
import { Services } from '../../app'; import { Services } from '../../app';
import { ServerConfig } from '../../config'; import { MongoConfig, ServerConfig } from '../../config';
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
import MongoStore from 'connect-mongo';
class Server { class Server {
private app: Express; private app: Express;
private config: ServerConfig; private config: ServerConfig;
constructor(config: ServerConfig, services: Services) { constructor(
config: ServerConfig,
mongoConfig: MongoConfig,
services: Services,
) {
this.config = config; this.config = config;
this.app = express(); this.app = express();
@ -26,14 +31,23 @@ class Server {
this.app.use(RequestId()); this.app.use(RequestId());
this.app.use( this.app.use(
session({ session({
proxy: process.env.NODE_ENV === 'production',
secret: randomUUID(), secret: randomUUID(),
cookie: { maxAge: 1000 * 3600 * 24 }, cookie: {
maxAge: 1000 * 3600 * 24,
sameSite: 'strict',
secure: process.env.NODE_ENV === 'production',
},
store: MongoStore.create({
mongoUrl: mongoConfig.uri,
dbName: mongoConfig.dbName,
touchAfter: 3600,
}),
resave: false, resave: false,
saveUninitialized: false, saveUninitialized: false,
}), }),
); );
//this.app.use(checkPermissions()); this.app.use(Routes(services));
this.app.use(getRoutes(services));
this.app.use(ErrorHandler()); this.app.use(ErrorHandler());
} }

View File

@ -7,7 +7,7 @@ import {
ReadUserSchema, ReadUserSchema,
LogoutUserSchema, LogoutUserSchema,
} from './schema/user'; } from './schema/user';
import { getId, SchemaValidator } from './middleware'; import { CheckPermissions, getId, SchemaValidator } from './middleware';
function LoginHandler(services: Services): RequestHandler { function LoginHandler(services: Services): RequestHandler {
const login = LoginUser(services); const login = LoginUser(services);
@ -52,16 +52,9 @@ function ReadHandler(services: Services): RequestHandler {
}; };
} }
export function getRoutes(services: Services) { export function Routes(services: Services) {
const router = Router(); const router = Router();
router.get(
'/read/:uuid',
ReadUserSchema(),
SchemaValidator(),
ReadHandler(services),
);
router.post( router.post(
'/login', '/login',
LoginUserSchema(), LoginUserSchema(),
@ -74,10 +67,19 @@ export function getRoutes(services: Services) {
SchemaValidator(), SchemaValidator(),
LogoutHandler(services), LogoutHandler(services),
); );
router.get(
'/read/:uuid',
ReadUserSchema(),
SchemaValidator(),
CheckPermissions(),
ReadHandler(services),
);
router.post( router.post(
'/create', '/create',
CreateUserSchema(), CreateUserSchema(),
SchemaValidator(), SchemaValidator(),
CheckPermissions(),
CreateHandler(services), CreateHandler(services),
); );

View File

@ -1,3 +0,0 @@
{
"apps": ["core", "api", "www"]
}

View File

@ -1,12 +0,0 @@
#!/usr/bin/python
import os
import json
apps: 'list[str]' = json.load(open('apps.json'))['apps']
print(len(apps), 'apps:', apps)
for app in apps:
os.chdir(app)
os.system('npm run build')
os.chdir('..')

View File

@ -2,8 +2,8 @@
useradd node -md /home/node -s /usr/sbin/nologin useradd node -md /home/node -s /usr/sbin/nologin
python3 scripts/install.py python3 scripts/tools.py install
python3 scripts/build.py python3 scripts/tools.py build
rm -r /var/www/html/* rm -r /var/www/html/*
mv www/public/* /var/www/html mv www/public/* /var/www/html

View File

@ -1,12 +0,0 @@
#!/usr/bin/python
import os
import json
apps: 'list[str]' = json.load(open('apps.json'))['apps']
print(len(apps), 'apps:', apps)
for app in apps:
os.chdir(app)
os.system('npm ci')
os.chdir('..')

View File

@ -1,12 +0,0 @@
#!/usr/bin/python
import os
import json
apps: 'list[str]' = json.load(open('apps.json'))['apps']
print(len(apps), 'apps:', apps)
for app in apps:
os.chdir(app)
os.system('npm run prettier')
os.chdir('..')

32
scripts/tools.py Executable file
View File

@ -0,0 +1,32 @@
#!/usr/bin/python
import os
import sys
import json
apps = ['core', 'api', 'www']
commands = {
'build': 'npm run build',
'install': 'npm ci',
'prettier': 'npm run prettier'
}
def print_commands():
print('Commandes disponibles: ', [c for c in commands])
if __name__ == '__main__':
if len(sys.argv) < 2:
print_commands()
exit()
cmd = sys.argv[1]
if cmd not in commands:
print('Commande \'%s\' non disponible' % cmd)
print_commands()
exit()
for app in apps:
os.chdir(app)
os.system(commands[cmd])
os.chdir('..')