finalize migration (yay)

This commit is contained in:
tw-blue
2025-12-10 22:11:30 +01:00
parent cd6cfec7f3
commit c7d53c88bc
40 changed files with 966 additions and 499 deletions
+3 -3
View File
@@ -44,9 +44,9 @@ export class DatabaseService {
private readonly prismaClient: ExtendedClient;
public constructor() {
const dbUser = process.env["TKD_DB_USER"];
const dbPassword = process.env["TKD_DB_PASSWORD"];
const dbHost = process.env["TKD_DB_HOST"];
const dbUser = process.env["TKD_DB_USER"]!;
const dbPassword = process.env["TKD_DB_PASSWORD"]!;
const dbHost = process.env["TKD_DB_HOST"]!;
const connectionString = `postgresql://${dbUser}:${dbPassword}@${dbHost}:5432/taekwondo?schema=public`;
@@ -1,23 +1,43 @@
import { Injectable } from "../../../infrastructure/di/injectable-decorator.js";
import kcClientConfig from "./nodejs-api.json" with { type: "json" };
import KeycloakAdminClient from "@keycloak/keycloak-admin-client";
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation.js";
import { Claim } from "../../../dtos/claim.js";
import { type User } from "../../../dtos/user.js";
import { inject } from "../../../infrastructure/di/index.js";
import { EnvironmentService } from "../../environmentService.js";
@Injectable()
export class KeycloakAdmin {
private readonly kcAdminClient: Promise<KeycloakAdminClient>;
private readonly environment = inject(EnvironmentService);
private readonly config = this.getConfig();
private async getConfig() {
if (this.environment.isDev()) {
const config = await import("./nodejs-api.dev.json", {
with: { type: "json" },
});
return config.default;
} else {
const config = await import("./nodejs-api.prod.json", {
with: { type: "json" },
});
return config.default;
}
}
constructor() {
this.kcAdminClient = new Promise(async resolve => {
const config = await this.config;
const client = new KeycloakAdminClient({
baseUrl: kcClientConfig.baseUrl,
baseUrl: config["auth-server-url"],
realmName: "taekwondo",
});
await client.auth({
...kcClientConfig,
clientSecret: config.credentials.secret,
clientId: config.resource,
grantType: "client_credentials",
});
resolve(client);
@@ -0,0 +1,10 @@
{
"realm": "taekwondo",
"auth-server-url": "http://localhost:8080/",
"ssl-required": "external",
"resource": "nodejs-api",
"credentials": {
"secret": "devsecret"
},
"confidential-port": 0
}
@@ -1,60 +0,0 @@
{
"clientId": "nodejs-api",
"name": "NodejsApi",
"description": "",
"rootUrl": "http://localhost:3000",
"adminUrl": "http://localhost:3000",
"baseUrl": "http://localhost:3000",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"secret": "devsecret",
"redirectUris": ["http://localhost:3000/*"],
"webOrigins": ["http://localhost:3000"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": false,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": true,
"publicClient": false,
"frontchannelLogout": true,
"protocol": "openid-connect",
"attributes": {
"realm_client": "false",
"oidc.ciba.grant.enabled": "false",
"client.secret.creation.time": "1762463686",
"backchannel.logout.session.required": "true",
"standard.token.exchange.enabled": "true",
"oauth2.device.authorization.grant.enabled": "false",
"pkce.code.challenge.method": "S256",
"backchannel.logout.revoke.offline.tokens": "false",
"dpop.bound.access.tokens": "false"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1,
"defaultClientScopes": [
"service_account",
"web-origins",
"acr",
"roles",
"profile",
"basic",
"email"
],
"optionalClientScopes": [
"address",
"phone",
"organization",
"offline_access",
"microprofile-jwt"
],
"access": {
"view": true,
"configure": true,
"manage": true
}
}
@@ -1,11 +1,52 @@
import { type Request, type RequestHandler, type Response } from "express";
import KeycloakConnect from "keycloak-connect";
import { inject } from "../../../infrastructure/di/index.js";
import { Injectable } from "../../../infrastructure/di/injectable-decorator.js";
import { sessionStore } from "../../../infrastructure/sessionHandler.js";
import keycloakConfig from "./keycloak.json" with { type: "json" };
import { EnvironmentService } from "../../environmentService.js";
//TODO transform into service and load config based on environment (see keycloak-admin)
export const KC_SECURITY_NAME = "bearerAuth";
export const keycloak = new KeycloakConnect(
{ store: sessionStore },
keycloakConfig,
);
export const kcUserConfig = keycloakConfig;
@Injectable()
export class KeycloakUser {
private readonly environment = inject(EnvironmentService);
private readonly keycloakConnect: Promise<KeycloakConnect.Keycloak>;
private async getConfig() {
if (this.environment.isDev()) {
const config = await import("./keycloak.dev.json", {
with: { type: "json" },
});
return config.default;
} else {
const config = await import("./keycloak.prod.json", {
with: { type: "json" },
});
return config.default;
}
}
public constructor() {
this.keycloakConnect = new Promise(async resolve => {
const config = await this.getConfig();
const client = new KeycloakConnect({ store: sessionStore }, config);
resolve(client);
});
}
public async checkRoles(
roles: string[],
request: Request,
response: Response,
) {
const client = await this.keycloakConnect;
const grant = await client.getGrant(request, response ?? ({} as any));
return roles.every(role => grant.access_token?.hasRealmRole(role));
}
public async middleware(): Promise<RequestHandler[]> {
const client = await this.keycloakConnect;
return client.middleware();
}
}
+11 -10
View File
@@ -1,7 +1,7 @@
import express from "express";
import {
type Member,
type MemberCreateArgs,
type MemberDto,
mapDbMemberToMember,
} from "../dtos/member.js";
import { inject, Injectable } from "../infrastructure/di/index.js";
@@ -12,7 +12,7 @@ import { DatabaseService } from "./db/prisma.js";
export class MembersService {
private readonly database = inject(DatabaseService);
public async getMembers(request: express.Request): Promise<Member[]> {
public async getMembers(request: express.Request): Promise<MemberDto[]> {
const dbMembers = await this.database.doRequest(
async prisma => await prisma.member.findMany(),
request,
@@ -23,7 +23,7 @@ export class MembersService {
public async getMemberById(
memberId: string,
request: express.Request,
): Promise<Member> {
): Promise<MemberDto> {
const member = await this.database.doRequest(
async prisma =>
await prisma.member.findFirst({ where: { id: memberId } }),
@@ -42,11 +42,11 @@ export class MembersService {
public async createMember(
createArgs: MemberCreateArgs,
request: express.Request,
): Promise<Member> {
const x: MemberCreateArgs = {
...createArgs,
geburtsdatum: createArgs.geburtsdatum ?? null,
};
): Promise<MemberDto> {
// const x: MemberCreateArgs = {
// ...createArgs,
// geburtsdatum: createArgs.geburtsdatum ?? null,
// };
const newMember = await this.database.doRequest(
async prisma => await prisma.member.create({ data: createArgs }),
request,
@@ -55,15 +55,16 @@ export class MembersService {
}
public async updateMember(
member: Member,
member: MemberDto,
request: express.Request,
): Promise<Member> {
): Promise<MemberDto> {
const id = member.id;
if (!id) {
throw new Error("Member id is required for update", {
cause: ErrorStates.BAD_REQUEST,
});
}
const modifiedMember = await this.database.doRequest(
async prisma =>
await prisma.member.update({