diff --git a/.env.example b/.env.example index 28b487d..c3fd1f3 100644 --- a/.env.example +++ b/.env.example @@ -18,5 +18,5 @@ MAIL_HOST = mail_hoststring MAIL_PORT = mail_portnumber MAIL_SECURE (true|false) // true for port 465, fals for other ports -CLUB_NAME = clubname +CLUB_NAME = clubname #default FF Admin CLUB_WEBSITE = https://my-club-website-url \ No newline at end of file diff --git a/README.md b/README.md index 3dcc570..17ee5d8 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ services: - MAIL_HOST= - MAIL_PORT= # default ist auf 578 gesetzt - MAIL_SECURE= # default ist auf false gesetzt - - CLUB_NAME= + - CLUB_NAME= # default ist auf FF Admin gesetzt - CLUB_WEBSITE= volumes: - :/app/files diff --git a/src/controller/admin/user/apiController.ts b/src/controller/admin/user/apiController.ts index 93aee08..1d64139 100644 --- a/src/controller/admin/user/apiController.ts +++ b/src/controller/admin/user/apiController.ts @@ -7,6 +7,9 @@ import { CreateApiCommand, DeleteApiCommand, UpdateApiCommand } from "../../../c import ApiCommandHandler from "../../../command/user/api/apiCommandHandler"; import { UpdateApiPermissionsCommand } from "../../../command/user/api/apiPermissionCommand"; import ApiPermissionCommandHandler from "../../../command/user/api/apiPermissionCommandHandler"; +import { JWTHelper } from "../../../helpers/jwtHelper"; +import { CLUB_NAME } from "../../../env.defaults"; +import { StringHelper } from "../../../helpers/stringHelper"; /** * @description get All apis @@ -69,10 +72,15 @@ export async function createApi(req: Request, res: Response): Promise { let title = req.body.title; let expiry = req.body.expiry; - // TODO: create jwt as token to prevent random string tests + let token = await JWTHelper.create({ + iss: CLUB_NAME, + sub: "api_token_retrieve", + iat: new Date().toISOString(), + aud: StringHelper.random(32), + }); let createApi: CreateApiCommand = { - token: "", + token: token, title: title, expiry: expiry, }; diff --git a/src/controller/apiController.ts b/src/controller/apiController.ts new file mode 100644 index 0000000..7b9bc82 --- /dev/null +++ b/src/controller/apiController.ts @@ -0,0 +1,34 @@ +import { Request, Response } from "express"; +import { JWTHelper } from "../helpers/jwtHelper"; +import { JWTToken } from "../type/jwtTypes"; +import InternalException from "../exceptions/internalException"; +import RefreshCommandHandler from "../command/refreshCommandHandler"; +import { CreateRefreshCommand, DeleteRefreshCommand } from "../command/refreshCommand"; +import UserService from "../service/user/userService"; +import speakeasy from "speakeasy"; +import UnauthorizedRequestException from "../exceptions/unauthorizedRequestException"; +import RefreshService from "../service/refreshService"; +import ApiService from "../service/user/apiService"; +import ForbiddenRequestException from "../exceptions/forbiddenRequestException"; + +/** + * @description Check authentication status by token + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAccess(req: Request, res: Response): Promise { + const bearer = req.headers.authorization?.split(" ")?.[1] ?? undefined; + + let { expiry } = await ApiService.getByToken(bearer); + + if (new Date() > new Date(expiry)) { + throw new ForbiddenRequestException("api token expired"); + } + + let accessToken = await JWTHelper.buildApiToken(bearer); + + res.json({ + accessToken, + }); +} diff --git a/src/env.defaults.ts b/src/env.defaults.ts index 33fe53a..d4e3c3b 100644 --- a/src/env.defaults.ts +++ b/src/env.defaults.ts @@ -21,7 +21,7 @@ export const MAIL_HOST = process.env.MAIL_HOST ?? ""; export const MAIL_PORT = Number(process.env.MAIL_PORT ?? "587"); export const MAIL_SECURE = process.env.MAIL_SECURE ?? "false"; -export const CLUB_NAME = process.env.CLUB_NAME ?? ""; +export const CLUB_NAME = process.env.CLUB_NAME ?? "FF Admin"; export const CLUB_WEBSITE = process.env.CLUB_WEBSITE ?? ""; export function configCheck() { diff --git a/src/helpers/jwtHelper.ts b/src/helpers/jwtHelper.ts index b8a62a5..1be67c4 100644 --- a/src/helpers/jwtHelper.ts +++ b/src/helpers/jwtHelper.ts @@ -6,6 +6,8 @@ import RolePermissionService from "../service/user/rolePermissionService"; import UserPermissionService from "../service/user/userPermissionService"; import UserService from "../service/user/userService"; import PermissionHelper from "./permissionHelper"; +import ApiService from "../service/user/apiService"; +import ApiPermissionService from "../service/user/apiPermissionService"; export abstract class JWTHelper { static validate(token: string): Promise { @@ -72,4 +74,29 @@ export abstract class JWTHelper { throw new InternalException("Failed accessToken creation", err); }); } + + static async buildApiToken(token: string): Promise { + let { id, title } = await ApiService.getByToken(token); + let apiPermissions = await ApiPermissionService.getByApi(id); + let apiPermissionStrings = apiPermissions.map((e) => e.permission); + let permissionObject = PermissionHelper.convertToObject(apiPermissionStrings); + + let jwtData: JWTToken = { + userId: id, + mail: "", + username: title, + firstname: "", + lastname: "", + isOwner: false, + permissions: permissionObject, + }; + + return await JWTHelper.create(jwtData) + .then((result) => { + return result; + }) + .catch((err) => { + throw new InternalException("Failed accessToken creation", err); + }); + } } diff --git a/src/middleware/authenticate.ts b/src/middleware/authenticate.ts index cfa1f56..1b59362 100644 --- a/src/middleware/authenticate.ts +++ b/src/middleware/authenticate.ts @@ -29,6 +29,10 @@ export default async function authenticate(req: Request, res: Response, next: Fu throw new InternalException("process failed"); } + if (decoded?.sub == "api_token_retrieve") { + throw new BadRequestException("This token is only authorized to get temporary access tokens via GET /api/webapi"); + } + req.userId = decoded.userId; req.username = decoded.username; req.isOwner = decoded.isOwner; diff --git a/src/middleware/authenticateAPI.ts b/src/middleware/authenticateAPI.ts new file mode 100644 index 0000000..b05060e --- /dev/null +++ b/src/middleware/authenticateAPI.ts @@ -0,0 +1,37 @@ +import { Request, Response } from "express"; +import jwt from "jsonwebtoken"; +import BadRequestException from "../exceptions/badRequestException"; +import UnauthorizedRequestException from "../exceptions/unauthorizedRequestException"; +import InternalException from "../exceptions/internalException"; +import { JWTHelper } from "../helpers/jwtHelper"; + +export default async function authenticateAPI(req: Request, res: Response, next: Function) { + const bearer = req.headers.authorization?.split(" ")?.[1] ?? undefined; + + if (!bearer) { + throw new BadRequestException("Provide valid Authorization Header"); + } + + let decoded: string | jwt.JwtPayload; + await JWTHelper.validate(bearer) + .then((result) => { + decoded = result; + }) + .catch((err) => { + if (err == "jwt expired") { + throw new UnauthorizedRequestException("Token expired", err); + } else { + throw new BadRequestException("Failed Authorization Header decoding", err); + } + }); + + if (typeof decoded == "string" || !decoded) { + throw new InternalException("process failed"); + } + + if (decoded?.sub != "api_token_retrieve") { + throw new BadRequestException("This route can only be accessed via a api token"); + } + + next(); +}