From 6b815a6a68eaf1f0f285330453778d69461440a8 Mon Sep 17 00:00:00 2001 From: Yanis Rigaudeau Date: Tue, 25 Oct 2022 22:03:11 +0200 Subject: [PATCH] env + perms + tools --- api/api.service | 1 + api/package-lock.json | 122 +++++++++++++++++++++++ api/package.json | 3 +- api/src/app.ts | 2 +- api/src/framework/express/middleware.ts | 65 ++++-------- api/src/framework/express/permissions.ts | 10 -- api/src/framework/express/router.ts | 4 +- api/src/framework/express/server.ts | 28 ++++-- api/src/framework/express/user.ts | 20 ++-- apps.json | 3 - scripts/build.py | 12 --- scripts/deploy.sh | 4 +- scripts/install.py | 12 --- scripts/prettier.py | 12 --- scripts/tools.py | 32 ++++++ 15 files changed, 216 insertions(+), 114 deletions(-) delete mode 100644 api/src/framework/express/permissions.ts delete mode 100644 apps.json delete mode 100755 scripts/build.py delete mode 100755 scripts/install.py delete mode 100755 scripts/prettier.py create mode 100755 scripts/tools.py diff --git a/api/api.service b/api/api.service index 4940d61..48eb3da 100644 --- a/api/api.service +++ b/api/api.service @@ -10,6 +10,7 @@ User=node Group=node Environment="CONFIGFILE=/home/node/config/config.json" +Environment="NODE_ENV=production" WorkingDirectory=/home/node/app ExecStart=/usr/bin/node /home/node/app/dist/api/src/app.js diff --git a/api/package-lock.json b/api/package-lock.json index 13c2122..02ed472 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -8,6 +8,7 @@ "name": "api", "version": "1.0.0", "dependencies": { + "connect-mongo": "^4.6.0", "cors": "^2.8.5", "express": "^4.18.1", "express-session": "^1.17.3", @@ -279,6 +280,17 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "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": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -313,6 +325,11 @@ "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": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", @@ -451,6 +468,42 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "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": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -921,6 +974,17 @@ "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": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -989,6 +1053,11 @@ "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": { "version": "3.1.2", "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", "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": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1967,6 +2047,11 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "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": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", @@ -2064,6 +2149,30 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "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": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -2415,6 +2524,14 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "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": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -2465,6 +2582,11 @@ "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": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", diff --git a/api/package.json b/api/package.json index d4eba30..6debfcb 100644 --- a/api/package.json +++ b/api/package.json @@ -6,7 +6,7 @@ "private": true, "scripts": { "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", "prettier": "prettier -w ./src" }, @@ -23,6 +23,7 @@ "typescript": "^4.0.0" }, "dependencies": { + "connect-mongo": "^4.6.0", "cors": "^2.8.5", "express": "^4.18.1", "express-session": "^1.17.3", diff --git a/api/src/app.ts b/api/src/app.ts index 7907bb0..df325cb 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -25,7 +25,7 @@ const services: Services = { userModel: new UserModel(db), }; -const server = new Server(config.server, services); +const server = new Server(config.server, config.mongo, services); server.start(() => console.log( diff --git a/api/src/framework/express/middleware.ts b/api/src/framework/express/middleware.ts index 89db7d6..6069954 100644 --- a/api/src/framework/express/middleware.ts +++ b/api/src/framework/express/middleware.ts @@ -2,7 +2,6 @@ import { ErrorRequestHandler, Request, RequestHandler } from 'express'; import { randomUUID } from 'crypto'; import { validationResult } from 'express-validator'; import { UserInfo, UserRoles } from '@core'; -import permissions from './permissions'; declare module 'express-session' { interface SessionData { @@ -21,62 +20,42 @@ export function RequestId(): RequestHandler { }; } -export function checkPermissions(): RequestHandler { - const getRoute = (url: string): string => { - for (const route in permissions) { - if (url.startsWith(route)) return route; - } - return ''; - }; - - 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; - } +export function CheckPermissions(): RequestHandler { + function getResourceId(req: Request): string | null { + console.log(req.url); + if (req.params.uuid) return req.params.uuid; + if (req.body.uuid) return req.body.uuid; + return null; + } + function canAccessRessource(user: UserInfo, uuid: string): boolean { + if (user.uuid === uuid) return true; return false; - }; + } 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) { next({ status: 401, messsage: 'Unauthorized' }); return; } - if ( - !(route in permissions) || - (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' }); + if (req.session.user.role === UserRoles.ADMIN) { + next(); return; } - if ( - req.session.user.role === UserRoles.ADMIN || - (req.session.user.role === permissions[route] && canAccess(req)) - ) { + const ressourceId = getResourceId(req); + console.log(ressourceId); + if (!ressourceId) { + next({ status: 403, messsage: 'Forbidden' }); + return; + } + if (canAccessRessource(req.session.user, ressourceId)) { next(); return; + } else { + next({ status: 403, messsage: 'Forbidden' }); + return; } next({ status: 401, messsage: 'Unauthorized' }); diff --git a/api/src/framework/express/permissions.ts b/api/src/framework/express/permissions.ts deleted file mode 100644 index 60339ec..0000000 --- a/api/src/framework/express/permissions.ts +++ /dev/null @@ -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; diff --git a/api/src/framework/express/router.ts b/api/src/framework/express/router.ts index ea69708..f60b70d 100644 --- a/api/src/framework/express/router.ts +++ b/api/src/framework/express/router.ts @@ -2,10 +2,10 @@ import { Router } from 'express'; import { Services } from '../../app'; import * as user from './user'; -export function getRoutes(services: Services) { +export function Routes(services: Services) { const router = Router(); - router.use('/user', user.getRoutes(services)); + router.use('/user', user.Routes(services)); return router; } diff --git a/api/src/framework/express/server.ts b/api/src/framework/express/server.ts index 0043bd6..1ead917 100644 --- a/api/src/framework/express/server.ts +++ b/api/src/framework/express/server.ts @@ -1,17 +1,22 @@ import express, { Express } from 'express'; import cors from 'cors'; import session from 'express-session'; -import { getRoutes } from './router'; -import { RequestId, ErrorHandler, checkPermissions } from './middleware'; +import { Routes } from './router'; +import { RequestId, ErrorHandler } from './middleware'; import { Services } from '../../app'; -import { ServerConfig } from '../../config'; +import { MongoConfig, ServerConfig } from '../../config'; import { randomUUID } from 'crypto'; +import MongoStore from 'connect-mongo'; class Server { private app: Express; private config: ServerConfig; - constructor(config: ServerConfig, services: Services) { + constructor( + config: ServerConfig, + mongoConfig: MongoConfig, + services: Services, + ) { this.config = config; this.app = express(); @@ -26,14 +31,23 @@ class Server { this.app.use(RequestId()); this.app.use( session({ + proxy: process.env.NODE_ENV === 'production', 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, saveUninitialized: false, }), ); - //this.app.use(checkPermissions()); - this.app.use(getRoutes(services)); + this.app.use(Routes(services)); this.app.use(ErrorHandler()); } diff --git a/api/src/framework/express/user.ts b/api/src/framework/express/user.ts index 3a6758b..08fea14 100644 --- a/api/src/framework/express/user.ts +++ b/api/src/framework/express/user.ts @@ -7,7 +7,7 @@ import { ReadUserSchema, LogoutUserSchema, } from './schema/user'; -import { getId, SchemaValidator } from './middleware'; +import { CheckPermissions, getId, SchemaValidator } from './middleware'; function LoginHandler(services: Services): RequestHandler { 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(); - router.get( - '/read/:uuid', - ReadUserSchema(), - SchemaValidator(), - ReadHandler(services), - ); - router.post( '/login', LoginUserSchema(), @@ -74,10 +67,19 @@ export function getRoutes(services: Services) { SchemaValidator(), LogoutHandler(services), ); + router.get( + '/read/:uuid', + ReadUserSchema(), + SchemaValidator(), + CheckPermissions(), + ReadHandler(services), + ); + router.post( '/create', CreateUserSchema(), SchemaValidator(), + CheckPermissions(), CreateHandler(services), ); diff --git a/apps.json b/apps.json deleted file mode 100644 index df0af35..0000000 --- a/apps.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "apps": ["core", "api", "www"] -} diff --git a/scripts/build.py b/scripts/build.py deleted file mode 100755 index ee7e1fe..0000000 --- a/scripts/build.py +++ /dev/null @@ -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('..') diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 567544e..304f589 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -2,8 +2,8 @@ useradd node -md /home/node -s /usr/sbin/nologin -python3 scripts/install.py -python3 scripts/build.py +python3 scripts/tools.py install +python3 scripts/tools.py build rm -r /var/www/html/* mv www/public/* /var/www/html diff --git a/scripts/install.py b/scripts/install.py deleted file mode 100755 index 46b3bfa..0000000 --- a/scripts/install.py +++ /dev/null @@ -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('..') diff --git a/scripts/prettier.py b/scripts/prettier.py deleted file mode 100755 index 8244a4f..0000000 --- a/scripts/prettier.py +++ /dev/null @@ -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('..') diff --git a/scripts/tools.py b/scripts/tools.py new file mode 100755 index 0000000..25f9e74 --- /dev/null +++ b/scripts/tools.py @@ -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('..')