api keycloak setup
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 4,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"bracketSameLine": true,
|
||||||
|
"semi": true
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
import { Controller, Get, Route, SuccessResponse } from 'tsoa';
|
import { Controller, Get, Post, Route, Security, SuccessResponse } from "tsoa";
|
||||||
|
import { inject } from "../infrastructure/di/index.js";
|
||||||
|
import { KC_SECURITY_NAME } from "../services/keycloak/user/index.js";
|
||||||
|
import { MessagingService } from "../services/messagingService.js";
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
email: string;
|
email: string;
|
||||||
@@ -8,13 +11,21 @@ export interface User {
|
|||||||
|
|
||||||
export type UserCreationParams = Pick<User, "email" | "name" | "phoneNumbers">;
|
export type UserCreationParams = Pick<User, "email" | "name" | "phoneNumbers">;
|
||||||
|
|
||||||
@Route('health')
|
@Route("health")
|
||||||
export class HealthCheckController extends Controller {
|
export class HealthCheckController extends Controller {
|
||||||
@Get('ok')
|
private readonly messaging = inject(MessagingService);
|
||||||
@SuccessResponse('200', 'OK')
|
|
||||||
public async getOk(
|
@Get("ok")
|
||||||
): Promise<string> {
|
@SuccessResponse("200", "OK")
|
||||||
this.setStatus(200);
|
public async getOk() {}
|
||||||
return 'Ok';
|
|
||||||
|
@Get("auth")
|
||||||
|
@Security(KC_SECURITY_NAME)
|
||||||
|
@SuccessResponse("200", "OK")
|
||||||
|
public async getAuth() {}
|
||||||
|
|
||||||
|
@Post("testmessage")
|
||||||
|
public async sendTestMessage() {
|
||||||
|
await this.messaging.sendTestMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Controller, Route } from "@tsoa/runtime";
|
||||||
|
import { inject } from "../infrastructure/di/index.js";
|
||||||
|
import { MembersService } from "../services/membersService.js";
|
||||||
|
|
||||||
|
@Route("members")
|
||||||
|
export class MembersContoller extends Controller {
|
||||||
|
private readonly membersService = inject(MembersService);
|
||||||
|
// @Security(KC_SECURITY_NAME)
|
||||||
|
// @Get()
|
||||||
|
// public async getMembers(): Promise<Member[]> {
|
||||||
|
// return await this.membersService.getMembers();
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
BodyProp,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Path,
|
||||||
|
Put,
|
||||||
|
Query,
|
||||||
|
Route,
|
||||||
|
Security,
|
||||||
|
} from "@tsoa/runtime";
|
||||||
|
import { inject } from "../infrastructure/di";
|
||||||
|
import { Claim } from "../models/claim";
|
||||||
|
import { Topic } from "../models/topic";
|
||||||
|
import { User } from "../models/user";
|
||||||
|
import { KC_SECURITY_NAME } from "../services/keycloak/user/index.js";
|
||||||
|
import { UserService } from "../services/userService";
|
||||||
|
|
||||||
|
@Route("users")
|
||||||
|
export class UsersController extends Controller {
|
||||||
|
private readonly userService = inject(UserService);
|
||||||
|
|
||||||
|
@Security(KC_SECURITY_NAME, [Claim.UserAdmin])
|
||||||
|
@Get()
|
||||||
|
public async getUsers(): Promise<User[]> {
|
||||||
|
return await this.userService.getUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Security(KC_SECURITY_NAME, [Claim.UserAdmin])
|
||||||
|
@Put("/claims/{uid}")
|
||||||
|
public async setClaims(
|
||||||
|
@Path() uid: string,
|
||||||
|
@BodyProp() claim: Claim,
|
||||||
|
@Query() value: boolean,
|
||||||
|
): Promise<void> {
|
||||||
|
await this.userService.setClaim(uid, claim, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Security(KC_SECURITY_NAME)
|
||||||
|
@Put("/subscriptions/{token}")
|
||||||
|
public async setSubscriptions(
|
||||||
|
@Body() topics: Topic[],
|
||||||
|
@Path() token: string,
|
||||||
|
) {
|
||||||
|
await this.userService.setSubscriptions(token, topics);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,19 @@
|
|||||||
import express from 'express';
|
import express from "express";
|
||||||
import { RegisterRoutes } from './generated/routes.js';
|
import { RegisterRoutes } from "./generated/routes.js";
|
||||||
|
import { sessionHandler } from "./infrastructure/session.js";
|
||||||
|
import { keycloak } from "./services/keycloak/user/index.js";
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const port = 3000;
|
const port = 3000;
|
||||||
|
|
||||||
|
app.use(sessionHandler);
|
||||||
|
app.use(keycloak.middleware());
|
||||||
|
|
||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
RegisterRoutes(app);
|
RegisterRoutes(app);
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Server is running at http://localhost:${port}`);
|
console.log(`Server is running at http://localhost:${port}`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Pool } from "pg";
|
||||||
|
|
||||||
|
const dbUser = process.env["TKD_DB_USER"];
|
||||||
|
const dbPassword = process.env["TKD_DB_PASSWORD"];
|
||||||
|
|
||||||
|
if (dbUser === undefined || dbPassword === undefined) {
|
||||||
|
throw new Error("missing db configuration (user/password)");
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DB_POOL = new Pool({
|
||||||
|
user: dbUser,
|
||||||
|
password: dbPassword,
|
||||||
|
host: "localhost",
|
||||||
|
port: 5432,
|
||||||
|
database: "taekwondo",
|
||||||
|
});
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { Injectable } from "./injectable-decorator.js";
|
||||||
|
export { inject } from "./injector.js";
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { type Token } from "./types.js";
|
||||||
|
|
||||||
|
const INJECTABLE_META = Symbol("injectable");
|
||||||
|
const INJECTABLE_REGISTRY = new Set<Token>();
|
||||||
|
|
||||||
|
export function Injectable(): ClassDecorator {
|
||||||
|
return function (target: any) {
|
||||||
|
Reflect.defineProperty(target, INJECTABLE_META, {
|
||||||
|
value: true,
|
||||||
|
writable: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
INJECTABLE_REGISTRY.add(target);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isInjectable(targert: any) {
|
||||||
|
return !!targert[INJECTABLE_META];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAllInjectables(): Token[] {
|
||||||
|
return Array.from(INJECTABLE_REGISTRY);
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { getAllInjectables, isInjectable } from "./injectable-decorator.js";
|
||||||
|
import { type Provider, type Token } from "./types.js";
|
||||||
|
|
||||||
|
const currentInjector: Injector = createGlobalInjector();
|
||||||
|
|
||||||
|
export function inject<T>(token: Token<T>): T {
|
||||||
|
if (!currentInjector) {
|
||||||
|
throw new Error("inject() called outside of injection context");
|
||||||
|
}
|
||||||
|
return currentInjector.get(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Injector {
|
||||||
|
private instances = new Map<Token, any>();
|
||||||
|
private records = new Map<Token, Provider>();
|
||||||
|
|
||||||
|
constructor(providers: Provider[]) {
|
||||||
|
providers.forEach(p => this.records.set(p.provide, p));
|
||||||
|
}
|
||||||
|
|
||||||
|
get<T>(token: Token<T>): T {
|
||||||
|
if (this.instances.has(token)) {
|
||||||
|
return this.instances.get(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.records.get(token);
|
||||||
|
if (!provider) {
|
||||||
|
throw new Error("No provider for token");
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = this.instantiate(token);
|
||||||
|
this.instances.set(token, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private instantiate<T>(token: Token<T>): T {
|
||||||
|
return new token();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createInjector(injectables: Token[]) {
|
||||||
|
const providers: Provider[] = injectables.map(injectable => {
|
||||||
|
if (!isInjectable(injectable)) {
|
||||||
|
throw new Error(`${injectable.name} is not injectable`);
|
||||||
|
}
|
||||||
|
return { provide: injectable, useClass: injectable };
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Injector(providers);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createGlobalInjector() {
|
||||||
|
const injectables = getAllInjectables();
|
||||||
|
|
||||||
|
return createInjector(injectables);
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export type Token<T = any> = new (...args: any[]) => T;
|
||||||
|
export interface Provider<T = any> {
|
||||||
|
provide: Token<T>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import pgSession from "connect-pg-simple";
|
||||||
|
import { type RequestHandler } from "express";
|
||||||
|
import session from "express-session";
|
||||||
|
import { DB_POOL } from "./database.js";
|
||||||
|
|
||||||
|
const cookieSecret = process.env["TKD_COOKIE_SECRET"];
|
||||||
|
|
||||||
|
if (cookieSecret === undefined) {
|
||||||
|
throw new Error("TKD_COOKIE_SECRET missing");
|
||||||
|
}
|
||||||
|
const pgSessionFunc = pgSession(session);
|
||||||
|
export const sessionStore = new pgSessionFunc({
|
||||||
|
pool: DB_POOL,
|
||||||
|
tableName: "session",
|
||||||
|
});
|
||||||
|
const sessionConfig = {
|
||||||
|
store: sessionStore,
|
||||||
|
name: "SID",
|
||||||
|
secret: cookieSecret,
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: true,
|
||||||
|
cookie: {
|
||||||
|
maxAge: 1000 * 60 * 60 * 24 * 7,
|
||||||
|
sameSite: true,
|
||||||
|
secure: true, //TODO false for development
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sessionHandler: RequestHandler = session(sessionConfig);
|
||||||
@@ -1,10 +1,47 @@
|
|||||||
import * as express from 'express';
|
import { type Request, type RequestHandler } from "express";
|
||||||
|
|
||||||
|
import { KC_SECURITY_NAME, keycloak } from "../services/keycloak/user/index.js";
|
||||||
|
|
||||||
|
function promisifyMiddleware(
|
||||||
|
request: Request,
|
||||||
|
middleware: RequestHandler,
|
||||||
|
response?: Response,
|
||||||
|
): Promise<any> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
middleware(request, response || ({} as any), (result: any) => {
|
||||||
|
result instanceof Error ? reject(result) : resolve(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doKeycloakAuth(
|
||||||
|
request: Request,
|
||||||
|
roles?: string[],
|
||||||
|
response?: Response,
|
||||||
|
): Promise<any> {
|
||||||
|
if (roles) {
|
||||||
|
return await promisifyMiddleware(
|
||||||
|
request,
|
||||||
|
keycloak.enforcer(roles?.map(role => `realm:${role}`)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return await promisifyMiddleware(request, keycloak.protect(), response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function expressAuthentication(
|
export async function expressAuthentication(
|
||||||
request: express.Request,
|
request: Request,
|
||||||
securityName: string,
|
securityName: string,
|
||||||
scopes?: string[]
|
roles?: string[],
|
||||||
|
response?: Response,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
console.dir({ request, securityName, scopes });
|
console.dir({ request, securityName, roles });
|
||||||
return {};
|
switch (securityName) {
|
||||||
|
case KC_SECURITY_NAME:
|
||||||
|
return doKeycloakAuth(request, roles, response);
|
||||||
|
case undefined:
|
||||||
|
return Promise.resolve();
|
||||||
|
default:
|
||||||
|
return Promise.reject("Unknown security name");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
/// Sync to Keycloak realm roles
|
||||||
|
export enum Claim {
|
||||||
|
MemberAdmin = "memberadmin",
|
||||||
|
UserAdmin = "useradmin",
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
export interface Member {
|
||||||
|
id?: string | undefined;
|
||||||
|
vorname?: string | undefined;
|
||||||
|
nachname?: string | undefined;
|
||||||
|
geburtsdatum?: string | undefined;
|
||||||
|
geburtsort?: string | undefined;
|
||||||
|
geschlecht?: string | undefined;
|
||||||
|
passnummer?: string | undefined;
|
||||||
|
uvertrag?: boolean | undefined;
|
||||||
|
verein?: boolean | undefined;
|
||||||
|
graduierung?: string | undefined;
|
||||||
|
letztePruefung?: string | undefined;
|
||||||
|
kontakt?: string | undefined;
|
||||||
|
kuendigungzum?: string | undefined;
|
||||||
|
loeschenam?: string | undefined;
|
||||||
|
level?: string | undefined;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export enum Topic {
|
||||||
|
test = "test",
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Claim } from "./claim";
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
readonly id?: string;
|
||||||
|
readonly username: string;
|
||||||
|
readonly roles: Claim[];
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./keycloak-admin.js";
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
|
||||||
|
import { Injectable } from "../../../infrastructure/di/injectable-decorator";
|
||||||
|
import kcClientConfig from "./nodejs-api.json" with { type: "json" };
|
||||||
|
|
||||||
|
import KeycloakAdminClient from "@keycloak/keycloak-admin-client";
|
||||||
|
import { Claim } from "../../../models/claim";
|
||||||
|
import { User } from "../../../models/user";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class KeycloakAdmin {
|
||||||
|
private readonly kcAdminClient: Promise<KeycloakAdminClient>;
|
||||||
|
constructor() {
|
||||||
|
this.kcAdminClient = new Promise(async resolve => {
|
||||||
|
const client = new KeycloakAdminClient({
|
||||||
|
baseUrl: kcClientConfig.baseUrl,
|
||||||
|
realmName: "taekwondo",
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.auth({
|
||||||
|
...kcClientConfig,
|
||||||
|
grantType: "client_credentials",
|
||||||
|
});
|
||||||
|
resolve(client);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async addRoleToUser(uid: string, roleName: string): Promise<void> {
|
||||||
|
const client = await this.kcAdminClient;
|
||||||
|
const role = await this.getRole(roleName);
|
||||||
|
if (!role.id || !role.name) {
|
||||||
|
throw new Error(`Role ${roleName} is missing id or name`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.users.addRealmRoleMappings({
|
||||||
|
id: uid,
|
||||||
|
roles: [{ id: role.id, name: role.name }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeRoleFromUser(uid: string, claim: Claim): Promise<void> {
|
||||||
|
const client = await this.kcAdminClient;
|
||||||
|
const role = await this.getRole(claim);
|
||||||
|
if (!role.id || !role.name) {
|
||||||
|
throw new Error(`Role ${claim} is missing id or name`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.users.delRealmRoleMappings({
|
||||||
|
id: uid,
|
||||||
|
roles: [{ id: role.id, name: role.name }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getUsers(): Promise<User[]> {
|
||||||
|
const client = await this.kcAdminClient;
|
||||||
|
const kcUsers = await client.users.find();
|
||||||
|
return kcUsers.map(kcUser => ({
|
||||||
|
id: kcUser.id,
|
||||||
|
username: kcUser.username || "",
|
||||||
|
roles: (kcUser.realmRoles || []).map(role => role as Claim),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getRole(name: string): Promise<RoleRepresentation> {
|
||||||
|
const client = await this.kcAdminClient;
|
||||||
|
const role = await client.roles.findOneByName({
|
||||||
|
name: name,
|
||||||
|
realm: "taekwondo",
|
||||||
|
});
|
||||||
|
if (!role) {
|
||||||
|
throw new Error(`Role ${name} not found`);
|
||||||
|
}
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"clientId": "nodejs-api",
|
||||||
|
"name": "NodejsApi",
|
||||||
|
"description": "",
|
||||||
|
"rootUrl": "https://tkd-api.toni714.de",
|
||||||
|
"adminUrl": "https://tkd-api.toni714.de",
|
||||||
|
"baseUrl": "https://tkd-api.toni714.de",
|
||||||
|
"surrogateAuthRequired": false,
|
||||||
|
"enabled": true,
|
||||||
|
"alwaysDisplayInConsole": false,
|
||||||
|
"clientAuthenticatorType": "client-secret",
|
||||||
|
"secret": "btdeoaq2mgtoa7SpCEL1phLYcw2gGhGp",
|
||||||
|
"redirectUris": ["https://tkd-api.toni714.de/*"],
|
||||||
|
"webOrigins": ["https://tkd-api.toni714.de"],
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./keycloak-user.js";
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import KeycloakConnect from "keycloak-connect";
|
||||||
|
import { sessionStore } from "../../../infrastructure/session.js";
|
||||||
|
import keycloakConfig from "./keycloak.json" with { type: "json" };
|
||||||
|
|
||||||
|
export const KC_SECURITY_NAME = "bearerAuth";
|
||||||
|
|
||||||
|
export const keycloak = new KeycloakConnect(
|
||||||
|
{ store: sessionStore },
|
||||||
|
keycloakConfig,
|
||||||
|
);
|
||||||
|
export const kcUserConfig = keycloakConfig;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"realm": "taekwondo",
|
||||||
|
"auth-server-url": "https://keycloak.toni714.de:8443",
|
||||||
|
"ssl-required": "external",
|
||||||
|
"resource": "nodejs-api",
|
||||||
|
"credentials": {
|
||||||
|
"secret": "btdeoaq2mgtoa7SpCEL1phLYcw2gGhGp"
|
||||||
|
},
|
||||||
|
"confidential-port": 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { Injectable } from "../infrastructure/di/index.js";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MembersService {
|
||||||
|
// public async getMembers(): Promise<Member[]> {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { Injectable } from "../infrastructure/di/index.js";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MessagingService {
|
||||||
|
public async sendTestMessage(): Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { inject } from "../infrastructure/di/index.js";
|
||||||
|
import { Claim } from "../models/claim";
|
||||||
|
import { Topic } from "../models/topic.js";
|
||||||
|
import { User } from "../models/user.js";
|
||||||
|
import { KeycloakAdmin } from "./keycloak/admin/index.js";
|
||||||
|
|
||||||
|
export class UserService {
|
||||||
|
private readonly keycloakAdmin = inject(KeycloakAdmin);
|
||||||
|
public async setClaim(
|
||||||
|
uid: string,
|
||||||
|
claim: Claim,
|
||||||
|
value: boolean,
|
||||||
|
): Promise<void> {
|
||||||
|
if (value) {
|
||||||
|
return await this.keycloakAdmin.addRoleToUser(uid, claim);
|
||||||
|
} else {
|
||||||
|
return await this.keycloakAdmin.removeRoleFromUser(uid, claim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getUsers(): Promise<User[]> {
|
||||||
|
return await this.keycloakAdmin.getUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setSubscriptions(token: string, topics: Topic[]) {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
+52
-52
@@ -1,60 +1,60 @@
|
|||||||
{
|
{
|
||||||
// Visit https://aka.ms/tsconfig to read more about this file
|
// Visit https://aka.ms/tsconfig to read more about this file
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// File Layout
|
// File Layout
|
||||||
"rootDir": "./src",
|
"rootDir": "./src",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
|
|
||||||
// Environment Settings
|
// Environment Settings
|
||||||
// See also https://aka.ms/tsconfig/module
|
// See also https://aka.ms/tsconfig/module
|
||||||
"module": "nodenext",
|
"module": "nodenext",
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
// "types": [],
|
// "types": [],
|
||||||
// For nodejs:
|
// For nodejs:
|
||||||
"lib": ["esnext"],
|
"lib": ["esnext"],
|
||||||
"types": ["node"],
|
"types": ["node"],
|
||||||
// and npm install -D @types/node
|
// and npm install -D @types/node
|
||||||
|
|
||||||
// Other Outputs
|
// Other Outputs
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
|
|
||||||
// Stricter Typechecking Options
|
// Stricter Typechecking Options
|
||||||
"noUncheckedIndexedAccess": true,
|
"noUncheckedIndexedAccess": true,
|
||||||
"exactOptionalPropertyTypes": true,
|
"exactOptionalPropertyTypes": true,
|
||||||
|
|
||||||
// Style Options
|
// Style Options
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noImplicitOverride": true,
|
"noImplicitOverride": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noPropertyAccessFromIndexSignature": true,
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
|
||||||
// Recommended Options
|
// Recommended Options
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noUncheckedSideEffectImports": true,
|
"noUncheckedSideEffectImports": true,
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
|
||||||
// Custom Options
|
// Custom Options
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"useDefineForClassFields": false,
|
"useDefineForClassFields": false,
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "nodenext",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"strictFunctionTypes": true,
|
"strictFunctionTypes": true,
|
||||||
"strictBindCallApply": true,
|
"strictBindCallApply": true,
|
||||||
"strictPropertyInitialization": true,
|
"strictPropertyInitialization": true,
|
||||||
"noImplicitThis": true,
|
"noImplicitThis": true,
|
||||||
"alwaysStrict": true,
|
"alwaysStrict": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true
|
||||||
},
|
},
|
||||||
"files": ["./src/index.ts"]
|
"files": ["./src/index.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user