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,9 +1,14 @@
|
|||||||
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());
|
||||||
|
|
||||||
|
|||||||
@@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
"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