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
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

122
api/package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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(

View File

@ -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' });

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 * 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;
}

View File

@ -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());
}

View File

@ -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),
);

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
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

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('..')