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;
})
.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);
});
}

View file

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

View file

@ -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<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 {
permission: PermissionString;
apiId: number;
webapiId: number;
}
export interface DeleteWebapiPermissionCommand {
permission: PermissionString;
apiId: number;
webapiId: number;
}
export interface UpdateWebapiPermissionsCommand {
apiId: number;
webapiId: number;
permissions: Array<PermissionString>;
}

View file

@ -19,7 +19,7 @@ export default abstract class WebapiPermissionCommandHandler {
* @returns {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
);
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(() => {})

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> {
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<any> {
export async function updateWebapi(req: Request, res: Response): Promise<any> {
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);

View file

@ -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<any> {
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,

View file

@ -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)

View file

@ -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<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) => {
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<string> {
static async buildWebapiToken(token: string, expiration?: Date): Promise<string> {
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;
})

View file

@ -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

View file

@ -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);
});

View file

@ -11,14 +11,14 @@ export default abstract class WebapiPermissionService {
static async getByApi(webapiId: number): Promise<Array<webapiPermission>> {
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);
});
}
}

View file

@ -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) => {