From 26d2f288e9c9d3a0789e9e362ae9c10ab0e4d21b Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Tue, 21 Jan 2025 11:03:48 +0100 Subject: [PATCH 1/8] add api tables with permission --- src/data-source.ts | 6 +++ src/entity/user/api.ts | 19 +++++++ src/entity/user/api_permission.ts | 19 +++++++ src/migrations/1737453096674-addApiTokens.ts | 53 ++++++++++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 src/entity/user/api.ts create mode 100644 src/entity/user/api_permission.ts create mode 100644 src/migrations/1737453096674-addApiTokens.ts diff --git a/src/data-source.ts b/src/data-source.ts index c7ba457..b3c0227 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -68,6 +68,9 @@ import { Memberlist1736079005086 } from "./migrations/1736079005086-memberlist"; import { ExtendViewValues1736084198860 } from "./migrations/1736084198860-extendViewValues"; import { FinishInternalIdTransfer1736505324488 } from "./migrations/1736505324488-finishInternalIdTransfer"; import { ProtocolPresenceExcuse1737287798828 } from "./migrations/1737287798828-protocolPresenceExcuse"; +import { api } from "./entity/user/api"; +import { apiPermission } from "./entity/user/api_permission"; +import { AddApiTokens1737453096674 } from "./migrations/1737453096674-addApiTokens"; const dataSource = new DataSource({ type: DB_TYPE as any, @@ -117,6 +120,8 @@ const dataSource = new DataSource({ memberExecutivePositionsView, memberQualificationsView, membershipView, + api, + apiPermission, ], migrations: [ Initial1724317398939, @@ -146,6 +151,7 @@ const dataSource = new DataSource({ ExtendViewValues1736084198860, FinishInternalIdTransfer1736505324488, ProtocolPresenceExcuse1737287798828, + AddApiTokens1737453096674, ], migrationsRun: true, migrationsTransactionMode: "each", diff --git a/src/entity/user/api.ts b/src/entity/user/api.ts new file mode 100644 index 0000000..a3ead90 --- /dev/null +++ b/src/entity/user/api.ts @@ -0,0 +1,19 @@ +import { Column, CreateDateColumn, Entity, PrimaryColumn } from "typeorm"; + +@Entity() +export class api { + @PrimaryColumn({ type: "varchar", length: 255 }) + token: string; + + @Column({ type: "varchar", length: 255 }) + title: string; + + @CreateDateColumn() + createdAt: Date; + + @Column({ type: "datetime", nullable: true }) + lastUsage?: Date; + + @Column({ type: "datetime", nullable: true }) + expiry?: Date; +} diff --git a/src/entity/user/api_permission.ts b/src/entity/user/api_permission.ts new file mode 100644 index 0000000..4eb546d --- /dev/null +++ b/src/entity/user/api_permission.ts @@ -0,0 +1,19 @@ +import { Column, Entity, ManyToOne, PrimaryColumn } from "typeorm"; +import { PermissionObject, PermissionString } from "../../type/permissionTypes"; +import { api } from "./api"; + +@Entity() +export class apiPermission { + @PrimaryColumn({ type: "int" }) + apiToken: number; + + @PrimaryColumn({ type: "varchar", length: 255 }) + permission: PermissionString; + + @ManyToOne(() => api, { + nullable: false, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + api: api; +} diff --git a/src/migrations/1737453096674-addApiTokens.ts b/src/migrations/1737453096674-addApiTokens.ts new file mode 100644 index 0000000..182f77f --- /dev/null +++ b/src/migrations/1737453096674-addApiTokens.ts @@ -0,0 +1,53 @@ +import { MigrationInterface, QueryRunner, Table, TableForeignKey } from "typeorm"; +import { DB_TYPE } from "../env.defaults"; + +export class AddApiTokens1737453096674 implements MigrationInterface { + name = "AddApiTokens1737453096674"; + + public async up(queryRunner: QueryRunner): Promise { + const variableType_int = DB_TYPE == "mysql" ? "int" : "integer"; + + await queryRunner.createTable( + new Table({ + name: "api", + columns: [ + { name: "token", type: "varchar", length: "255", isPrimary: true, isNullable: false }, + { name: "title", type: "varchar", length: "255", isNullable: false }, + { name: "createdAt", type: "datetime", default: "CURRENT_TIMESTAMP(6)", isNullable: false }, + { name: "lastUsage", type: "datetime", isNullable: true }, + { name: "expiry", type: "datetime", isNullable: true }, + ], + }), + true + ); + + await queryRunner.createTable( + new Table({ + name: "api_permission", + columns: [ + { name: "apiToken", type: "varchar", length: "255", isPrimary: true, isNullable: false }, + { name: "permission", type: "varchar", length: "255", isPrimary: true, isNullable: false }, + ], + }) + ); + + await queryRunner.createForeignKey( + "api_permission", + new TableForeignKey({ + columnNames: ["apiToken"], + referencedColumnNames: ["token"], + referencedTableName: "api", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + ); + } + + public async down(queryRunner: QueryRunner): Promise { + const table = await queryRunner.getTable("api_permission"); + const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("apiToken") !== -1); + await queryRunner.dropForeignKey("api_permission", foreignKey); + await queryRunner.dropTable("api_permission"); + await queryRunner.dropTable("api"); + } +} From ca6dbafaf19fe400a3cd1c55fb7c280d14941306 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Tue, 21 Jan 2025 11:25:26 +0100 Subject: [PATCH 2/8] extend api table by id --- src/entity/user/api.ts | 5 ++++- src/entity/user/api_permission.ts | 2 +- src/migrations/1737453096674-addApiTokens.ts | 16 +++++++++------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/entity/user/api.ts b/src/entity/user/api.ts index a3ead90..36279fd 100644 --- a/src/entity/user/api.ts +++ b/src/entity/user/api.ts @@ -2,7 +2,10 @@ import { Column, CreateDateColumn, Entity, PrimaryColumn } from "typeorm"; @Entity() export class api { - @PrimaryColumn({ type: "varchar", length: 255 }) + @PrimaryColumn({ generated: "increment", type: "int" }) + id: number; + + @Column({ type: "varchar", length: 255, unique: true, select: false }) token: string; @Column({ type: "varchar", length: 255 }) diff --git a/src/entity/user/api_permission.ts b/src/entity/user/api_permission.ts index 4eb546d..1b031b4 100644 --- a/src/entity/user/api_permission.ts +++ b/src/entity/user/api_permission.ts @@ -5,7 +5,7 @@ import { api } from "./api"; @Entity() export class apiPermission { @PrimaryColumn({ type: "int" }) - apiToken: number; + apiId: number; @PrimaryColumn({ type: "varchar", length: 255 }) permission: PermissionString; diff --git a/src/migrations/1737453096674-addApiTokens.ts b/src/migrations/1737453096674-addApiTokens.ts index 182f77f..d547892 100644 --- a/src/migrations/1737453096674-addApiTokens.ts +++ b/src/migrations/1737453096674-addApiTokens.ts @@ -11,11 +11,12 @@ export class AddApiTokens1737453096674 implements MigrationInterface { new Table({ name: "api", columns: [ - { name: "token", type: "varchar", length: "255", isPrimary: true, isNullable: false }, + { name: "id", type: variableType_int, isPrimary: true, isNullable: false }, + { name: "token", type: "varchar", length: "255", isUnique: true, isNullable: false }, { name: "title", type: "varchar", length: "255", isNullable: false }, { name: "createdAt", type: "datetime", default: "CURRENT_TIMESTAMP(6)", isNullable: false }, - { name: "lastUsage", type: "datetime", isNullable: true }, - { name: "expiry", type: "datetime", isNullable: true }, + { name: "lastUsage", type: "datetime", isNullable: true, default: null }, + { name: "expiry", type: "datetime", isNullable: true, default: null }, ], }), true @@ -25,17 +26,18 @@ export class AddApiTokens1737453096674 implements MigrationInterface { new Table({ name: "api_permission", columns: [ - { name: "apiToken", type: "varchar", length: "255", isPrimary: true, isNullable: false }, + { name: "apiId", type: variableType_int, isPrimary: true, isNullable: false }, { name: "permission", type: "varchar", length: "255", isPrimary: true, isNullable: false }, ], - }) + }), + true ); await queryRunner.createForeignKey( "api_permission", new TableForeignKey({ - columnNames: ["apiToken"], - referencedColumnNames: ["token"], + columnNames: ["apiId"], + referencedColumnNames: ["id"], referencedTableName: "api", onDelete: "CASCADE", onUpdate: "RESTRICT", From 3f0549bd4431cb0549a3d82a80cfb0d80c7c6630 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Tue, 21 Jan 2025 11:37:28 +0100 Subject: [PATCH 3/8] api & permission services and commandHandler --- src/command/user/api/apiCommand.ts | 15 +++ src/command/user/api/apiCommandHandler.ts | 69 +++++++++++ src/command/user/api/apiPermissionCommand.ts | 16 +++ .../user/api/apiPermissionCommandHandler.ts | 114 ++++++++++++++++++ src/service/user/apiPermissionService.ts | 24 ++++ src/service/user/apiService.ts | 83 +++++++++++++ 6 files changed, 321 insertions(+) create mode 100644 src/command/user/api/apiCommand.ts create mode 100644 src/command/user/api/apiCommandHandler.ts create mode 100644 src/command/user/api/apiPermissionCommand.ts create mode 100644 src/command/user/api/apiPermissionCommandHandler.ts create mode 100644 src/service/user/apiPermissionService.ts create mode 100644 src/service/user/apiService.ts diff --git a/src/command/user/api/apiCommand.ts b/src/command/user/api/apiCommand.ts new file mode 100644 index 0000000..b2c669f --- /dev/null +++ b/src/command/user/api/apiCommand.ts @@ -0,0 +1,15 @@ +export interface CreateApiCommand { + title: string; + token: string; + expiry?: Date; +} + +export interface UpdateApiCommand { + id: number; + title: string; + expiry?: Date; +} + +export interface DeleteApiCommand { + id: number; +} diff --git a/src/command/user/api/apiCommandHandler.ts b/src/command/user/api/apiCommandHandler.ts new file mode 100644 index 0000000..180ff32 --- /dev/null +++ b/src/command/user/api/apiCommandHandler.ts @@ -0,0 +1,69 @@ +import { dataSource } from "../../../data-source"; +import { api } from "../../../entity/user/api"; +import InternalException from "../../../exceptions/internalException"; +import { CreateApiCommand, DeleteApiCommand, UpdateApiCommand } from "./apiCommand"; + +export default abstract class ApiCommandHandler { + /** + * @description create api + * @param {CreateApiCommand} createApi + * @returns {Promise} + */ + static async create(createApi: CreateApiCommand): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(api) + .values({ + token: createApi.token, + title: createApi.title, + expiry: createApi.expiry, + }) + .execute() + .then((result) => { + return result.identifiers[0].token; + }) + .catch((err) => { + throw new InternalException("Failed creating api", err); + }); + } + + /** + * @description update api + * @param {UpdateApiCommand} updateApi + * @returns {Promise} + */ + static async update(updateApi: UpdateApiCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(api) + .set({ + title: updateApi.title, + expiry: updateApi.expiry, + }) + .where("id = :id", { id: updateApi.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new InternalException("Failed updating api", err); + }); + } + + /** + * @description delete api + * @param {DeleteApiCommand} deleteApi + * @returns {Promise} + */ + static async delete(deleteApi: DeleteApiCommand): Promise { + return await dataSource + .createQueryBuilder() + .delete() + .from(api) + .where("id = :id", { id: deleteApi.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new InternalException("Failed deleting api", err); + }); + } +} diff --git a/src/command/user/api/apiPermissionCommand.ts b/src/command/user/api/apiPermissionCommand.ts new file mode 100644 index 0000000..2b61b65 --- /dev/null +++ b/src/command/user/api/apiPermissionCommand.ts @@ -0,0 +1,16 @@ +import { PermissionString } from "../../../type/permissionTypes"; + +export interface CreateApiPermissionCommand { + permission: PermissionString; + apiId: number; +} + +export interface DeleteApiPermissionCommand { + permission: PermissionString; + apiId: number; +} + +export interface UpdateApiPermissionsCommand { + apiId: number; + permissions: Array; +} diff --git a/src/command/user/api/apiPermissionCommandHandler.ts b/src/command/user/api/apiPermissionCommandHandler.ts new file mode 100644 index 0000000..f6fdb64 --- /dev/null +++ b/src/command/user/api/apiPermissionCommandHandler.ts @@ -0,0 +1,114 @@ +import { DeleteResult, EntityManager, InsertResult } from "typeorm"; +import { dataSource } from "../../../data-source"; +import { apiPermission } from "../../../entity/user/api_permission"; +import InternalException from "../../../exceptions/internalException"; +import ApiService from "../../../service/user/apiService"; +import { + CreateApiPermissionCommand, + DeleteApiPermissionCommand, + UpdateApiPermissionsCommand, +} from "./apiPermissionCommand"; +import PermissionHelper from "../../../helpers/permissionHelper"; +import ApiPermissionService from "../../../service/user/apiPermissionService"; +import { PermissionString } from "../../../type/permissionTypes"; + +export default abstract class ApiPermissionCommandHandler { + /** + * @description update api permissions + * @param {UpdateApiPermissionsCommand} updateApiPermissions + * @returns {Promise} + */ + static async updatePermissions(updateApiPermissions: UpdateApiPermissionsCommand): Promise { + let currentPermissions = (await ApiPermissionService.getByApi(updateApiPermissions.apiId)).map((r) => r.permission); + return await dataSource.manager + .transaction(async (manager) => { + let newPermissions = PermissionHelper.getWhatToAdd(currentPermissions, updateApiPermissions.permissions); + let removePermissions = PermissionHelper.getWhatToRemove(currentPermissions, updateApiPermissions.permissions); + if (newPermissions.length != 0) { + await this.updatePermissionsAdd(manager, updateApiPermissions.apiId, newPermissions); + } + if (removePermissions.length != 0) { + await this.updatePermissionsRemove(manager, updateApiPermissions.apiId, removePermissions); + } + }) + .then(() => {}) + .catch((err) => { + throw new InternalException("Failed saving api permissions", err); + }); + } + + private static async updatePermissionsAdd( + manager: EntityManager, + apiId: number, + permissions: Array + ): Promise { + return await manager + .createQueryBuilder() + .insert() + .into(apiPermission) + .values( + permissions.map((p) => ({ + permission: p, + apiId: apiId, + })) + ) + .orIgnore() + .execute(); + } + + private static async updatePermissionsRemove( + manager: EntityManager, + apiId: number, + permissions: Array + ): Promise { + return await manager + .createQueryBuilder() + .delete() + .from(apiPermission) + .where("apiId = :id", { id: apiId }) + .andWhere("permission IN (:...permission)", { permission: permissions }) + .execute(); + } + + /** + * @description grant permission to user + * @param {CreateApiPermissionCommand} createPermission + * @returns {Promise} + */ + static async create(createPermission: CreateApiPermissionCommand): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(apiPermission) + .values({ + permission: createPermission.permission, + apiId: createPermission.apiId, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new InternalException("Failed saving api permission", err); + }); + } + + /** + * @description remove permission from api + * @param {DeleteApiPermissionCommand} deletePermission + * @returns {Promise} + */ + static async delete(deletePermission: DeleteApiPermissionCommand): Promise { + return await dataSource + .createQueryBuilder() + .delete() + .from(apiPermission) + .where("apiId = :id", { id: deletePermission.apiId }) + .andWhere("permission = :permission", { permission: deletePermission.permission }) + .execute() + .then(() => {}) + .catch((err) => { + throw new InternalException("failed api permission removal", err); + }); + } +} diff --git a/src/service/user/apiPermissionService.ts b/src/service/user/apiPermissionService.ts new file mode 100644 index 0000000..13f0ed2 --- /dev/null +++ b/src/service/user/apiPermissionService.ts @@ -0,0 +1,24 @@ +import { dataSource } from "../../data-source"; +import { apiPermission } from "../../entity/user/api_permission"; +import InternalException from "../../exceptions/internalException"; + +export default abstract class ApiPermissionService { + /** + * @description get permission by api + * @param apiId number + * @returns {Promise>} + */ + static async getByApi(apiId: number): Promise> { + return await dataSource + .getRepository(apiPermission) + .createQueryBuilder("api_permission") + .where("api_permission.apiId = :apiId", { apiId: apiId }) + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("api permissions not found by api", err); + }); + } +} diff --git a/src/service/user/apiService.ts b/src/service/user/apiService.ts new file mode 100644 index 0000000..b44b3ca --- /dev/null +++ b/src/service/user/apiService.ts @@ -0,0 +1,83 @@ +import { dataSource } from "../../data-source"; +import { api } from "../../entity/user/api"; +import InternalException from "../../exceptions/internalException"; + +export default abstract class ApiService { + /** + * @description get apis + * @returns {Promise>} + */ + static async getAll(): Promise> { + return await dataSource + .getRepository(api) + .createQueryBuilder("api") + .leftJoinAndSelect("api.permissions", "permissions") + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("apis not found", err); + }); + } + + /** + * @description get api by id + * @param id number + * @returns {Promise} + */ + static async getById(id: number): Promise { + return await dataSource + .getRepository(api) + .createQueryBuilder("api") + .leftJoinAndSelect("api.permissions", "permissions") + .where("api.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("api not found by id", err); + }); + } + + /** + * @description get api by token + * @param token string + * @returns {Promise} + */ + static async getByToken(token: string): Promise { + return await dataSource + .getRepository(api) + .createQueryBuilder("api") + .leftJoinAndSelect("api.permissions", "permissions") + .where("api.token = :token", { token: token }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("api not found by token", err); + }); + } + + /** + * @description get api by id + * @param id number + * @returns {Promise} + */ + static async getTokenById(id: number): Promise { + return await dataSource + .getRepository(api) + .createQueryBuilder("api") + .select("token") + .where("api.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("api token not found by id", err); + }); + } +} From 36ecccd0dca7b270e05201461048c0388e91d6ec Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Tue, 21 Jan 2025 11:47:28 +0100 Subject: [PATCH 4/8] controller & factory --- src/controller/admin/user/apiController.ts | 141 +++++++++++++++++++++ src/entity/user/api.ts | 6 +- src/entity/user/api_permission.ts | 2 +- src/factory/admin/user/api.ts | 30 +++++ src/viewmodel/admin/user/api.models.ts | 10 ++ 5 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 src/controller/admin/user/apiController.ts create mode 100644 src/factory/admin/user/api.ts create mode 100644 src/viewmodel/admin/user/api.models.ts diff --git a/src/controller/admin/user/apiController.ts b/src/controller/admin/user/apiController.ts new file mode 100644 index 0000000..93aee08 --- /dev/null +++ b/src/controller/admin/user/apiController.ts @@ -0,0 +1,141 @@ +import { Request, Response } from "express"; +import ApiService from "../../../service/user/apiService"; +import ApiFactory from "../../../factory/admin/user/api"; +import ApiPermissionService from "../../../service/user/apiPermissionService"; +import PermissionHelper from "../../../helpers/permissionHelper"; +import { CreateApiCommand, DeleteApiCommand, UpdateApiCommand } from "../../../command/user/api/apiCommand"; +import ApiCommandHandler from "../../../command/user/api/apiCommandHandler"; +import { UpdateApiPermissionsCommand } from "../../../command/user/api/apiPermissionCommand"; +import ApiPermissionCommandHandler from "../../../command/user/api/apiPermissionCommandHandler"; + +/** + * @description get All apis + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllApis(req: Request, res: Response): Promise { + let apis = await ApiService.getAll(); + + res.json(ApiFactory.mapToBase(apis)); +} + +/** + * @description get api by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getApiById(req: Request, res: Response): Promise { + const id = parseInt(req.params.id); + let api = await ApiService.getById(id); + + res.json(ApiFactory.mapToSingle(api)); +} + +/** + * @description get api token by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getApiTokenById(req: Request, res: Response): Promise { + const id = parseInt(req.params.id); + let { token } = await ApiService.getTokenById(id); + + res.send(token); +} + +/** + * @description get permissions by api + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getApiPermissions(req: Request, res: Response): Promise { + const id = parseInt(req.params.id); + let permissions = await ApiPermissionService.getByApi(id); + + res.json(PermissionHelper.convertToObject(permissions.map((p) => p.permission))); +} + +/** + * @description create new api + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +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 createApi: CreateApiCommand = { + token: "", + title: title, + expiry: expiry, + }; + await ApiCommandHandler.create(createApi); + + res.sendStatus(204); +} + +/** + * @description update api data + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateApi(req: Request, res: Response): Promise { + const id = parseInt(req.params.id); + let title = req.body.title; + let expiry = req.body.expiry; + + let updateApi: UpdateApiCommand = { + id: id, + title: title, + expiry: expiry, + }; + await ApiCommandHandler.update(updateApi); + + res.sendStatus(204); +} + +/** + * @description update api assigned permission strings + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateApiPermissions(req: Request, res: Response): Promise { + const id = parseInt(req.params.id); + let permissions = req.body.permissions; + + let permissionStrings = PermissionHelper.convertToStringArray(permissions); + + let updateApiPermissions: UpdateApiPermissionsCommand = { + apiId: id, + permissions: permissionStrings, + }; + await ApiPermissionCommandHandler.updatePermissions(updateApiPermissions); + + res.sendStatus(204); +} + +/** + * @description delete api by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function deleteApi(req: Request, res: Response): Promise { + const id = parseInt(req.params.id); + + let deleteApi: DeleteApiCommand = { + id: id, + }; + await ApiCommandHandler.delete(deleteApi); + + res.sendStatus(204); +} diff --git a/src/entity/user/api.ts b/src/entity/user/api.ts index 36279fd..3e21b78 100644 --- a/src/entity/user/api.ts +++ b/src/entity/user/api.ts @@ -1,4 +1,5 @@ -import { Column, CreateDateColumn, Entity, PrimaryColumn } from "typeorm"; +import { Column, CreateDateColumn, Entity, OneToMany, PrimaryColumn } from "typeorm"; +import { apiPermission } from "./api_permission"; @Entity() export class api { @@ -19,4 +20,7 @@ export class api { @Column({ type: "datetime", nullable: true }) expiry?: Date; + + @OneToMany(() => apiPermission, (apiPermission) => apiPermission.api) + permissions: apiPermission[]; } diff --git a/src/entity/user/api_permission.ts b/src/entity/user/api_permission.ts index 1b031b4..4e7509f 100644 --- a/src/entity/user/api_permission.ts +++ b/src/entity/user/api_permission.ts @@ -1,4 +1,4 @@ -import { Column, Entity, ManyToOne, PrimaryColumn } from "typeorm"; +import { Column, Entity, ManyToOne, OneToMany, PrimaryColumn } from "typeorm"; import { PermissionObject, PermissionString } from "../../type/permissionTypes"; import { api } from "./api"; diff --git a/src/factory/admin/user/api.ts b/src/factory/admin/user/api.ts new file mode 100644 index 0000000..111be2a --- /dev/null +++ b/src/factory/admin/user/api.ts @@ -0,0 +1,30 @@ +import { api } from "../../../entity/user/api"; +import PermissionHelper from "../../../helpers/permissionHelper"; +import { ApiViewModel } from "../../../viewmodel/admin/user/api.models"; + +export default abstract class ApiFactory { + /** + * @description map record to api + * @param {api} record + * @returns {apiViewModel} + */ + public static mapToSingle(record: api): ApiViewModel { + return { + id: record.id, + permissions: PermissionHelper.convertToObject(record.permissions.map((e) => e.permission)), + title: record.title, + expiry: record.expiry, + lastUsage: record.lastUsage, + createdAt: record.createdAt, + }; + } + + /** + * @description map records to api + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/viewmodel/admin/user/api.models.ts b/src/viewmodel/admin/user/api.models.ts new file mode 100644 index 0000000..cf3a861 --- /dev/null +++ b/src/viewmodel/admin/user/api.models.ts @@ -0,0 +1,10 @@ +import { PermissionObject } from "../../../type/permissionTypes"; + +export interface ApiViewModel { + id: number; + permissions: PermissionObject; + title: string; + createdAt: Date; + lastUsage?: Date; + expiry?: Date; +} From 4568bef10e469eba599fe252caa6f332d915bab7 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Tue, 21 Jan 2025 13:54:52 +0100 Subject: [PATCH 5/8] api Controller & token --- .env.example | 2 +- README.md | 2 +- src/controller/admin/user/apiController.ts | 12 +++++-- src/controller/apiController.ts | 34 ++++++++++++++++++++ src/env.defaults.ts | 2 +- src/helpers/jwtHelper.ts | 27 ++++++++++++++++ src/middleware/authenticate.ts | 4 +++ src/middleware/authenticateAPI.ts | 37 ++++++++++++++++++++++ 8 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 src/controller/apiController.ts create mode 100644 src/middleware/authenticateAPI.ts 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(); +} From 0b40b9d92c67deb8c03b6f6b2e83ba10f9d006a1 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Wed, 22 Jan 2025 09:27:15 +0100 Subject: [PATCH 6/8] permissions & routes with middleware --- src/controller/apiController.ts | 2 +- src/helpers/jwtHelper.ts | 1 + src/index.ts | 1 + src/middleware/authenticate.ts | 1 + src/middleware/preventWebApiAccess.ts | 10 +++++ src/routes/admin/index.ts | 3 ++ src/routes/admin/user/api.ts | 59 +++++++++++++++++++++++++++ src/routes/api.ts | 10 +++++ src/routes/index.ts | 3 ++ src/type/permissionTypes.ts | 4 +- 10 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 src/middleware/preventWebApiAccess.ts create mode 100644 src/routes/admin/user/api.ts create mode 100644 src/routes/api.ts diff --git a/src/controller/apiController.ts b/src/controller/apiController.ts index 7b9bc82..bb49a8c 100644 --- a/src/controller/apiController.ts +++ b/src/controller/apiController.ts @@ -17,7 +17,7 @@ import ForbiddenRequestException from "../exceptions/forbiddenRequestException"; * @param res {Response} Express res object * @returns {Promise<*>} */ -export async function getAccess(req: Request, res: Response): Promise { +export async function getWebApiAccess(req: Request, res: Response): Promise { const bearer = req.headers.authorization?.split(" ")?.[1] ?? undefined; let { expiry } = await ApiService.getByToken(bearer); diff --git a/src/helpers/jwtHelper.ts b/src/helpers/jwtHelper.ts index 1be67c4..66beaab 100644 --- a/src/helpers/jwtHelper.ts +++ b/src/helpers/jwtHelper.ts @@ -89,6 +89,7 @@ export abstract class JWTHelper { lastname: "", isOwner: false, permissions: permissionObject, + sub: "webapi_access_token", }; return await JWTHelper.create(jwtData) diff --git a/src/index.ts b/src/index.ts index f5a73e6..68eef4f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ declare global { isOwner: boolean; permissions: PermissionObject; isPWA: boolean; + isWebApiRequest: boolean; } } } diff --git a/src/middleware/authenticate.ts b/src/middleware/authenticate.ts index 1b59362..9e62bb4 100644 --- a/src/middleware/authenticate.ts +++ b/src/middleware/authenticate.ts @@ -37,6 +37,7 @@ export default async function authenticate(req: Request, res: Response, next: Fu req.username = decoded.username; req.isOwner = decoded.isOwner; req.permissions = decoded.permissions; + req.isWebApiRequest = decoded?.sub == "webapi_access_token"; next(); } diff --git a/src/middleware/preventWebApiAccess.ts b/src/middleware/preventWebApiAccess.ts new file mode 100644 index 0000000..a7b10a5 --- /dev/null +++ b/src/middleware/preventWebApiAccess.ts @@ -0,0 +1,10 @@ +import { Request, Response } from "express"; +import ForbiddenRequestException from "../exceptions/forbiddenRequestException"; + +export default async function preventApiAccess(req: Request, res: Response, next: Function) { + if (req.isWebApiRequest) { + throw new ForbiddenRequestException("This route cannot be accessed via webapi"); + } else { + next(); + } +} diff --git a/src/routes/admin/index.ts b/src/routes/admin/index.ts index cd7c935..3662de9 100644 --- a/src/routes/admin/index.ts +++ b/src/routes/admin/index.ts @@ -21,6 +21,8 @@ import newsletter from "./club/newsletter"; import role from "./user/role"; import user from "./user/user"; import invite from "./user/invite"; +import api from "./user/api"; +import preventApiAccess from "../../middleware/preventWebApiAccess"; var router = express.Router({ mergeParams: true }); @@ -60,5 +62,6 @@ router.use("/newsletter", PermissionHelper.passCheckMiddleware("read", "club", " router.use("/role", PermissionHelper.passCheckMiddleware("read", "user", "role"), role); router.use("/user", PermissionHelper.passCheckMiddleware("read", "user", "user"), user); router.use("/invite", PermissionHelper.passCheckMiddleware("read", "user", "user"), invite); +router.use("/webapi", preventApiAccess, PermissionHelper.passCheckMiddleware("read", "user", "webapi"), api); export default router; diff --git a/src/routes/admin/user/api.ts b/src/routes/admin/user/api.ts new file mode 100644 index 0000000..c5b3089 --- /dev/null +++ b/src/routes/admin/user/api.ts @@ -0,0 +1,59 @@ +import express, { Request, Response } from "express"; +import PermissionHelper from "../../../helpers/permissionHelper"; +import { + createApi, + deleteApi, + getAllApis, + getApiById, + getApiPermissions, + updateApi, + updateApiPermissions, +} from "../../../controller/admin/user/apiController"; + +var router = express.Router({ mergeParams: true }); + +router.get("/", async (req: Request, res: Response) => { + await getAllApis(req, res); +}); + +router.get("/:id", async (req: Request, res: Response) => { + await getApiById(req, res); +}); + +router.get("/:id/permissions", async (req: Request, res: Response) => { + await getApiPermissions(req, res); +}); + +router.post( + "/", + PermissionHelper.passCheckMiddleware("create", "user", "webapi"), + async (req: Request, res: Response) => { + await createApi(req, res); + } +); + +router.patch( + "/:id", + PermissionHelper.passCheckMiddleware("update", "user", "webapi"), + async (req: Request, res: Response) => { + await updateApi(req, res); + } +); + +router.patch( + "/:id/permissions", + PermissionHelper.passCheckMiddleware("admin", "user", "webapi"), + async (req: Request, res: Response) => { + await updateApiPermissions(req, res); + } +); + +router.delete( + "/:id", + PermissionHelper.passCheckMiddleware("delete", "user", "webapi"), + async (req: Request, res: Response) => { + await deleteApi(req, res); + } +); + +export default router; diff --git a/src/routes/api.ts b/src/routes/api.ts new file mode 100644 index 0000000..0ded862 --- /dev/null +++ b/src/routes/api.ts @@ -0,0 +1,10 @@ +import express, { Request, Response } from "express"; +import { getWebApiAccess } from "../controller/apiController"; + +var router = express.Router({ mergeParams: true }); + +router.get("/retrieve", async (req: Request, res: Response) => { + await getWebApiAccess(req, res); +}); + +export default router; diff --git a/src/routes/index.ts b/src/routes/index.ts index b939d52..06a1063 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -14,6 +14,8 @@ import auth from "./auth"; import admin from "./admin/index"; import user from "./user"; import detectPWA from "../middleware/detectPWA"; +import api from "./api"; +import authenticateAPI from "../middleware/authenticateAPI"; export default (app: Express) => { app.set("query parser", "extended"); @@ -32,6 +34,7 @@ export default (app: Express) => { app.use("/api/reset", reset); app.use("/api/invite", invite); app.use("/api/auth", auth); + app.use("/api/webapi", authenticateAPI, api); app.use(authenticate); app.use("/api/admin", admin); app.use("/api/user", user); diff --git a/src/type/permissionTypes.ts b/src/type/permissionTypes.ts index 95ea543..5ba5cb4 100644 --- a/src/type/permissionTypes.ts +++ b/src/type/permissionTypes.ts @@ -14,6 +14,7 @@ export type PermissionModule = | "calendar_type" | "user" | "role" + | "webapi" | "query" | "query_store" | "template" @@ -55,6 +56,7 @@ export const permissionModules: Array = [ "calendar_type", "user", "role", + "webapi", "query", "query_store", "template", @@ -75,5 +77,5 @@ export const sectionsAndModules: SectionsAndModulesObject = { "template_usage", "newsletter_config", ], - user: ["user", "role"], + user: ["user", "role", "webapi"], }; From 313785b4ace01eddc53ce48bc7b076a4eeee8c0f Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Wed, 22 Jan 2025 09:39:31 +0100 Subject: [PATCH 7/8] renaming api module to webapi --- .../apiCommand.ts => webapi/webapiCommand.ts} | 6 +- .../webapiCommandHandler.ts} | 38 ++++----- .../webapiPermissionCommand.ts} | 6 +- .../webapiPermissionCommandHandler.ts} | 63 +++++++------- .../{apiController.ts => webapiController.ts} | 58 +++++++------ .../{apiController.ts => webapiController.ts} | 6 +- src/data-source.ts | 12 +-- src/entity/user/{api.ts => webapi.ts} | 8 +- ...api_permission.ts => webapi_permission.ts} | 10 +-- src/factory/admin/user/{api.ts => webapi.ts} | 12 +-- src/helpers/jwtHelper.ts | 16 ++-- src/middleware/preventWebApiAccess.ts | 2 +- ...ns.ts => 1737453096674-addwebapiTokens.ts} | 28 +++---- src/routes/admin/index.ts | 6 +- src/routes/admin/user/{api.ts => webapi.ts} | 30 +++---- src/routes/index.ts | 2 +- src/routes/{api.ts => webapi.ts} | 2 +- src/service/user/apiService.ts | 83 ------------------- ...nService.ts => webapiPermissionService.ts} | 14 ++-- src/service/user/webapiService.ts | 83 +++++++++++++++++++ .../user/{api.models.ts => webapi.models.ts} | 0 21 files changed, 247 insertions(+), 238 deletions(-) rename src/command/user/{api/apiCommand.ts => webapi/webapiCommand.ts} (50%) rename src/command/user/{api/apiCommandHandler.ts => webapi/webapiCommandHandler.ts} (54%) rename src/command/user/{api/apiPermissionCommand.ts => webapi/webapiPermissionCommand.ts} (60%) rename src/command/user/{api/apiPermissionCommandHandler.ts => webapi/webapiPermissionCommandHandler.ts} (57%) rename src/controller/admin/user/{apiController.ts => webapiController.ts} (58%) rename src/controller/{apiController.ts => webapiController.ts} (86%) rename src/entity/user/{api.ts => webapi.ts} (72%) rename src/entity/user/{api_permission.ts => webapi_permission.ts} (73%) rename src/factory/admin/user/{api.ts => webapi.ts} (64%) rename src/migrations/{1737453096674-addApiTokens.ts => 1737453096674-addwebapiTokens.ts} (67%) rename src/routes/admin/user/{api.ts => webapi.ts} (69%) rename src/routes/{api.ts => webapi.ts} (78%) delete mode 100644 src/service/user/apiService.ts rename src/service/user/{apiPermissionService.ts => webapiPermissionService.ts} (52%) create mode 100644 src/service/user/webapiService.ts rename src/viewmodel/admin/user/{api.models.ts => webapi.models.ts} (100%) diff --git a/src/command/user/api/apiCommand.ts b/src/command/user/webapi/webapiCommand.ts similarity index 50% rename from src/command/user/api/apiCommand.ts rename to src/command/user/webapi/webapiCommand.ts index b2c669f..2ec3586 100644 --- a/src/command/user/api/apiCommand.ts +++ b/src/command/user/webapi/webapiCommand.ts @@ -1,15 +1,15 @@ -export interface CreateApiCommand { +export interface CreateWebapiCommand { title: string; token: string; expiry?: Date; } -export interface UpdateApiCommand { +export interface UpdateWebapiCommand { id: number; title: string; expiry?: Date; } -export interface DeleteApiCommand { +export interface DeleteWebapiCommand { id: number; } diff --git a/src/command/user/api/apiCommandHandler.ts b/src/command/user/webapi/webapiCommandHandler.ts similarity index 54% rename from src/command/user/api/apiCommandHandler.ts rename to src/command/user/webapi/webapiCommandHandler.ts index 180ff32..71689a6 100644 --- a/src/command/user/api/apiCommandHandler.ts +++ b/src/command/user/webapi/webapiCommandHandler.ts @@ -1,23 +1,23 @@ import { dataSource } from "../../../data-source"; -import { api } from "../../../entity/user/api"; +import { webapi } from "../../../entity/user/webapi"; import InternalException from "../../../exceptions/internalException"; -import { CreateApiCommand, DeleteApiCommand, UpdateApiCommand } from "./apiCommand"; +import { CreateWebapiCommand, DeleteWebapiCommand, UpdateWebapiCommand } from "./webapiCommand"; -export default abstract class ApiCommandHandler { +export default abstract class WebapiCommandHandler { /** * @description create api - * @param {CreateApiCommand} createApi + * @param {CreateWebapiCommand} createWebapi * @returns {Promise} */ - static async create(createApi: CreateApiCommand): Promise { + static async create(createWebapi: CreateWebapiCommand): Promise { return await dataSource .createQueryBuilder() .insert() - .into(api) + .into(webapi) .values({ - token: createApi.token, - title: createApi.title, - expiry: createApi.expiry, + token: createWebapi.token, + title: createWebapi.title, + expiry: createWebapi.expiry, }) .execute() .then((result) => { @@ -30,18 +30,18 @@ export default abstract class ApiCommandHandler { /** * @description update api - * @param {UpdateApiCommand} updateApi + * @param {UpdateWebapiCommand} updateWebapi * @returns {Promise} */ - static async update(updateApi: UpdateApiCommand): Promise { + static async update(updateWebapi: UpdateWebapiCommand): Promise { return await dataSource .createQueryBuilder() - .update(api) + .update(webapi) .set({ - title: updateApi.title, - expiry: updateApi.expiry, + title: updateWebapi.title, + expiry: updateWebapi.expiry, }) - .where("id = :id", { id: updateApi.id }) + .where("id = :id", { id: updateWebapi.id }) .execute() .then(() => {}) .catch((err) => { @@ -51,15 +51,15 @@ export default abstract class ApiCommandHandler { /** * @description delete api - * @param {DeleteApiCommand} deleteApi + * @param {DeleteWebapiCommand} deleteWebapi * @returns {Promise} */ - static async delete(deleteApi: DeleteApiCommand): Promise { + static async delete(deleteWebapi: DeleteWebapiCommand): Promise { return await dataSource .createQueryBuilder() .delete() - .from(api) - .where("id = :id", { id: deleteApi.id }) + .from(webapi) + .where("id = :id", { id: deleteWebapi.id }) .execute() .then(() => {}) .catch((err) => { diff --git a/src/command/user/api/apiPermissionCommand.ts b/src/command/user/webapi/webapiPermissionCommand.ts similarity index 60% rename from src/command/user/api/apiPermissionCommand.ts rename to src/command/user/webapi/webapiPermissionCommand.ts index 2b61b65..6f192d1 100644 --- a/src/command/user/api/apiPermissionCommand.ts +++ b/src/command/user/webapi/webapiPermissionCommand.ts @@ -1,16 +1,16 @@ import { PermissionString } from "../../../type/permissionTypes"; -export interface CreateApiPermissionCommand { +export interface CreateWebapiPermissionCommand { permission: PermissionString; apiId: number; } -export interface DeleteApiPermissionCommand { +export interface DeleteWebapiPermissionCommand { permission: PermissionString; apiId: number; } -export interface UpdateApiPermissionsCommand { +export interface UpdateWebapiPermissionsCommand { apiId: number; permissions: Array; } diff --git a/src/command/user/api/apiPermissionCommandHandler.ts b/src/command/user/webapi/webapiPermissionCommandHandler.ts similarity index 57% rename from src/command/user/api/apiPermissionCommandHandler.ts rename to src/command/user/webapi/webapiPermissionCommandHandler.ts index f6fdb64..6d30bc0 100644 --- a/src/command/user/api/apiPermissionCommandHandler.ts +++ b/src/command/user/webapi/webapiPermissionCommandHandler.ts @@ -1,34 +1,39 @@ import { DeleteResult, EntityManager, InsertResult } from "typeorm"; import { dataSource } from "../../../data-source"; -import { apiPermission } from "../../../entity/user/api_permission"; +import { webapiPermission } from "../../../entity/user/webapi_permission"; import InternalException from "../../../exceptions/internalException"; -import ApiService from "../../../service/user/apiService"; +import WebapiService from "../../../service/user/webapiService"; import { - CreateApiPermissionCommand, - DeleteApiPermissionCommand, - UpdateApiPermissionsCommand, -} from "./apiPermissionCommand"; + CreateWebapiPermissionCommand, + DeleteWebapiPermissionCommand, + UpdateWebapiPermissionsCommand, +} from "./webapiPermissionCommand"; import PermissionHelper from "../../../helpers/permissionHelper"; -import ApiPermissionService from "../../../service/user/apiPermissionService"; +import WebapiPermissionService from "../../../service/user/webapiPermissionService"; import { PermissionString } from "../../../type/permissionTypes"; -export default abstract class ApiPermissionCommandHandler { +export default abstract class WebapiPermissionCommandHandler { /** * @description update api permissions - * @param {UpdateApiPermissionsCommand} updateApiPermissions + * @param {UpdateWebapiPermissionsCommand} updateWebapiPermissions * @returns {Promise} */ - static async updatePermissions(updateApiPermissions: UpdateApiPermissionsCommand): Promise { - let currentPermissions = (await ApiPermissionService.getByApi(updateApiPermissions.apiId)).map((r) => r.permission); + static async updatePermissions(updateWebapiPermissions: UpdateWebapiPermissionsCommand): Promise { + let currentPermissions = (await WebapiPermissionService.getByApi(updateWebapiPermissions.apiId)).map( + (r) => r.permission + ); return await dataSource.manager .transaction(async (manager) => { - let newPermissions = PermissionHelper.getWhatToAdd(currentPermissions, updateApiPermissions.permissions); - let removePermissions = PermissionHelper.getWhatToRemove(currentPermissions, updateApiPermissions.permissions); + let newPermissions = PermissionHelper.getWhatToAdd(currentPermissions, updateWebapiPermissions.permissions); + let removePermissions = PermissionHelper.getWhatToRemove( + currentPermissions, + updateWebapiPermissions.permissions + ); if (newPermissions.length != 0) { - await this.updatePermissionsAdd(manager, updateApiPermissions.apiId, newPermissions); + await this.updatePermissionsAdd(manager, updateWebapiPermissions.apiId, newPermissions); } if (removePermissions.length != 0) { - await this.updatePermissionsRemove(manager, updateApiPermissions.apiId, removePermissions); + await this.updatePermissionsRemove(manager, updateWebapiPermissions.apiId, removePermissions); } }) .then(() => {}) @@ -39,17 +44,17 @@ export default abstract class ApiPermissionCommandHandler { private static async updatePermissionsAdd( manager: EntityManager, - apiId: number, + webapiId: number, permissions: Array ): Promise { return await manager .createQueryBuilder() .insert() - .into(apiPermission) + .into(webapiPermission) .values( permissions.map((p) => ({ permission: p, - apiId: apiId, + apiId: webapiId, })) ) .orIgnore() @@ -58,31 +63,31 @@ export default abstract class ApiPermissionCommandHandler { private static async updatePermissionsRemove( manager: EntityManager, - apiId: number, + webapiId: number, permissions: Array ): Promise { return await manager .createQueryBuilder() .delete() - .from(apiPermission) - .where("apiId = :id", { id: apiId }) + .from(webapiPermission) + .where("webapiId = :id", { id: webapiId }) .andWhere("permission IN (:...permission)", { permission: permissions }) .execute(); } /** * @description grant permission to user - * @param {CreateApiPermissionCommand} createPermission + * @param {CreateWebapiPermissionCommand} createPermission * @returns {Promise} */ - static async create(createPermission: CreateApiPermissionCommand): Promise { + static async create(createPermission: CreateWebapiPermissionCommand): Promise { return await dataSource .createQueryBuilder() .insert() - .into(apiPermission) + .into(webapiPermission) .values({ permission: createPermission.permission, - apiId: createPermission.apiId, + webapiId: createPermission.apiId, }) .execute() .then((result) => { @@ -95,15 +100,15 @@ export default abstract class ApiPermissionCommandHandler { /** * @description remove permission from api - * @param {DeleteApiPermissionCommand} deletePermission + * @param {DeleteWebapiPermissionCommand} deletePermission * @returns {Promise} */ - static async delete(deletePermission: DeleteApiPermissionCommand): Promise { + static async delete(deletePermission: DeleteWebapiPermissionCommand): Promise { return await dataSource .createQueryBuilder() .delete() - .from(apiPermission) - .where("apiId = :id", { id: deletePermission.apiId }) + .from(webapiPermission) + .where("webapiId = :id", { id: deletePermission.apiId }) .andWhere("permission = :permission", { permission: deletePermission.permission }) .execute() .then(() => {}) diff --git a/src/controller/admin/user/apiController.ts b/src/controller/admin/user/webapiController.ts similarity index 58% rename from src/controller/admin/user/apiController.ts rename to src/controller/admin/user/webapiController.ts index 1d64139..bcef24b 100644 --- a/src/controller/admin/user/apiController.ts +++ b/src/controller/admin/user/webapiController.ts @@ -1,12 +1,16 @@ import { Request, Response } from "express"; -import ApiService from "../../../service/user/apiService"; -import ApiFactory from "../../../factory/admin/user/api"; -import ApiPermissionService from "../../../service/user/apiPermissionService"; +import WebapiService from "../../../service/user/webapiService"; +import ApiFactory from "../../../factory/admin/user/webapi"; +import WebapiPermissionService from "../../../service/user/webapiPermissionService"; import PermissionHelper from "../../../helpers/permissionHelper"; -import { CreateApiCommand, DeleteApiCommand, UpdateApiCommand } from "../../../command/user/api/apiCommand"; -import ApiCommandHandler from "../../../command/user/api/apiCommandHandler"; -import { UpdateApiPermissionsCommand } from "../../../command/user/api/apiPermissionCommand"; -import ApiPermissionCommandHandler from "../../../command/user/api/apiPermissionCommandHandler"; +import { + CreateWebapiCommand, + DeleteWebapiCommand, + UpdateWebapiCommand, +} from "../../../command/user/webapi/webapiCommand"; +import WebapiCommandHandler from "../../../command/user/webapi/webapiCommandHandler"; +import { UpdateWebapiPermissionsCommand } from "../../../command/user/webapi/webapiPermissionCommand"; +import WebapiPermissionCommandHandler from "../../../command/user/webapi/webapiPermissionCommandHandler"; import { JWTHelper } from "../../../helpers/jwtHelper"; import { CLUB_NAME } from "../../../env.defaults"; import { StringHelper } from "../../../helpers/stringHelper"; @@ -17,8 +21,8 @@ import { StringHelper } from "../../../helpers/stringHelper"; * @param res {Response} Express res object * @returns {Promise<*>} */ -export async function getAllApis(req: Request, res: Response): Promise { - let apis = await ApiService.getAll(); +export async function getAllWebapis(req: Request, res: Response): Promise { + let apis = await WebapiService.getAll(); res.json(ApiFactory.mapToBase(apis)); } @@ -29,9 +33,9 @@ export async function getAllApis(req: Request, res: Response): Promise { * @param res {Response} Express res object * @returns {Promise<*>} */ -export async function getApiById(req: Request, res: Response): Promise { +export async function getWebapiById(req: Request, res: Response): Promise { const id = parseInt(req.params.id); - let api = await ApiService.getById(id); + let api = await WebapiService.getById(id); res.json(ApiFactory.mapToSingle(api)); } @@ -42,9 +46,9 @@ export async function getApiById(req: Request, res: Response): Promise { * @param res {Response} Express res object * @returns {Promise<*>} */ -export async function getApiTokenById(req: Request, res: Response): Promise { +export async function getWebapiTokenById(req: Request, res: Response): Promise { const id = parseInt(req.params.id); - let { token } = await ApiService.getTokenById(id); + let { token } = await WebapiService.getTokenById(id); res.send(token); } @@ -55,9 +59,9 @@ export async function getApiTokenById(req: Request, res: Response): Promise * @param res {Response} Express res object * @returns {Promise<*>} */ -export async function getApiPermissions(req: Request, res: Response): Promise { +export async function getWebapiPermissions(req: Request, res: Response): Promise { const id = parseInt(req.params.id); - let permissions = await ApiPermissionService.getByApi(id); + let permissions = await WebapiPermissionService.getByApi(id); res.json(PermissionHelper.convertToObject(permissions.map((p) => p.permission))); } @@ -68,7 +72,7 @@ export async function getApiPermissions(req: Request, res: Response): Promise} */ -export async function createApi(req: Request, res: Response): Promise { +export async function createWebapi(req: Request, res: Response): Promise { let title = req.body.title; let expiry = req.body.expiry; @@ -79,12 +83,12 @@ export async function createApi(req: Request, res: Response): Promise { aud: StringHelper.random(32), }); - let createApi: CreateApiCommand = { + let createApi: CreateWebapiCommand = { token: token, title: title, expiry: expiry, }; - await ApiCommandHandler.create(createApi); + await WebapiCommandHandler.create(createApi); res.sendStatus(204); } @@ -95,17 +99,17 @@ export async function createApi(req: Request, res: Response): Promise { * @param res {Response} Express res object * @returns {Promise<*>} */ -export async function updateApi(req: Request, res: Response): Promise { +export async function updateWebapi(req: Request, res: Response): Promise { const id = parseInt(req.params.id); let title = req.body.title; let expiry = req.body.expiry; - let updateApi: UpdateApiCommand = { + let updateApi: UpdateWebapiCommand = { id: id, title: title, expiry: expiry, }; - await ApiCommandHandler.update(updateApi); + await WebapiCommandHandler.update(updateApi); res.sendStatus(204); } @@ -116,17 +120,17 @@ export async function updateApi(req: Request, res: Response): Promise { * @param res {Response} Express res object * @returns {Promise<*>} */ -export async function updateApiPermissions(req: Request, res: Response): Promise { +export async function updateWebapiPermissions(req: Request, res: Response): Promise { const id = parseInt(req.params.id); let permissions = req.body.permissions; let permissionStrings = PermissionHelper.convertToStringArray(permissions); - let updateApiPermissions: UpdateApiPermissionsCommand = { + let updateApiPermissions: UpdateWebapiPermissionsCommand = { apiId: id, permissions: permissionStrings, }; - await ApiPermissionCommandHandler.updatePermissions(updateApiPermissions); + await WebapiPermissionCommandHandler.updatePermissions(updateApiPermissions); res.sendStatus(204); } @@ -137,13 +141,13 @@ export async function updateApiPermissions(req: Request, res: Response): Promise * @param res {Response} Express res object * @returns {Promise<*>} */ -export async function deleteApi(req: Request, res: Response): Promise { +export async function deleteWebapi(req: Request, res: Response): Promise { const id = parseInt(req.params.id); - let deleteApi: DeleteApiCommand = { + let deleteApi: DeleteWebapiCommand = { id: id, }; - await ApiCommandHandler.delete(deleteApi); + await WebapiCommandHandler.delete(deleteApi); res.sendStatus(204); } diff --git a/src/controller/apiController.ts b/src/controller/webapiController.ts similarity index 86% rename from src/controller/apiController.ts rename to src/controller/webapiController.ts index bb49a8c..33f765d 100644 --- a/src/controller/apiController.ts +++ b/src/controller/webapiController.ts @@ -8,7 +8,7 @@ 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 WebapiService from "../service/user/webapiService"; import ForbiddenRequestException from "../exceptions/forbiddenRequestException"; /** @@ -20,13 +20,13 @@ import ForbiddenRequestException from "../exceptions/forbiddenRequestException"; export async function getWebApiAccess(req: Request, res: Response): Promise { const bearer = req.headers.authorization?.split(" ")?.[1] ?? undefined; - let { expiry } = await ApiService.getByToken(bearer); + let { expiry } = await WebapiService.getByToken(bearer); if (new Date() > new Date(expiry)) { throw new ForbiddenRequestException("api token expired"); } - let accessToken = await JWTHelper.buildApiToken(bearer); + let accessToken = await JWTHelper.buildWebapiToken(bearer); res.json({ accessToken, diff --git a/src/data-source.ts b/src/data-source.ts index b3c0227..8ba9056 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -68,9 +68,9 @@ import { Memberlist1736079005086 } from "./migrations/1736079005086-memberlist"; import { ExtendViewValues1736084198860 } from "./migrations/1736084198860-extendViewValues"; import { FinishInternalIdTransfer1736505324488 } from "./migrations/1736505324488-finishInternalIdTransfer"; import { ProtocolPresenceExcuse1737287798828 } from "./migrations/1737287798828-protocolPresenceExcuse"; -import { api } from "./entity/user/api"; -import { apiPermission } from "./entity/user/api_permission"; -import { AddApiTokens1737453096674 } from "./migrations/1737453096674-addApiTokens"; +import { webapi } from "./entity/user/webapi"; +import { webapiPermission } from "./entity/user/webapi_permission"; +import { AddWebapiTokens1737453096674 } from "./migrations/1737453096674-addwebapiTokens"; const dataSource = new DataSource({ type: DB_TYPE as any, @@ -120,8 +120,8 @@ const dataSource = new DataSource({ memberExecutivePositionsView, memberQualificationsView, membershipView, - api, - apiPermission, + webapi, + webapiPermission, ], migrations: [ Initial1724317398939, @@ -151,7 +151,7 @@ const dataSource = new DataSource({ ExtendViewValues1736084198860, FinishInternalIdTransfer1736505324488, ProtocolPresenceExcuse1737287798828, - AddApiTokens1737453096674, + AddWebapiTokens1737453096674, ], migrationsRun: true, migrationsTransactionMode: "each", diff --git a/src/entity/user/api.ts b/src/entity/user/webapi.ts similarity index 72% rename from src/entity/user/api.ts rename to src/entity/user/webapi.ts index 3e21b78..e1c73a6 100644 --- a/src/entity/user/api.ts +++ b/src/entity/user/webapi.ts @@ -1,8 +1,8 @@ import { Column, CreateDateColumn, Entity, OneToMany, PrimaryColumn } from "typeorm"; -import { apiPermission } from "./api_permission"; +import { webapiPermission } from "./webapi_permission"; @Entity() -export class api { +export class webapi { @PrimaryColumn({ generated: "increment", type: "int" }) id: number; @@ -21,6 +21,6 @@ export class api { @Column({ type: "datetime", nullable: true }) expiry?: Date; - @OneToMany(() => apiPermission, (apiPermission) => apiPermission.api) - permissions: apiPermission[]; + @OneToMany(() => webapiPermission, (apiPermission) => apiPermission.webapi) + permissions: webapiPermission[]; } diff --git a/src/entity/user/api_permission.ts b/src/entity/user/webapi_permission.ts similarity index 73% rename from src/entity/user/api_permission.ts rename to src/entity/user/webapi_permission.ts index 4e7509f..988c7b9 100644 --- a/src/entity/user/api_permission.ts +++ b/src/entity/user/webapi_permission.ts @@ -1,19 +1,19 @@ import { Column, Entity, ManyToOne, OneToMany, PrimaryColumn } from "typeorm"; import { PermissionObject, PermissionString } from "../../type/permissionTypes"; -import { api } from "./api"; +import { webapi } from "./webapi"; @Entity() -export class apiPermission { +export class webapiPermission { @PrimaryColumn({ type: "int" }) - apiId: number; + webapiId: number; @PrimaryColumn({ type: "varchar", length: 255 }) permission: PermissionString; - @ManyToOne(() => api, { + @ManyToOne(() => webapi, { nullable: false, onDelete: "CASCADE", onUpdate: "RESTRICT", }) - api: api; + webapi: webapi; } diff --git a/src/factory/admin/user/api.ts b/src/factory/admin/user/webapi.ts similarity index 64% rename from src/factory/admin/user/api.ts rename to src/factory/admin/user/webapi.ts index 111be2a..3c0ede4 100644 --- a/src/factory/admin/user/api.ts +++ b/src/factory/admin/user/webapi.ts @@ -1,14 +1,14 @@ -import { api } from "../../../entity/user/api"; +import { webapi } from "../../../entity/user/webapi"; import PermissionHelper from "../../../helpers/permissionHelper"; -import { ApiViewModel } from "../../../viewmodel/admin/user/api.models"; +import { ApiViewModel } from "../../../viewmodel/admin/user/webapi.models"; export default abstract class ApiFactory { /** * @description map record to api - * @param {api} record + * @param {webapi} record * @returns {apiViewModel} */ - public static mapToSingle(record: api): ApiViewModel { + public static mapToSingle(record: webapi): ApiViewModel { return { id: record.id, permissions: PermissionHelper.convertToObject(record.permissions.map((e) => e.permission)), @@ -21,10 +21,10 @@ export default abstract class ApiFactory { /** * @description map records to api - * @param {Array} records + * @param {Array} records * @returns {Array} */ - public static mapToBase(records: Array): Array { + public static mapToBase(records: Array): Array { return records.map((r) => this.mapToSingle(r)); } } diff --git a/src/helpers/jwtHelper.ts b/src/helpers/jwtHelper.ts index 66beaab..af177e5 100644 --- a/src/helpers/jwtHelper.ts +++ b/src/helpers/jwtHelper.ts @@ -6,8 +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"; +import WebapiService from "../service/user/webapiService"; +import WebapiPermissionService from "../service/user/webapiPermissionService"; export abstract class JWTHelper { static validate(token: string): Promise { @@ -75,11 +75,11 @@ export abstract class JWTHelper { }); } - 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); + static async buildWebapiToken(token: string): Promise { + let { id, title } = await WebapiService.getByToken(token); + let webapiPermissions = await WebapiPermissionService.getByApi(id); + let webapiPermissionStrings = webapiPermissions.map((e) => e.permission); + let permissionObject = PermissionHelper.convertToObject(webapiPermissionStrings); let jwtData: JWTToken = { userId: id, @@ -97,7 +97,7 @@ export abstract class JWTHelper { return result; }) .catch((err) => { - throw new InternalException("Failed accessToken creation", err); + throw new InternalException("Failed webapi accessToken creation", err); }); } } diff --git a/src/middleware/preventWebApiAccess.ts b/src/middleware/preventWebApiAccess.ts index a7b10a5..3c3b7c6 100644 --- a/src/middleware/preventWebApiAccess.ts +++ b/src/middleware/preventWebApiAccess.ts @@ -1,7 +1,7 @@ import { Request, Response } from "express"; import ForbiddenRequestException from "../exceptions/forbiddenRequestException"; -export default async function preventApiAccess(req: Request, res: Response, next: Function) { +export default async function preventWebapiAccess(req: Request, res: Response, next: Function) { if (req.isWebApiRequest) { throw new ForbiddenRequestException("This route cannot be accessed via webapi"); } else { diff --git a/src/migrations/1737453096674-addApiTokens.ts b/src/migrations/1737453096674-addwebapiTokens.ts similarity index 67% rename from src/migrations/1737453096674-addApiTokens.ts rename to src/migrations/1737453096674-addwebapiTokens.ts index d547892..81b4e45 100644 --- a/src/migrations/1737453096674-addApiTokens.ts +++ b/src/migrations/1737453096674-addwebapiTokens.ts @@ -1,17 +1,17 @@ import { MigrationInterface, QueryRunner, Table, TableForeignKey } from "typeorm"; import { DB_TYPE } from "../env.defaults"; -export class AddApiTokens1737453096674 implements MigrationInterface { - name = "AddApiTokens1737453096674"; +export class AddWebapiTokens1737453096674 implements MigrationInterface { + name = "AddWebApiTokens1737453096674"; public async up(queryRunner: QueryRunner): Promise { const variableType_int = DB_TYPE == "mysql" ? "int" : "integer"; await queryRunner.createTable( new Table({ - name: "api", + name: "webapi", columns: [ - { name: "id", type: variableType_int, isPrimary: true, isNullable: false }, + { name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" }, { name: "token", type: "varchar", length: "255", isUnique: true, isNullable: false }, { name: "title", type: "varchar", length: "255", isNullable: false }, { name: "createdAt", type: "datetime", default: "CURRENT_TIMESTAMP(6)", isNullable: false }, @@ -24,9 +24,9 @@ export class AddApiTokens1737453096674 implements MigrationInterface { await queryRunner.createTable( new Table({ - name: "api_permission", + name: "webapi_permission", columns: [ - { name: "apiId", type: variableType_int, isPrimary: true, isNullable: false }, + { name: "webapiId", type: variableType_int, isPrimary: true, isNullable: false }, { name: "permission", type: "varchar", length: "255", isPrimary: true, isNullable: false }, ], }), @@ -34,11 +34,11 @@ export class AddApiTokens1737453096674 implements MigrationInterface { ); await queryRunner.createForeignKey( - "api_permission", + "webapi_permission", new TableForeignKey({ - columnNames: ["apiId"], + columnNames: ["webapiId"], referencedColumnNames: ["id"], - referencedTableName: "api", + referencedTableName: "webapi", onDelete: "CASCADE", onUpdate: "RESTRICT", }) @@ -46,10 +46,10 @@ export class AddApiTokens1737453096674 implements MigrationInterface { } public async down(queryRunner: QueryRunner): Promise { - const table = await queryRunner.getTable("api_permission"); - const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("apiToken") !== -1); - await queryRunner.dropForeignKey("api_permission", foreignKey); - await queryRunner.dropTable("api_permission"); - await queryRunner.dropTable("api"); + const table = await queryRunner.getTable("webapi_permission"); + const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("webapiId") !== -1); + await queryRunner.dropForeignKey("webapi_permission", foreignKey); + await queryRunner.dropTable("webapi_permission"); + await queryRunner.dropTable("webapi"); } } diff --git a/src/routes/admin/index.ts b/src/routes/admin/index.ts index 3662de9..9aa24ce 100644 --- a/src/routes/admin/index.ts +++ b/src/routes/admin/index.ts @@ -21,8 +21,8 @@ import newsletter from "./club/newsletter"; import role from "./user/role"; import user from "./user/user"; import invite from "./user/invite"; -import api from "./user/api"; -import preventApiAccess from "../../middleware/preventWebApiAccess"; +import api from "./user/webapi"; +import preventWebapiAccess from "../../middleware/preventWebApiAccess"; var router = express.Router({ mergeParams: true }); @@ -62,6 +62,6 @@ router.use("/newsletter", PermissionHelper.passCheckMiddleware("read", "club", " router.use("/role", PermissionHelper.passCheckMiddleware("read", "user", "role"), role); router.use("/user", PermissionHelper.passCheckMiddleware("read", "user", "user"), user); router.use("/invite", PermissionHelper.passCheckMiddleware("read", "user", "user"), invite); -router.use("/webapi", preventApiAccess, PermissionHelper.passCheckMiddleware("read", "user", "webapi"), api); +router.use("/webapi", preventWebapiAccess, PermissionHelper.passCheckMiddleware("read", "user", "webapi"), api); export default router; diff --git a/src/routes/admin/user/api.ts b/src/routes/admin/user/webapi.ts similarity index 69% rename from src/routes/admin/user/api.ts rename to src/routes/admin/user/webapi.ts index c5b3089..4e8feb9 100644 --- a/src/routes/admin/user/api.ts +++ b/src/routes/admin/user/webapi.ts @@ -1,34 +1,34 @@ import express, { Request, Response } from "express"; import PermissionHelper from "../../../helpers/permissionHelper"; import { - createApi, - deleteApi, - getAllApis, - getApiById, - getApiPermissions, - updateApi, - updateApiPermissions, -} from "../../../controller/admin/user/apiController"; + createWebapi, + deleteWebapi, + getAllWebapis, + getWebapiById, + getWebapiPermissions, + updateWebapi, + updateWebapiPermissions, +} from "../../../controller/admin/user/webapiController"; var router = express.Router({ mergeParams: true }); router.get("/", async (req: Request, res: Response) => { - await getAllApis(req, res); + await getAllWebapis(req, res); }); router.get("/:id", async (req: Request, res: Response) => { - await getApiById(req, res); + await getWebapiById(req, res); }); router.get("/:id/permissions", async (req: Request, res: Response) => { - await getApiPermissions(req, res); + await getWebapiPermissions(req, res); }); router.post( "/", PermissionHelper.passCheckMiddleware("create", "user", "webapi"), async (req: Request, res: Response) => { - await createApi(req, res); + await createWebapi(req, res); } ); @@ -36,7 +36,7 @@ router.patch( "/:id", PermissionHelper.passCheckMiddleware("update", "user", "webapi"), async (req: Request, res: Response) => { - await updateApi(req, res); + await updateWebapi(req, res); } ); @@ -44,7 +44,7 @@ router.patch( "/:id/permissions", PermissionHelper.passCheckMiddleware("admin", "user", "webapi"), async (req: Request, res: Response) => { - await updateApiPermissions(req, res); + await updateWebapiPermissions(req, res); } ); @@ -52,7 +52,7 @@ router.delete( "/:id", PermissionHelper.passCheckMiddleware("delete", "user", "webapi"), async (req: Request, res: Response) => { - await deleteApi(req, res); + await deleteWebapi(req, res); } ); diff --git a/src/routes/index.ts b/src/routes/index.ts index 06a1063..3509940 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -14,7 +14,7 @@ import auth from "./auth"; import admin from "./admin/index"; import user from "./user"; import detectPWA from "../middleware/detectPWA"; -import api from "./api"; +import api from "./webapi"; import authenticateAPI from "../middleware/authenticateAPI"; export default (app: Express) => { diff --git a/src/routes/api.ts b/src/routes/webapi.ts similarity index 78% rename from src/routes/api.ts rename to src/routes/webapi.ts index 0ded862..164802c 100644 --- a/src/routes/api.ts +++ b/src/routes/webapi.ts @@ -1,5 +1,5 @@ import express, { Request, Response } from "express"; -import { getWebApiAccess } from "../controller/apiController"; +import { getWebApiAccess } from "../controller/webapiController"; var router = express.Router({ mergeParams: true }); diff --git a/src/service/user/apiService.ts b/src/service/user/apiService.ts deleted file mode 100644 index b44b3ca..0000000 --- a/src/service/user/apiService.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { dataSource } from "../../data-source"; -import { api } from "../../entity/user/api"; -import InternalException from "../../exceptions/internalException"; - -export default abstract class ApiService { - /** - * @description get apis - * @returns {Promise>} - */ - static async getAll(): Promise> { - return await dataSource - .getRepository(api) - .createQueryBuilder("api") - .leftJoinAndSelect("api.permissions", "permissions") - .getMany() - .then((res) => { - return res; - }) - .catch((err) => { - throw new InternalException("apis not found", err); - }); - } - - /** - * @description get api by id - * @param id number - * @returns {Promise} - */ - static async getById(id: number): Promise { - return await dataSource - .getRepository(api) - .createQueryBuilder("api") - .leftJoinAndSelect("api.permissions", "permissions") - .where("api.id = :id", { id: id }) - .getOneOrFail() - .then((res) => { - return res; - }) - .catch((err) => { - throw new InternalException("api not found by id", err); - }); - } - - /** - * @description get api by token - * @param token string - * @returns {Promise} - */ - static async getByToken(token: string): Promise { - return await dataSource - .getRepository(api) - .createQueryBuilder("api") - .leftJoinAndSelect("api.permissions", "permissions") - .where("api.token = :token", { token: token }) - .getOneOrFail() - .then((res) => { - return res; - }) - .catch((err) => { - throw new InternalException("api not found by token", err); - }); - } - - /** - * @description get api by id - * @param id number - * @returns {Promise} - */ - static async getTokenById(id: number): Promise { - return await dataSource - .getRepository(api) - .createQueryBuilder("api") - .select("token") - .where("api.id = :id", { id: id }) - .getOneOrFail() - .then((res) => { - return res; - }) - .catch((err) => { - throw new InternalException("api token not found by id", err); - }); - } -} diff --git a/src/service/user/apiPermissionService.ts b/src/service/user/webapiPermissionService.ts similarity index 52% rename from src/service/user/apiPermissionService.ts rename to src/service/user/webapiPermissionService.ts index 13f0ed2..c50895c 100644 --- a/src/service/user/apiPermissionService.ts +++ b/src/service/user/webapiPermissionService.ts @@ -1,18 +1,18 @@ import { dataSource } from "../../data-source"; -import { apiPermission } from "../../entity/user/api_permission"; +import { webapiPermission } from "../../entity/user/webapi_permission"; import InternalException from "../../exceptions/internalException"; -export default abstract class ApiPermissionService { +export default abstract class WebapiPermissionService { /** * @description get permission by api - * @param apiId number - * @returns {Promise>} + * @param webapiId number + * @returns {Promise>} */ - static async getByApi(apiId: number): Promise> { + static async getByApi(webapiId: number): Promise> { return await dataSource - .getRepository(apiPermission) + .getRepository(webapiPermission) .createQueryBuilder("api_permission") - .where("api_permission.apiId = :apiId", { apiId: apiId }) + .where("api_permission.apiId = :apiId", { apiId: webapiId }) .getMany() .then((res) => { return res; diff --git a/src/service/user/webapiService.ts b/src/service/user/webapiService.ts new file mode 100644 index 0000000..264ef7a --- /dev/null +++ b/src/service/user/webapiService.ts @@ -0,0 +1,83 @@ +import { dataSource } from "../../data-source"; +import { webapi } from "../../entity/user/webapi"; +import InternalException from "../../exceptions/internalException"; + +export default abstract class WebapiService { + /** + * @description get apis + * @returns {Promise>} + */ + static async getAll(): Promise> { + return await dataSource + .getRepository(webapi) + .createQueryBuilder("webapi") + .leftJoinAndSelect("webapi.permissions", "permissions") + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("webapis not found", err); + }); + } + + /** + * @description get api by id + * @param id number + * @returns {Promise} + */ + static async getById(id: number): Promise { + return await dataSource + .getRepository(webapi) + .createQueryBuilder("webapi") + .leftJoinAndSelect("webapi.permissions", "permissions") + .where("webapi.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("webapi not found by id", err); + }); + } + + /** + * @description get api by token + * @param token string + * @returns {Promise} + */ + static async getByToken(token: string): Promise { + return await dataSource + .getRepository(webapi) + .createQueryBuilder("webapi") + .leftJoinAndSelect("webapi.permissions", "permissions") + .where("webapi.token = :token", { token: token }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("webapi not found by token", err); + }); + } + + /** + * @description get api by id + * @param id number + * @returns {Promise} + */ + static async getTokenById(id: number): Promise { + return await dataSource + .getRepository(webapi) + .createQueryBuilder("webapi") + .select("token") + .where("webapi.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("webapi token not found by id", err); + }); + } +} diff --git a/src/viewmodel/admin/user/api.models.ts b/src/viewmodel/admin/user/webapi.models.ts similarity index 100% rename from src/viewmodel/admin/user/api.models.ts rename to src/viewmodel/admin/user/webapi.models.ts From a165231c475f3dfcb5c5fc3eb341d28ae9ce13f3 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Wed, 22 Jan 2025 11:57:19 +0100 Subject: [PATCH 8/8] jwt gen & rename fixes --- .../club/member/memberCommandHandler.ts | 12 ++++-- src/command/user/webapi/webapiCommand.ts | 4 ++ .../user/webapi/webapiCommandHandler.ts | 37 +++++++++++++++++-- .../user/webapi/webapiPermissionCommand.ts | 6 +-- .../webapi/webapiPermissionCommandHandler.ts | 12 +++--- src/controller/admin/user/webapiController.ts | 20 +++++----- src/controller/webapiController.ts | 9 +++-- src/entity/user/webapi.ts | 6 +-- src/helpers/jwtHelper.ts | 17 +++++++-- .../1737453096674-addwebapiTokens.ts | 6 +-- src/routes/admin/user/webapi.ts | 5 +++ src/service/user/webapiPermissionService.ts | 6 +-- src/service/user/webapiService.ts | 2 +- 13 files changed, 101 insertions(+), 41 deletions(-) diff --git a/src/command/club/member/memberCommandHandler.ts b/src/command/club/member/memberCommandHandler.ts index d0fd59a..2bea5b5 100644 --- a/src/command/club/member/memberCommandHandler.ts +++ b/src/command/club/member/memberCommandHandler.ts @@ -33,7 +33,10 @@ export default abstract class MemberCommandHandler { return result.identifiers[0].id; }) .catch((err) => { - throw new InternalException("Failed creating member", err); + throw new InternalException( + `Failed creating member${err.code.includes("ER_DUP_ENTRY") ? " due to duplicate entry for column" : ""}`, + err + ); }); } @@ -58,7 +61,10 @@ export default abstract class MemberCommandHandler { .execute() .then(() => {}) .catch((err) => { - throw new InternalException("Failed updating member", err); + throw new InternalException( + `Failed updating member${err.code.includes("ER_DUP_ENTRY") ? " due to duplicate entry for column" : ""}`, + err + ); }); } @@ -83,7 +89,7 @@ export default abstract class MemberCommandHandler { .execute() .then(() => {}) .catch((err) => { - throw new InternalException("Failed updating member", err); + throw new InternalException(`Failed updating member`, err); }); } diff --git a/src/command/user/webapi/webapiCommand.ts b/src/command/user/webapi/webapiCommand.ts index 2ec3586..4415747 100644 --- a/src/command/user/webapi/webapiCommand.ts +++ b/src/command/user/webapi/webapiCommand.ts @@ -10,6 +10,10 @@ export interface UpdateWebapiCommand { expiry?: Date; } +export interface UpdateLastUsageWebapiCommand { + id: number; +} + export interface DeleteWebapiCommand { id: number; } diff --git a/src/command/user/webapi/webapiCommandHandler.ts b/src/command/user/webapi/webapiCommandHandler.ts index 71689a6..b49f7a6 100644 --- a/src/command/user/webapi/webapiCommandHandler.ts +++ b/src/command/user/webapi/webapiCommandHandler.ts @@ -1,7 +1,12 @@ import { dataSource } from "../../../data-source"; import { webapi } from "../../../entity/user/webapi"; import InternalException from "../../../exceptions/internalException"; -import { CreateWebapiCommand, DeleteWebapiCommand, UpdateWebapiCommand } from "./webapiCommand"; +import { + CreateWebapiCommand, + DeleteWebapiCommand, + UpdateLastUsageWebapiCommand, + UpdateWebapiCommand, +} from "./webapiCommand"; export default abstract class WebapiCommandHandler { /** @@ -24,7 +29,10 @@ export default abstract class WebapiCommandHandler { return result.identifiers[0].token; }) .catch((err) => { - throw new InternalException("Failed creating api", err); + throw new InternalException( + `Failed creating api${err.code.includes("ER_DUP_ENTRY") ? " due to duplicate entry for column" : ""}`, + err + ); }); } @@ -45,7 +53,30 @@ export default abstract class WebapiCommandHandler { .execute() .then(() => {}) .catch((err) => { - throw new InternalException("Failed updating api", err); + throw new InternalException( + `Failed updating api${err.code.includes("ER_DUP_ENTRY") ? " due to duplicate entry for column" : ""}`, + err + ); + }); + } + + /** + * @description update api usage + * @param {UpdateLastUsageWebapiCommand} updateWebapi + * @returns {Promise} + */ + static async updateUsage(updateWebapi: UpdateLastUsageWebapiCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(webapi) + .set({ + lastUsage: new Date(), + }) + .where("id = :id", { id: updateWebapi.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new InternalException(`Failed updating api last usage`, err); }); } diff --git a/src/command/user/webapi/webapiPermissionCommand.ts b/src/command/user/webapi/webapiPermissionCommand.ts index 6f192d1..3942115 100644 --- a/src/command/user/webapi/webapiPermissionCommand.ts +++ b/src/command/user/webapi/webapiPermissionCommand.ts @@ -2,15 +2,15 @@ import { PermissionString } from "../../../type/permissionTypes"; export interface CreateWebapiPermissionCommand { permission: PermissionString; - apiId: number; + webapiId: number; } export interface DeleteWebapiPermissionCommand { permission: PermissionString; - apiId: number; + webapiId: number; } export interface UpdateWebapiPermissionsCommand { - apiId: number; + webapiId: number; permissions: Array; } diff --git a/src/command/user/webapi/webapiPermissionCommandHandler.ts b/src/command/user/webapi/webapiPermissionCommandHandler.ts index 6d30bc0..74ea514 100644 --- a/src/command/user/webapi/webapiPermissionCommandHandler.ts +++ b/src/command/user/webapi/webapiPermissionCommandHandler.ts @@ -19,7 +19,7 @@ export default abstract class WebapiPermissionCommandHandler { * @returns {Promise} */ static async updatePermissions(updateWebapiPermissions: UpdateWebapiPermissionsCommand): Promise { - let currentPermissions = (await WebapiPermissionService.getByApi(updateWebapiPermissions.apiId)).map( + let currentPermissions = (await WebapiPermissionService.getByApi(updateWebapiPermissions.webapiId)).map( (r) => r.permission ); return await dataSource.manager @@ -30,10 +30,10 @@ export default abstract class WebapiPermissionCommandHandler { updateWebapiPermissions.permissions ); if (newPermissions.length != 0) { - await this.updatePermissionsAdd(manager, updateWebapiPermissions.apiId, newPermissions); + await this.updatePermissionsAdd(manager, updateWebapiPermissions.webapiId, newPermissions); } if (removePermissions.length != 0) { - await this.updatePermissionsRemove(manager, updateWebapiPermissions.apiId, removePermissions); + await this.updatePermissionsRemove(manager, updateWebapiPermissions.webapiId, removePermissions); } }) .then(() => {}) @@ -54,7 +54,7 @@ export default abstract class WebapiPermissionCommandHandler { .values( permissions.map((p) => ({ permission: p, - apiId: webapiId, + webapiId: webapiId, })) ) .orIgnore() @@ -87,7 +87,7 @@ export default abstract class WebapiPermissionCommandHandler { .into(webapiPermission) .values({ permission: createPermission.permission, - webapiId: createPermission.apiId, + webapiId: createPermission.webapiId, }) .execute() .then((result) => { @@ -108,7 +108,7 @@ export default abstract class WebapiPermissionCommandHandler { .createQueryBuilder() .delete() .from(webapiPermission) - .where("webapiId = :id", { id: deletePermission.apiId }) + .where("webapiId = :id", { id: deletePermission.webapiId }) .andWhere("permission = :permission", { permission: deletePermission.permission }) .execute() .then(() => {}) diff --git a/src/controller/admin/user/webapiController.ts b/src/controller/admin/user/webapiController.ts index bcef24b..d5390ff 100644 --- a/src/controller/admin/user/webapiController.ts +++ b/src/controller/admin/user/webapiController.ts @@ -74,14 +74,16 @@ export async function getWebapiPermissions(req: Request, res: Response): Promise */ export async function createWebapi(req: Request, res: Response): Promise { let title = req.body.title; - let expiry = req.body.expiry; + let expiry = req.body.expiry || null; - let token = await JWTHelper.create({ - iss: CLUB_NAME, - sub: "api_token_retrieve", - iat: new Date().toISOString(), - aud: StringHelper.random(32), - }); + let token = await JWTHelper.create( + { + iss: CLUB_NAME, + sub: "api_token_retrieve", + aud: StringHelper.random(32), + }, + { useExpiration: false } + ); let createApi: CreateWebapiCommand = { token: token, @@ -102,7 +104,7 @@ export async function createWebapi(req: Request, res: Response): Promise { export async function updateWebapi(req: Request, res: Response): Promise { const id = parseInt(req.params.id); let title = req.body.title; - let expiry = req.body.expiry; + let expiry = req.body.expiry || null; let updateApi: UpdateWebapiCommand = { id: id, @@ -127,7 +129,7 @@ export async function updateWebapiPermissions(req: Request, res: Response): Prom let permissionStrings = PermissionHelper.convertToStringArray(permissions); let updateApiPermissions: UpdateWebapiPermissionsCommand = { - apiId: id, + webapiId: id, permissions: permissionStrings, }; await WebapiPermissionCommandHandler.updatePermissions(updateApiPermissions); diff --git a/src/controller/webapiController.ts b/src/controller/webapiController.ts index 33f765d..7af86dd 100644 --- a/src/controller/webapiController.ts +++ b/src/controller/webapiController.ts @@ -10,6 +10,7 @@ import UnauthorizedRequestException from "../exceptions/unauthorizedRequestExcep import RefreshService from "../service/refreshService"; import WebapiService from "../service/user/webapiService"; import ForbiddenRequestException from "../exceptions/forbiddenRequestException"; +import WebapiCommandHandler from "../command/user/webapi/webapiCommandHandler"; /** * @description Check authentication status by token @@ -20,13 +21,15 @@ import ForbiddenRequestException from "../exceptions/forbiddenRequestException"; export async function getWebApiAccess(req: Request, res: Response): Promise { const bearer = req.headers.authorization?.split(" ")?.[1] ?? undefined; - let { expiry } = await WebapiService.getByToken(bearer); + let { id, expiry } = await WebapiService.getByToken(bearer); - if (new Date() > new Date(expiry)) { + if (expiry != null && new Date() > new Date(expiry)) { throw new ForbiddenRequestException("api token expired"); } - let accessToken = await JWTHelper.buildWebapiToken(bearer); + await WebapiCommandHandler.updateUsage({ id }); + + let accessToken = await JWTHelper.buildWebapiToken(bearer, expiry); res.json({ accessToken, diff --git a/src/entity/user/webapi.ts b/src/entity/user/webapi.ts index e1c73a6..220db8c 100644 --- a/src/entity/user/webapi.ts +++ b/src/entity/user/webapi.ts @@ -6,10 +6,10 @@ export class webapi { @PrimaryColumn({ generated: "increment", type: "int" }) id: number; - @Column({ type: "varchar", length: 255, unique: true, select: false }) + @Column({ type: "text", unique: true, select: false }) token: string; - @Column({ type: "varchar", length: 255 }) + @Column({ type: "varchar", length: 255, unique: true }) title: string; @CreateDateColumn() @@ -18,7 +18,7 @@ export class webapi { @Column({ type: "datetime", nullable: true }) lastUsage?: Date; - @Column({ type: "datetime", nullable: true }) + @Column({ type: "date", nullable: true }) expiry?: Date; @OneToMany(() => webapiPermission, (apiPermission) => apiPermission.webapi) diff --git a/src/helpers/jwtHelper.ts b/src/helpers/jwtHelper.ts index af177e5..791af2a 100644 --- a/src/helpers/jwtHelper.ts +++ b/src/helpers/jwtHelper.ts @@ -8,6 +8,7 @@ import UserService from "../service/user/userService"; import PermissionHelper from "./permissionHelper"; import WebapiService from "../service/user/webapiService"; import WebapiPermissionService from "../service/user/webapiPermissionService"; +import ms from "ms"; export abstract class JWTHelper { static validate(token: string): Promise { @@ -19,13 +20,16 @@ export abstract class JWTHelper { }); } - static create(data: JWTData): Promise { + static create( + data: JWTData, + { expOverwrite, useExpiration }: { expOverwrite?: number; useExpiration?: boolean } = { useExpiration: true } + ): Promise { return new Promise((resolve, reject) => { jwt.sign( data, JWT_SECRET, { - expiresIn: JWT_EXPIRATION, + ...(useExpiration ?? true ? { expiresIn: expOverwrite ?? JWT_EXPIRATION } : {}), }, (err, token) => { if (err) reject(err.message); @@ -75,7 +79,7 @@ export abstract class JWTHelper { }); } - static async buildWebapiToken(token: string): Promise { + static async buildWebapiToken(token: string, expiration?: Date): Promise { let { id, title } = await WebapiService.getByToken(token); let webapiPermissions = await WebapiPermissionService.getByApi(id); let webapiPermissionStrings = webapiPermissions.map((e) => e.permission); @@ -92,7 +96,12 @@ export abstract class JWTHelper { sub: "webapi_access_token", }; - return await JWTHelper.create(jwtData) + let overwriteExpiration = + ms(JWT_EXPIRATION) < new Date().getTime() - new Date(expiration).getTime() + ? null + : Date.now() - new Date(expiration).getTime(); + + return await JWTHelper.create(jwtData, { expOverwrite: overwriteExpiration, useExpiration: true }) .then((result) => { return result; }) diff --git a/src/migrations/1737453096674-addwebapiTokens.ts b/src/migrations/1737453096674-addwebapiTokens.ts index 81b4e45..55f37fb 100644 --- a/src/migrations/1737453096674-addwebapiTokens.ts +++ b/src/migrations/1737453096674-addwebapiTokens.ts @@ -12,11 +12,11 @@ export class AddWebapiTokens1737453096674 implements MigrationInterface { name: "webapi", columns: [ { name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" }, - { name: "token", type: "varchar", length: "255", isUnique: true, isNullable: false }, - { name: "title", type: "varchar", length: "255", isNullable: false }, + { name: "token", type: "text", isUnique: true, isNullable: false }, + { name: "title", type: "varchar", isUnique: true, length: "255", isNullable: false }, { name: "createdAt", type: "datetime", default: "CURRENT_TIMESTAMP(6)", isNullable: false }, { name: "lastUsage", type: "datetime", isNullable: true, default: null }, - { name: "expiry", type: "datetime", isNullable: true, default: null }, + { name: "expiry", type: "date", isNullable: true, default: null }, ], }), true diff --git a/src/routes/admin/user/webapi.ts b/src/routes/admin/user/webapi.ts index 4e8feb9..0d0e7ab 100644 --- a/src/routes/admin/user/webapi.ts +++ b/src/routes/admin/user/webapi.ts @@ -6,6 +6,7 @@ import { getAllWebapis, getWebapiById, getWebapiPermissions, + getWebapiTokenById, updateWebapi, updateWebapiPermissions, } from "../../../controller/admin/user/webapiController"; @@ -20,6 +21,10 @@ router.get("/:id", async (req: Request, res: Response) => { await getWebapiById(req, res); }); +router.get("/:id/token", async (req: Request, res: Response) => { + await getWebapiTokenById(req, res); +}); + router.get("/:id/permissions", async (req: Request, res: Response) => { await getWebapiPermissions(req, res); }); diff --git a/src/service/user/webapiPermissionService.ts b/src/service/user/webapiPermissionService.ts index c50895c..a210353 100644 --- a/src/service/user/webapiPermissionService.ts +++ b/src/service/user/webapiPermissionService.ts @@ -11,14 +11,14 @@ export default abstract class WebapiPermissionService { static async getByApi(webapiId: number): Promise> { return await dataSource .getRepository(webapiPermission) - .createQueryBuilder("api_permission") - .where("api_permission.apiId = :apiId", { apiId: webapiId }) + .createQueryBuilder("webapi_permission") + .where("webapi_permission.webapiId = :webapiId", { webapiId: webapiId }) .getMany() .then((res) => { return res; }) .catch((err) => { - throw new InternalException("api permissions not found by api", err); + throw new InternalException("webapi permissions not found by api", err); }); } } diff --git a/src/service/user/webapiService.ts b/src/service/user/webapiService.ts index 264ef7a..0faa51a 100644 --- a/src/service/user/webapiService.ts +++ b/src/service/user/webapiService.ts @@ -70,7 +70,7 @@ export default abstract class WebapiService { return await dataSource .getRepository(webapi) .createQueryBuilder("webapi") - .select("token") + .select("webapi.token") .where("webapi.id = :id", { id: id }) .getOneOrFail() .then((res) => {