feature/#22-API-Tokens #49
8 changed files with 115 additions and 5 deletions
|
@ -18,5 +18,5 @@ MAIL_HOST = mail_hoststring
|
||||||
MAIL_PORT = mail_portnumber
|
MAIL_PORT = mail_portnumber
|
||||||
MAIL_SECURE (true|false) // true for port 465, fals for other ports
|
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
|
CLUB_WEBSITE = https://my-club-website-url
|
|
@ -40,7 +40,7 @@ services:
|
||||||
- MAIL_HOST=<url>
|
- MAIL_HOST=<url>
|
||||||
- MAIL_PORT=<port> # default ist auf 578 gesetzt
|
- MAIL_PORT=<port> # default ist auf 578 gesetzt
|
||||||
- MAIL_SECURE=<boolean> # default ist auf false gesetzt
|
- MAIL_SECURE=<boolean> # default ist auf false gesetzt
|
||||||
- CLUB_NAME=<tobemodified>
|
- CLUB_NAME=<tobemodified> # default ist auf FF Admin gesetzt
|
||||||
- CLUB_WEBSITE=<tobemodified>
|
- CLUB_WEBSITE=<tobemodified>
|
||||||
volumes:
|
volumes:
|
||||||
- <volume|local path>:/app/files
|
- <volume|local path>:/app/files
|
||||||
|
|
|
@ -7,6 +7,9 @@ import { CreateApiCommand, DeleteApiCommand, UpdateApiCommand } from "../../../c
|
||||||
import ApiCommandHandler from "../../../command/user/api/apiCommandHandler";
|
import ApiCommandHandler from "../../../command/user/api/apiCommandHandler";
|
||||||
import { UpdateApiPermissionsCommand } from "../../../command/user/api/apiPermissionCommand";
|
import { UpdateApiPermissionsCommand } from "../../../command/user/api/apiPermissionCommand";
|
||||||
import ApiPermissionCommandHandler from "../../../command/user/api/apiPermissionCommandHandler";
|
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
|
* @description get All apis
|
||||||
|
@ -69,10 +72,15 @@ export async function createApi(req: Request, res: Response): Promise<any> {
|
||||||
let title = req.body.title;
|
let title = req.body.title;
|
||||||
let expiry = req.body.expiry;
|
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 = {
|
let createApi: CreateApiCommand = {
|
||||||
token: "",
|
token: token,
|
||||||
title: title,
|
title: title,
|
||||||
expiry: expiry,
|
expiry: expiry,
|
||||||
};
|
};
|
||||||
|
|
34
src/controller/apiController.ts
Normal file
34
src/controller/apiController.ts
Normal file
|
@ -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<any> {
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
|
@ -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_PORT = Number(process.env.MAIL_PORT ?? "587");
|
||||||
export const MAIL_SECURE = process.env.MAIL_SECURE ?? "false";
|
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 const CLUB_WEBSITE = process.env.CLUB_WEBSITE ?? "";
|
||||||
|
|
||||||
export function configCheck() {
|
export function configCheck() {
|
||||||
|
|
|
@ -6,6 +6,8 @@ import RolePermissionService from "../service/user/rolePermissionService";
|
||||||
import UserPermissionService from "../service/user/userPermissionService";
|
import UserPermissionService from "../service/user/userPermissionService";
|
||||||
import UserService from "../service/user/userService";
|
import UserService from "../service/user/userService";
|
||||||
import PermissionHelper from "./permissionHelper";
|
import PermissionHelper from "./permissionHelper";
|
||||||
|
import ApiService from "../service/user/apiService";
|
||||||
|
import ApiPermissionService from "../service/user/apiPermissionService";
|
||||||
|
|
||||||
export abstract class JWTHelper {
|
export abstract class JWTHelper {
|
||||||
static validate(token: string): Promise<string | jwt.JwtPayload> {
|
static validate(token: string): Promise<string | jwt.JwtPayload> {
|
||||||
|
@ -72,4 +74,29 @@ export abstract class JWTHelper {
|
||||||
throw new InternalException("Failed accessToken creation", err);
|
throw new InternalException("Failed accessToken creation", err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async buildApiToken(token: string): Promise<string> {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,10 @@ export default async function authenticate(req: Request, res: Response, next: Fu
|
||||||
throw new InternalException("process failed");
|
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.userId = decoded.userId;
|
||||||
req.username = decoded.username;
|
req.username = decoded.username;
|
||||||
req.isOwner = decoded.isOwner;
|
req.isOwner = decoded.isOwner;
|
||||||
|
|
37
src/middleware/authenticateAPI.ts
Normal file
37
src/middleware/authenticateAPI.ts
Normal file
|
@ -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();
|
||||||
|
}
|
Loading…
Reference in a new issue