jwt gen & rename fixes

This commit is contained in:
Julian Krauser 2025-01-22 11:57:19 +01:00
parent 313785b4ac
commit a165231c47
13 changed files with 101 additions and 41 deletions

View file

@ -33,7 +33,10 @@ export default abstract class MemberCommandHandler {
return result.identifiers[0].id; return result.identifiers[0].id;
}) })
.catch((err) => { .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() .execute()
.then(() => {}) .then(() => {})
.catch((err) => { .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() .execute()
.then(() => {}) .then(() => {})
.catch((err) => { .catch((err) => {
throw new InternalException("Failed updating member", err); throw new InternalException(`Failed updating member`, err);
}); });
} }

View file

@ -10,6 +10,10 @@ export interface UpdateWebapiCommand {
expiry?: Date; expiry?: Date;
} }
export interface UpdateLastUsageWebapiCommand {
id: number;
}
export interface DeleteWebapiCommand { export interface DeleteWebapiCommand {
id: number; id: number;
} }

View file

@ -1,7 +1,12 @@
import { dataSource } from "../../../data-source"; import { dataSource } from "../../../data-source";
import { webapi } from "../../../entity/user/webapi"; import { webapi } from "../../../entity/user/webapi";
import InternalException from "../../../exceptions/internalException"; import InternalException from "../../../exceptions/internalException";
import { CreateWebapiCommand, DeleteWebapiCommand, UpdateWebapiCommand } from "./webapiCommand"; import {
CreateWebapiCommand,
DeleteWebapiCommand,
UpdateLastUsageWebapiCommand,
UpdateWebapiCommand,
} from "./webapiCommand";
export default abstract class WebapiCommandHandler { export default abstract class WebapiCommandHandler {
/** /**
@ -24,7 +29,10 @@ export default abstract class WebapiCommandHandler {
return result.identifiers[0].token; return result.identifiers[0].token;
}) })
.catch((err) => { .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() .execute()
.then(() => {}) .then(() => {})
.catch((err) => { .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<void>}
*/
static async updateUsage(updateWebapi: UpdateLastUsageWebapiCommand): Promise<void> {
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);
}); });
} }

View file

@ -2,15 +2,15 @@ import { PermissionString } from "../../../type/permissionTypes";
export interface CreateWebapiPermissionCommand { export interface CreateWebapiPermissionCommand {
permission: PermissionString; permission: PermissionString;
apiId: number; webapiId: number;
} }
export interface DeleteWebapiPermissionCommand { export interface DeleteWebapiPermissionCommand {
permission: PermissionString; permission: PermissionString;
apiId: number; webapiId: number;
} }
export interface UpdateWebapiPermissionsCommand { export interface UpdateWebapiPermissionsCommand {
apiId: number; webapiId: number;
permissions: Array<PermissionString>; permissions: Array<PermissionString>;
} }

View file

