From bdeadd274bc0f9c8cbab35a8a5605bef4c22ba6c Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Sat, 13 Feb 2021 16:09:58 +0100 Subject: [PATCH 1/4] Implemented basic auth ref #26 --- src/app.ts | 2 ++ src/config.ts | 23 ++++++++++++++++++++++- src/controllers/PdfController.ts | 3 ++- src/middlewares/AuthChecker.ts | 14 ++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 src/middlewares/AuthChecker.ts diff --git a/src/app.ts b/src/app.ts index 6996e09..78846f3 100644 --- a/src/app.ts +++ b/src/app.ts @@ -3,11 +3,13 @@ import "reflect-metadata"; import { createExpressServer } from "routing-controllers"; import { config, e as errors } from './config'; import loaders from "./loaders/index"; +import AuthChecker from './middlewares/AuthChecker'; import { ErrorHandler } from './middlewares/ErrorHandler'; const CONTROLLERS_FILE_EXTENSION = process.env.NODE_ENV === 'production' ? 'js' : 'ts'; const app = createExpressServer({ middlewares: [ErrorHandler], + authorizationChecker: AuthChecker, development: config.development, cors: true, controllers: [`${__dirname}/controllers/*.${CONTROLLERS_FILE_EXTENSION}`], diff --git a/src/config.ts b/src/config.ts index 6815cc2..ad886d7 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,3 +1,4 @@ +import consola from "consola"; import { config as configDotenv } from 'dotenv'; configDotenv(); @@ -9,7 +10,8 @@ export const config = { currency_symbol: process.env.CURRENCY_SYMBOL || "€", sponsoring_receipt_minimum_amount: process.env.SPONSORING_RECEIPT_MINIMUM_AMOUNT || "10", codeformat: process.env.CODEFORMAT || "qrcode", - sponor_logos: getSponsorLogos() + sponor_logos: getSponsorLogos(), + api_key: getApiKey(), } let errors = 0 if (typeof config.internal_port !== "number") { @@ -27,4 +29,23 @@ function getSponsorLogos(): string[] { return ["data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg=="]; } } + +function getApiKey(): string { + const key = process.env.API_KEY; + if (!key) { + consola.info("No API key set - generating a random one..."); + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + const charactersLength = characters.length; + for (var i = 0; i < 64; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + consola.info(`API KEY: ${result}`) + return result; + } + if (key.length < 64) { + consola.error(`API key is too short - minimum: 64, current: ${key.length}`) + throw new Error("API_KEY too short.") + } +} export let e = errors \ No newline at end of file diff --git a/src/controllers/PdfController.ts b/src/controllers/PdfController.ts index 17e2ca6..15f0db4 100644 --- a/src/controllers/PdfController.ts +++ b/src/controllers/PdfController.ts @@ -1,4 +1,4 @@ -import { Body, JsonController, Post, QueryParam, Res } from 'routing-controllers'; +import { Authorized, Body, JsonController, Post, QueryParam, Res } from 'routing-controllers'; import { OpenAPI } from 'routing-controllers-openapi'; import { Runner } from '../models/Runner'; import { RunnerCard } from '../models/RunnerCard'; @@ -10,6 +10,7 @@ import { PdfCreator } from '../PdfCreator'; * All endpoints have to accept a locale query-param to support i18n. */ @JsonController() +@Authorized() export class PdfController { private pdf: PdfCreator = new PdfCreator(); private initialized: boolean = false; diff --git a/src/middlewares/AuthChecker.ts b/src/middlewares/AuthChecker.ts new file mode 100644 index 0000000..e4ede50 --- /dev/null +++ b/src/middlewares/AuthChecker.ts @@ -0,0 +1,14 @@ +import { Action } from "routing-controllers"; +import { config } from '../config'; + +/** + * Handles authentication via jwt's (Bearer authorization header) for all api endpoints using the @Authorized decorator. + * @param action Routing-Controllers action object that provides request and response objects among other stuff. + * @param permissions The permissions that the endpoint using @Authorized requires. + */ +const AuthChecker = async (action: Action) => { + const provided_token = action.request.query.key; + return provided_token == config.api_key; +} + +export default AuthChecker \ No newline at end of file From 7be211f8b7b26f7f620df81af4ebde5eec2feec2 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Sat, 13 Feb 2021 16:14:08 +0100 Subject: [PATCH 2/4] Fixed bug ref #26 --- src/config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config.ts b/src/config.ts index ad886d7..e7da417 100644 --- a/src/config.ts +++ b/src/config.ts @@ -47,5 +47,6 @@ function getApiKey(): string { consola.error(`API key is too short - minimum: 64, current: ${key.length}`) throw new Error("API_KEY too short.") } + return key } export let e = errors \ No newline at end of file From 454309278ef20a2b97248277b07a7b58a063618d Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Sat, 13 Feb 2021 16:16:36 +0100 Subject: [PATCH 3/4] Added api key to env doc ref #26 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7e77e25..ac522c5 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ The basic generation mechanism makes the templates and routes interchangeable (i | CURRENCY_SYMBOL | String | "€" | The your currency's symbol - used to generate pdf text. | SPONSORING_RECEIPT_MINIMUM_AMOUNT | String | "10" | The mimimum total donation amount a sponsor has to donate to be able to receive a donation receipt - used to generate pdf text. | SPONOR_LOGOS | Array | Empty png | The sponsor images you want to loop through. You can provide them via http url, local file or base64-encoded image. +| API_KEY | String(min length: 64) | Random generated string | The api key you want to use for auth (query-param `key`), has to be at least 64 chars long. ## Templates > The document server uses html templates to generate various pdf documents. From 729f2d7240b54ffe2d4db36cce29de0afdfc9417 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Sat, 13 Feb 2021 16:24:47 +0100 Subject: [PATCH 4/4] Added auth to openapi spec ref #26 --- src/apispec.ts | 8 ++++++++ src/controllers/PdfController.ts | 1 + 2 files changed, 9 insertions(+) diff --git a/src/apispec.ts b/src/apispec.ts index 4a1f496..9aa3018 100644 --- a/src/apispec.ts +++ b/src/apispec.ts @@ -14,6 +14,14 @@ export function generateSpec(storage: MetadataArgsStorage, schemas) { { components: { schemas, + "securitySchemes": { + "AuthToken": { + "type": "apiKey", + "in": "query", + "name": "key", + description: "A simple api key. See the README's env section for more details." + } + } }, info: { description: "The the API for the LfK! document server.", diff --git a/src/controllers/PdfController.ts b/src/controllers/PdfController.ts index 15f0db4..3e82693 100644 --- a/src/controllers/PdfController.ts +++ b/src/controllers/PdfController.ts @@ -11,6 +11,7 @@ import { PdfCreator } from '../PdfCreator'; */ @JsonController() @Authorized() +@OpenAPI({ security: [{ "AuthToken": [] }] }) export class PdfController { private pdf: PdfCreator = new PdfCreator(); private initialized: boolean = false;