diff --git a/api/src/framework/express/middleware.ts b/api/src/framework/express/middleware.ts index 8a21827..846db09 100644 --- a/api/src/framework/express/middleware.ts +++ b/api/src/framework/express/middleware.ts @@ -1,17 +1,100 @@ import { ErrorRequestHandler, Request, RequestHandler } from 'express'; import { randomUUID } from 'crypto'; +import { validationResult } from 'express-validator'; +import { UserInfo, UserRoles } from '@core'; +import permissions from './permissions'; -export function getId(req: Request): string { - return req.get('request-id') || 'unknown'; +declare module 'express-session' { + interface SessionData { + user: UserInfo | null; + } } -export function BeforeEach(): RequestHandler { +export function getId(req: Request): string { + return req.header('request-id') || 'unknown'; +} + +export function RequestId(): RequestHandler { return (req, res, next) => { req.headers['request-id'] = randomUUID(); next(); }; } +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; + } + + 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: 403, messsage: 'Forbidden' }); + 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: 403, messsage: 'Forbidden' }); + return; + } + + if ( + req.session.user.role === UserRoles.ADMIN || + (req.session.user.role === permissions[route] && canAccess(req)) + ) { + next(); + return; + } + + next({ status: 403, messsage: 'Forbidden' }); + }; +} + +export function SchemaValidator(): RequestHandler { + return (req, res, next) => { + const error = validationResult(req); + error.isEmpty() + ? next() + : next({ + status: 400, + ...error, + }); + }; +} + export function ErrorHandler(): ErrorRequestHandler { return (error, req, res, next) => { error.status diff --git a/api/src/framework/express/permissions.ts b/api/src/framework/express/permissions.ts new file mode 100644 index 0000000..60339ec --- /dev/null +++ b/api/src/framework/express/permissions.ts @@ -0,0 +1,10 @@ +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/schema/user.ts b/api/src/framework/express/schema/user.ts new file mode 100644 index 0000000..eb9a541 --- /dev/null +++ b/api/src/framework/express/schema/user.ts @@ -0,0 +1,38 @@ +import { checkSchema } from 'express-validator'; +import { UserRoles } from '@core'; + +export const ReadUserSchema = () => + checkSchema({ + uuid: { + isUUID: { + options: 4, + }, + }, + }); + +export const LoginUserSchema = () => + checkSchema({ + username: { + isString: true, + }, + password: { + isString: true, + }, + }); + +export const LogoutUserSchema = () => checkSchema({}); + +export const CreateUserSchema = () => + checkSchema({ + username: { + isString: true, + }, + password: { + isString: true, + }, + role: { + isIn: { + options: [Object.values(UserRoles)], + }, + }, + }); diff --git a/api/src/framework/express/server.ts b/api/src/framework/express/server.ts index 24eb2c4..22f08ce 100644 --- a/api/src/framework/express/server.ts +++ b/api/src/framework/express/server.ts @@ -1,9 +1,11 @@ import express, { Express } from 'express'; import cors from 'cors'; -import * as router from './router'; -import { BeforeEach, ErrorHandler } from './middleware'; +import session from 'express-session'; +import { getRoutes } from './router'; +import { RequestId, ErrorHandler, checkPermissions } from './middleware'; import { Services } from '../../app'; import { ServerConfig } from '../../config'; +import { randomUUID } from 'crypto'; class Server { private app: Express; @@ -15,8 +17,12 @@ class Server { this.app = express(); this.app.use(express.json()); this.app.use(cors()); - this.app.use(BeforeEach()); - this.app.use(router.getRoutes(services)); + this.app.use(RequestId()); + this.app.use( + session({ secret: randomUUID(), cookie: { maxAge: 1000 * 3600 * 24 } }), + ); + this.app.use(checkPermissions()); + this.app.use(getRoutes(services)); this.app.use(ErrorHandler()); } diff --git a/api/src/framework/express/user.ts b/api/src/framework/express/user.ts index 9898c95..a349cca 100644 --- a/api/src/framework/express/user.ts +++ b/api/src/framework/express/user.ts @@ -1,21 +1,39 @@ import { RequestHandler, Router } from 'express'; import { Services } from '../../app'; -import { Create, Read, Login } from '../../functions/user'; -import { getId } from './middleware'; +import { CreateUser, ReadUser, LoginUser } from '../../functions/user'; +import { + LoginUserSchema, + CreateUserSchema, + ReadUserSchema, + LogoutUserSchema, +} from './schema/user'; +import { getId, SchemaValidator } from './middleware'; -export function LoginHandler(services: Services): RequestHandler { - const login = Login(services); +function LoginHandler(services: Services): RequestHandler { + const login = LoginUser(services); return async (req, res, next) => { const user = await login(getId(req), req.body); + user ? (req.session.user = user) : (req.session.user = null); user ? res.status(200).send(user) - : next({ status: 404, message: 'bad user or password' }); + : next({ status: 404, message: 'wrong user or password' }); }; } -export function CreateHandler(services: Services): RequestHandler { - const createUser = Create(services); +function LogoutHandler(services: Services): RequestHandler { + return async (req, res, next) => { + if (req.session.user) { + req.session.user = null; + res.status(204).send(); + } else { + next({ message: 'not logged in' }); + } + }; +} + +function CreateHandler(services: Services): RequestHandler { + const createUser = CreateUser(services); return async (req, res, next) => { const user = await createUser(getId(req), req.body); @@ -23,8 +41,8 @@ export function CreateHandler(services: Services): RequestHandler { }; } -export function ReadHandler(services: Services): RequestHandler { - const readUser = Read(services); +function ReadHandler(services: Services): RequestHandler { + const readUser = ReadUser(services); return async (req, res, next) => { const user = await readUser(getId(req), req.params.uuid); @@ -37,9 +55,31 @@ export function ReadHandler(services: Services): RequestHandler { export function getRoutes(services: Services) { const router = Router(); - router.post('/login', LoginHandler(services)); - router.get('/read/:uuid', ReadHandler(services)); - router.post('/create', CreateHandler(services)); + router.get( + '/read/:uuid', + ReadUserSchema(), + SchemaValidator(), + ReadHandler(services), + ); + + router.post( + '/login', + LoginUserSchema(), + SchemaValidator(), + LoginHandler(services), + ); + router.post( + '/logout', + LogoutUserSchema(), + SchemaValidator(), + LogoutHandler(services), + ); + router.post( + '/create', + CreateUserSchema(), + SchemaValidator(), + CreateHandler(services), + ); return router; } diff --git a/api/src/functions/user.ts b/api/src/functions/user.ts index 92cbe74..e47c9b7 100644 --- a/api/src/functions/user.ts +++ b/api/src/functions/user.ts @@ -2,7 +2,7 @@ import { CreateUserBody, LoginUserBody, UserInfo } from '@core'; import { Services } from '../app'; import { UserWithPassword } from '../entities/user'; -export function Login( +export function LoginUser( services: Services, ): (tracker: string, raw: LoginUserBody) => Promise { const { userModel } = services; @@ -13,7 +13,7 @@ export function Login( }; } -export function Create( +export function CreateUser( services: Services, ): (tracker: string, raw: CreateUserBody) => Promise { const { userModel } = services; @@ -24,7 +24,7 @@ export function Create( }; } -export function Read( +export function ReadUser( services: Services, ): (tracker: string, uuid: string) => Promise { const { userModel } = services;