diff --git a/src/app.ts b/src/app.ts index 6a3e801..9e15de1 100644 --- a/src/app.ts +++ b/src/app.ts @@ -3,11 +3,15 @@ import * as dotenvSafe from "dotenv-safe"; import { createExpressServer } from "routing-controllers"; import consola from "consola"; import loaders from "./loaders/index"; +import authchecker from "./authchecker"; +import { ErrorHandler } from './middlewares/ErrorHandler'; dotenvSafe.config(); const PORT = process.env.APP_PORT || 4010; const app = createExpressServer({ + authorizationChecker: authchecker, + middlewares: [ErrorHandler], development: process.env.NODE_ENV === "production", cors: true, routePrefix: "/api", diff --git a/src/authchecker.ts b/src/authchecker.ts new file mode 100644 index 0000000..9b62dfa --- /dev/null +++ b/src/authchecker.ts @@ -0,0 +1,51 @@ +import * as jwt from "jsonwebtoken"; +import { Action, HttpError } from "routing-controllers"; +// ----------- +const sampletoken = jwt.sign({ + "permissions": { + "TRACKS": ["read", "update", "delete", "add"] + // "TRACKS": [] + } +}, process.env.JWT_SECRET || "secretjwtsecret") +console.log(`sampletoken: ${sampletoken}`); +// ----------- +const authchecker = async (action: Action, permissions: string | string[]) => { + let required_permissions = undefined + if (typeof permissions === "string") { + required_permissions = [permissions] + } else { + required_permissions = permissions + } + // const token = action.request.headers["authorization"]; + const provided_token = action.request.query["auth"]; + let jwtPayload = undefined + try { + jwtPayload = jwt.verify(provided_token, process.env.JWT_SECRET || "secretjwtsecret"); + } catch (error) { + throw new HttpError(401, "jwt_illegal") + } + if (jwtPayload.permissions) { + action.response.local = {} + action.response.local.jwtPayload = jwtPayload.permissions + required_permissions.forEach(r => { + const permission_key = r.split(":")[0] + const actual_accesslevel_for_permission = jwtPayload.permissions[permission_key] + const permission_access_level = r.split(":")[1] + if (actual_accesslevel_for_permission.includes(permission_access_level)) { + return true; + } else { + throw new HttpError(403, "no") + } + }); + } else { + throw new HttpError(403, "no") + } + // + try { + jwt.verify(provided_token, process.env.JWT_SECRET || "secretjwtsecret"); + return true + } catch (error) { + return false + } +} +export default authchecker \ No newline at end of file diff --git a/src/controllers/TrackController.ts b/src/controllers/TrackController.ts index 04c9e3a..c6327a9 100644 --- a/src/controllers/TrackController.ts +++ b/src/controllers/TrackController.ts @@ -1,90 +1,91 @@ -import { JsonController, Param, Body, Get, Post, Put, Delete, OnUndefined, NotAcceptableError } from 'routing-controllers'; -import { getConnectionManager, Repository } from 'typeorm'; -import { EntityFromBody } from 'typeorm-routing-controllers-extensions'; -import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; -import { Track } from '../models/Track'; -import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator'; -import {TrackIdChangeNotAllowedError, TrackNotFoundError} from "../errors/TrackErrors"; - -class CreateTrack { - @IsString() - @IsNotEmpty() - name: string; - - @IsInt() - @IsPositive() - length: number; -} - -@JsonController('/tracks') -export class TrackController { - private trackRepository: Repository; - - /** - * Gets the repository of this controller's model/entity. - */ - constructor() { - this.trackRepository = getConnectionManager().get().getRepository(Track); - } - - @Get() - @ResponseSchema(Track, { isArray: true }) - @OpenAPI({description: "Lists all tracks."}) - getAll() { - return this.trackRepository.find(); - } - - @Get('/:id') - @ResponseSchema(Track) - @ResponseSchema(TrackNotFoundError, {statusCode: 404}) - @OnUndefined(TrackNotFoundError) - @OpenAPI({description: "Returns a track of a specified id (if it exists)"}) - getOne(@Param('id') id: number) { - return this.trackRepository.findOne({ id: id }); - } - - @Post() - @ResponseSchema(Track) - @OpenAPI({description: "Create a new track object (id will be generated automagicly)."}) - post( - @Body({ validate: true }) - track: CreateTrack - ) { - return this.trackRepository.save(track); - } - - @Put('/:id') - @ResponseSchema(Track) - @ResponseSchema(TrackNotFoundError, {statusCode: 404}) - @ResponseSchema(TrackIdChangeNotAllowedError, {statusCode: 406}) - @OpenAPI({description: "Update a track object (id can't be changed)."}) - async put(@Param('id') id: number, @EntityFromBody() track: Track) { - let oldTrack = await this.trackRepository.findOne({ id: id }); - - if (!oldTrack) { - throw new TrackNotFoundError(); - } - - if(oldTrack.id != track.id){ - throw new TrackIdChangeNotAllowedError(); - } - - await this.trackRepository.update(oldTrack, track); - return track; - } - - @Delete('/:id') - @ResponseSchema(Track) - @ResponseSchema(TrackNotFoundError, {statusCode: 404}) - @OpenAPI({description: "Delete a specified track (if it exists)."}) - async remove(@Param('id') id: number) { - let track = await this.trackRepository.findOne({ id: id }); - - if (!track) { - throw new TrackNotFoundError(); - } - - await this.trackRepository.delete(track); - return track; - } -} +import { JsonController, Param, Body, Get, Post, Put, Delete, NotFoundError, OnUndefined, NotAcceptableError, Authorized } from 'routing-controllers'; +import { getConnectionManager, Repository } from 'typeorm'; +import { EntityFromBody } from 'typeorm-routing-controllers-extensions'; +import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; +import { Track } from '../models/Track'; +import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator'; +import {TrackIdChangeNotAllowedError, TrackNotFoundError} from "../errors/TrackErrors"; + +class CreateTrack { + @IsString() + @IsNotEmpty() + name: string; + + @IsInt() + @IsPositive() + length: number; +} + +@JsonController('/tracks') +@Authorized("TRACKS:read") +export class TrackController { + private trackRepository: Repository; + + /** + * Gets the repository of this controller's model/entity. + */ + constructor() { + this.trackRepository = getConnectionManager().get().getRepository(Track); + } + + @Get() + @ResponseSchema(Track, { isArray: true }) + @OpenAPI({ description: "Lists all tracks." }) + getAll() { + return this.trackRepository.find(); + } + + @Get('/:id') + @ResponseSchema(Track) + @ResponseSchema(TrackNotFoundError, {statusCode: 404}) + @OnUndefined(TrackNotFoundError) + @OpenAPI({ description: "Returns a track of a specified id (if it exists)" }) + getOne(@Param('id') id: number) { + return this.trackRepository.findOne({ id: id }); + } + + @Post() + @ResponseSchema(Track) + @OpenAPI({ description: "Create a new track object (id will be generated automagicly)." }) + post( + @Body({ validate: true }) + track: CreateTrack + ) { + return this.trackRepository.save(track); + } + + @Put('/:id') + @ResponseSchema(Track) + @ResponseSchema(TrackNotFoundError, {statusCode: 404}) + @ResponseSchema(TrackIdChangeNotAllowedError, {statusCode: 406}) + @OpenAPI({description: "Update a track object (id can't be changed)."}) + async put(@Param('id') id: number, @EntityFromBody() track: Track) { + let oldTrack = await this.trackRepository.findOne({ id: id }); + + if (!oldTrack) { + throw new TrackNotFoundError(); + } + + if(oldTrack.id != track.id){ + throw new TrackIdChangeNotAllowedError(); + } + + await this.trackRepository.update(oldTrack, track); + return track; + } + + @Delete('/:id') + @ResponseSchema(Track) + @ResponseSchema(TrackNotFoundError, {statusCode: 404}) + @OpenAPI({description: "Delete a specified track (if it exists)."}) + async remove(@Param('id') id: number) { + let track = await this.trackRepository.findOne({ id: id }); + + if (!track) { + throw new TrackNotFoundError(); + } + + await this.trackRepository.delete(track); + return track; + } +} diff --git a/src/middlewares/ErrorHandler.ts b/src/middlewares/ErrorHandler.ts new file mode 100644 index 0000000..7f45073 --- /dev/null +++ b/src/middlewares/ErrorHandler.ts @@ -0,0 +1,20 @@ +import { + Middleware, + ExpressErrorMiddlewareInterface +} from "routing-controllers"; + +@Middleware({ type: "after" }) +export class ErrorHandler implements ExpressErrorMiddlewareInterface { + public error( + error: any, + request: any, + response: any, + next: (err: any) => any + ) { + if (response.headersSent) { + return; + } + + response.json(error); + } +}