@ -19,7 +19,7 @@ export default abstract class WebapiPermissionCommandHandler {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
static async updatePermissions(updateWebapiPermissions: UpdateWebapiPermissionsCommand): Promise<void> { static async updatePermissions(updateWebapiPermissions: UpdateWebapiPermissionsCommand): Promise<void> {
let currentPermissions = (await WebapiPermissionService.getByApi(updateWebapiPermissions.apiId)).map( let currentPermissions = (await WebapiPermissionService.getByApi(updateWebapiPermissions.webapiId)).map(
(r) => r.permission (r) => r.permission
); );
return await dataSource.manager return await dataSource.manager
@ -30,10 +30,10 @@ export default abstract class WebapiPermissionCommandHandler {
updateWebapiPermissions.permissions updateWebapiPermissions.permissions
); );
if (newPermissions.length != 0) { if (newPermissions.length != 0) {
await this.updatePermissionsAdd(manager, updateWebapiPermissions.apiId, newPermissions); await this.updatePermissionsAdd(manager, updateWebapiPermissions.webapiId, newPermissions);
} }
if (removePermissions.length != 0) { if (removePermissions.length != 0) {
await this.updatePermissionsRemove(manager, updateWebapiPermissions.apiId, removePermissions); await this.updatePermissionsRemove(manager, updateWebapiPermissions.webapiId, removePermissions);
} }
}) })
.then(() => {}) .then(() => {})
@ -54,7 +54,7 @@ export default abstract class WebapiPermissionCommandHandler {
.values( .values(
permissions.map((p) => ({ permissions.map((p) => ({
permission: p, permission: p,
apiId: webapiId, webapiId: webapiId,
})) }))
) )
.orIgnore() .orIgnore()
@ -87,7 +87,7 @@ export default abstract class WebapiPermissionCommandHandler {
.into(webapiPermission) .into(webapiPermission)
.values({ .values({
permission: createPermission.permission, permission: createPermission.permission,
webapiId: createPermission.apiId, webapiId: createPermission.webapiId,
}) })
.execute() .execute()
.then((result) => { .then((result) => {
@ -108,7 +108,7 @@ export default abstract class WebapiPermissionCommandHandler {
.createQueryBuilder() .createQueryBuilder()
.delete() .delete()
.from(webapiPermission) .from(webapiPermission)
.where("webapiId = :id", { id: deletePermission.apiId }) .where("webapiId = :id", { id: deletePermission.webapiId })
.andWhere("permission = :permission", { permission: deletePermission.permission }) .andWhere("permission = :permission", { permission: deletePermission.permission })
.execute() .execute()
.then(() => {}) .then(() => {})

View file

@ -74,14 +74,16 @@ export async function getWebapiPermissions(req: Request, res: Response): Promise
*/ */
export async function createWebapi(req: Request, res: Response): Promise<any> { export async function createWebapi(req: Request, res: Response): Promise<any> {
let title = req.body.title; let title = req.body.title;
let expiry = req.body.expiry; let expiry = req.body.expiry || null;
let token = await JWTHelper.create({ let token = await JWTHelper.create(
{
iss: CLUB_NAME, iss: CLUB_NAME,
sub: "api_token_retrieve", sub: "api_token_retrieve",
iat: new Date().toISOString(),
aud: StringHelper.random(32), aud: StringHelper.random(32),
}); },
{ useExpiration: false }
);
let createApi: CreateWebapiCommand = { let createApi: CreateWebapiCommand = {
token: token, token: token,
@ -102,7 +104,7 @@ export async function createWebapi(req: Request, res: Response): Promise<any> {
export async function updateWebapi(req: Request, res: Response): Promise<any> { export async function updateWebapi(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id); const id = parseInt(req.params.id);
let title = req.body.title; let title = req.body.title;
let expiry = req.body.expiry; let expiry = req.body.expiry || null;
let updateApi: UpdateWebapiCommand = { let updateApi: UpdateWebapiCommand = {
id: id, id: id,
@ -127,7 +129,7 @@ export async function updateWebapiPermissions(req: Request, res: Response): Prom
let permissionStrings = PermissionHelper.convertToStringArray(permissions); let permissionStrings = PermissionHelper.convertToStringArray(permissions);
let updateApiPermissions: UpdateWebapiPermissionsCommand = { let updateApiPermissions: UpdateWebapiPermissionsCommand = {
apiId: id, webapiId: id,
permissions: permissionStrings, permissions: permissionStrings,
}; };
await WebapiPermissionCommandHandler.updatePermissions(updateApiPermissions); await WebapiPermissionCommandHandler.updatePermissions(updateApiPermissions);

View file

@ -10,6 +10,7 @@ import UnauthorizedRequestException from "../exceptions/unauthorizedRequestExcep
import RefreshService from "../service/refreshService"; import RefreshService from "../service/refreshService";
import WebapiService from "../service/user/webapiService"; import WebapiService from "../service/user/webapiService";
import ForbiddenRequestException from "../exceptions/forbiddenRequestException"; import ForbiddenRequestException from "../exceptions/forbiddenRequestException";
import WebapiCommandHandler from "../command/user/webapi/webapiCommandHandler";
/** /**
* @description Check authentication status by token * @description Check authentication status by token
@ -20,13 +21,15 @@ import ForbiddenRequestException from "../exceptions/forbiddenRequestException";
export async function getWebApiAccess(req: Request, res: Response): Promise<any> { export async function getWebApiAccess(req: Request, res: Response): Promise<any> {
const bearer = req.headers.authorization?.split(" ")?.[1] ?? undefined; 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"); 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({ res.json({
accessToken, accessToken,

View file

@ -6,10 +6,10 @@ export class webapi {
@PrimaryColumn({ generated: "increment", type: "int" }) @PrimaryColumn({ generated: "increment", type: "int" })
id: number; id: number;
@Column({ type: "varchar", length: 255, unique: true, select: false }) @Column({ type: "text", unique: true, select: false })
token: string; token: string;
@Column({ type: "varchar", length: 255 }) @Column({ type: "varchar", length: 255, unique: true })
title: string; title: string;
@CreateDateColumn() @CreateDateColumn()
@ -18,7 +18,7 @@ export class webapi {
@Column({ type: "datetime", nullable: true }) @Column({ type: "datetime", nullable: true })
lastUsage?: Date; lastUsage?: Date;
@Column({ type: "datetime", nullable: true }) @Column({ type: "date", nullable: true })
expiry?: Date; expiry?: Date;
@OneToMany(() => webapiPermission, (apiPermission) => apiPermission.webapi) @OneToMany(() => webapiPermission, (apiPermission) => apiPermission.webapi)

View file

@ -8,6 +8,7 @@ import UserService from "../service/user/userService";
import PermissionHelper from "./permissionHelper"; import PermissionHelper from "./permissionHelper";
import WebapiService from "../service/user/webapiService"; import WebapiService from "../service/user/webapiService";
import WebapiPermissionService from "../service/user/webapiPermissionService"; import WebapiPermissionService from "../service/user/webapiPermissionService";
import ms from "ms";
export abstract class JWTHelper { export abstract class JWTHelper {
static validate(token: string): Promise<string | jwt.JwtPayload> { static validate(token: string): Promise<string | jwt.JwtPayload> {
@ -19,13 +20,16 @@ export abstract class JWTHelper {
}); });
} }
static create(data: JWTData): Promise<string> { static create(
data: JWTData,
{ expOverwrite, useExpiration }: { expOverwrite?: number; useExpiration?: boolean } = { useExpiration: true }
): Promise<string> {
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
jwt.sign( jwt.sign(
data, data,
JWT_SECRET, JWT_SECRET,
{ {
expiresIn: JWT_EXPIRATION, ...(useExpiration ?? true ? { expiresIn: expOverwrite ?? JWT_EXPIRATION } : {}),
}, },
(err, token) => { (err, token) => {
if (err) reject(err.message); if (err) reject(err.message);
@ -75,7 +79,7 @@ export abstract class JWTHelper {
}); });
} }
static async buildWebapiToken(token: string): Promise<string> { static async buildWebapiToken(token: string, expiration?: Date): Promise<string> {
let { id, title } = await WebapiService.getByToken(token); let { id, title } = await WebapiService.getByToken(token);
let webapiPermissions = await WebapiPermissionService.getByApi(id); let webapiPermissions = await WebapiPermissionService.getByApi(id);
let webapiPermissionStrings = webapiPermissions.map((e) => e.permission); let webapiPermissionStrings = webapiPermissions.map((e) => e.permission);
@ -92,7 +96,12 @@ export abstract class JWTHelper {
sub: "webapi_access_token", 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) => { .then((result) => {
return result; return result;
}) })

View file

@ -12,11 +12,11 @@ export class AddWebapiTokens1737453096674 implements MigrationInterface {
name: "webapi", name: "webapi",
columns: [ columns: [
{ name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" }, { name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" },
{ name: "token", type: "varchar", length: "255", isUnique: true, isNullable: false }, { name: "token", type: "text", isUnique: true, isNullable: false },
{ name: "title", type: "varchar", length: "255", isNullable: false }, { name: "title", type: "varchar", isUnique: true, length: "255", isNullable: false },
{ name: "createdAt", type: "datetime", default: "CURRENT_TIMESTAMP(6)", isNullable: false }, { name: "createdAt", type: "datetime", default: "CURRENT_TIMESTAMP(6)", isNullable: false },
{ name: "lastUsage", type: "datetime", isNullable: true, default: null }, { 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 true

View file

@ -6,6 +6,7 @@ import {
getAllWebapis, getAllWebapis,
getWebapiById, getWebapiById,
getWebapiPermissions, getWebapiPermissions,
getWebapiTokenById,
updateWebapi, updateWebapi,
updateWebapiPermissions, updateWebapiPermissions,
} from "../../../controller/admin/user/webapiController"; } from "../../../controller/admin/user/webapiController";
@ -20,6 +21,10 @@ router.get("/:id", async (req: Request, res: Response) => {
await getWebapiById(req, res); 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) => { router.get("/:id/permissions", async (req: Request, res: Response) => {
await getWebapiPermissions(req, res); await getWebapiPermissions(req, res);
}); });

View file

@ -11,14 +11,14 @@ export default abstract class WebapiPermissionService {
static async getByApi(webapiId: number): Promise<Array<webapiPermission>> { static async getByApi(webapiId: number): Promise<Array<webapiPermission>> {
return await dataSource return await dataSource
.getRepository(webapiPermission) .getRepository(webapiPermission)
.createQueryBuilder("api_permission") .createQueryBuilder("webapi_permission")
.where("api_permission.apiId = :apiId", { apiId: webapiId }) .where("webapi_permission.webapiId = :webapiId", { webapiId: webapiId })
.getMany() .getMany()
.then((res) => { .then((res) => {
return res; return res;
}) })
.catch((err) => { .catch((err) => {
throw new InternalException("api permissions not found by api", err); throw new InternalException("webapi permissions not found by api", err);
}); });
} }
} }

View file

@ -70,7 +70,7 @@ export default abstract class WebapiService {
return await dataSource return await dataSource
.getRepository(webapi) .getRepository(webapi)
.createQueryBuilder("webapi") .createQueryBuilder("webapi")
.select("token") .select("webapi.token")
.where("webapi.id = :id", { id: id }) .where("webapi.id = :id", { id: id })
.getOneOrFail() .getOneOrFail()
.then((res) => { .then((res) => {