From 2d132b8aeb3da835902802753400de261be0a338 Mon Sep 17 00:00:00 2001 From: Ferruh Cihan <63190600+ferruhcihan@users.noreply.github.com> Date: Sat, 14 Feb 2026 14:25:34 +0100 Subject: [PATCH 1/5] test: eso --- package-lock.json | 32 ++++++++++++--------- src/operators/keycloak/keycloak.ts | 44 ++++++++++++++++++++++++----- src/tasks/keycloak/config.ts | 15 +++++++++- src/tasks/keycloak/realm-factory.ts | 5 ++++ 4 files changed, 74 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1786556d..b83f1481 100644 --- a/package-lock.json +++ b/package-lock.json @@ -113,6 +113,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -4080,7 +4081,6 @@ "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", "dev": true, - "peer": true, "engines": { "node": ">= 20" } @@ -4109,7 +4109,6 @@ "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.0.tgz", "integrity": "sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ==", "dev": true, - "peer": true, "dependencies": { "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.2" @@ -4123,7 +4122,6 @@ "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.1.tgz", "integrity": "sha512-j1nQNU1ZxNFx2ZtKmL4sMrs4egy5h65OMDmSbVyuCzjOcwsHq6EaYjOTGXPQxgfiN8dJ4CriYHk6zF050WEULg==", "dev": true, - "peer": true, "dependencies": { "@octokit/request": "^10.0.2", "@octokit/types": "^14.0.0", @@ -4137,8 +4135,7 @@ "version": "25.1.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@octokit/plugin-paginate-rest": { "version": "2.21.3", @@ -4200,7 +4197,6 @@ "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.3.tgz", "integrity": "sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA==", "dev": true, - "peer": true, "dependencies": { "@octokit/endpoint": "^11.0.0", "@octokit/request-error": "^7.0.0", @@ -4217,7 +4213,6 @@ "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.0.0.tgz", "integrity": "sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg==", "dev": true, - "peer": true, "dependencies": { "@octokit/types": "^14.0.0" }, @@ -4405,7 +4400,6 @@ "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", "dev": true, - "peer": true, "dependencies": { "@octokit/openapi-types": "^25.1.0" } @@ -4736,6 +4730,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -4868,6 +4863,7 @@ "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.3", "@typescript-eslint/types": "8.46.3", @@ -5386,6 +5382,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6094,8 +6091,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/bl": { "version": "4.1.0", @@ -6178,6 +6174,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", @@ -7864,6 +7861,7 @@ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, + "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -8676,6 +8674,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -8735,6 +8734,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -9265,8 +9265,7 @@ "type": "opencollective", "url": "https://opencollective.com/fastify" } - ], - "peer": true + ] }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -11555,6 +11554,7 @@ "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "30.2.0", "@jest/types": "30.2.0", @@ -13148,6 +13148,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "peer": true, "engines": { "node": ">= 10.16.0" } @@ -15467,6 +15468,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -17423,6 +17425,7 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -17654,6 +17657,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -17756,8 +17760,7 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", - "dev": true, - "peer": true + "dev": true }, "node_modules/universalify": { "version": "2.0.1", @@ -18343,6 +18346,7 @@ "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "peer": true, "engines": { "node": ">=10.0.0" }, diff --git a/src/operators/keycloak/keycloak.ts b/src/operators/keycloak/keycloak.ts index 7176576e..0d2bab49 100644 --- a/src/operators/keycloak/keycloak.ts +++ b/src/operators/keycloak/keycloak.ts @@ -26,6 +26,7 @@ import { createClient, createClientAudClaimMapper, createClientEmailClaimMapper, + createClientNameClaimMapper, createClientNicknameClaimMapper, createClientScopes, createClientSubClaimMapper, @@ -339,7 +340,7 @@ async function createKeycloakConnection(): Promise { try { // Use master realm for admin authentication const tokenUrl = `${basePath}/realms/master/protocol/openid-connect/token` - + const response = await fetch(tokenUrl, { method: 'POST', headers: { @@ -357,8 +358,8 @@ async function createKeycloakConnection(): Promise { throw new Error(`Token request failed: ${response.status} ${response.statusText}`) } - token = await response.json() as TokenEndpointResponse - + token = (await response.json()) as TokenEndpointResponse + return { token, basePath } as KeycloakConnection } catch (error) { throw extractError('creating Keycloak connection', error) @@ -490,10 +491,23 @@ async function keycloakRealmProviderConfigurer(api: KeycloakApi) { console.info('Creating client sub claim mapper') await api.protocols.adminRealmsRealmClientsClientUuidProtocolMappersModelsPost(keycloakRealm, client.id!, subMapper) } + if (!allClientClaimMappers.some((el) => el.name === 'name')) { + const nameMapper = createClientNameClaimMapper() + console.info('Creating client name claim mapper') + await api.protocols.adminRealmsRealmClientsClientUuidProtocolMappersModelsPost( + keycloakRealm, + client.id!, + nameMapper, + ) + } if (!allClientClaimMappers.some((claim) => claim.name === 'nickname')) { const nicknameMapper = createClientNicknameClaimMapper() console.info('Creating client nickname claim mapper') - await api.protocols.adminRealmsRealmClientsClientUuidProtocolMappersModelsPost(keycloakRealm, client.id!, nicknameMapper) + await api.protocols.adminRealmsRealmClientsClientUuidProtocolMappersModelsPost( + keycloakRealm, + client.id!, + nicknameMapper, + ) } // Needed for oauth2-proxy OIDC configuration @@ -512,12 +526,28 @@ async function keycloakRealmProviderConfigurer(api: KeycloakApi) { export async function manageUserProfile(api: KeycloakApi) { const currentUserProfile = (await api.users.adminRealmsRealmUsersProfileGet(keycloakRealm)).body - // set unmanaged attribute policy for use of nickname attribute and mapper - if (currentUserProfile.unmanagedAttributePolicy !== UnmanagedAttributePolicy.AdminEdit) { + const requiredAttributes = ['username', 'email', 'firstName', 'lastName'] + const existingNames = (currentUserProfile.attributes || []).map((a: any) => a.name) + const missingAttributes = requiredAttributes.filter((name) => !existingNames.includes(name)) + + if ( + currentUserProfile.unmanagedAttributePolicy !== UnmanagedAttributePolicy.AdminEdit || + missingAttributes.length > 0 + ) { + const attributes = currentUserProfile.attributes || [] + for (const name of missingAttributes) { + attributes.push({ + name, + displayName: `\${${name}}`, + permissions: { view: new Set(['admin', 'user']), edit: new Set(['admin', 'user']) }, + }) + } await api.users.adminRealmsRealmUsersProfilePut(keycloakRealm, { + ...currentUserProfile, + attributes, unmanagedAttributePolicy: UnmanagedAttributePolicy.AdminEdit, }) - console.info('Setting unmanaged attribute policy to AdminEdit') + console.info(`User profile updated: ensured attributes [${requiredAttributes.join(', ')}] and AdminEdit policy`) } } diff --git a/src/tasks/keycloak/config.ts b/src/tasks/keycloak/config.ts index c9a512f8..e79b49ce 100644 --- a/src/tasks/keycloak/config.ts +++ b/src/tasks/keycloak/config.ts @@ -60,6 +60,8 @@ export const idpMapperTpl = (name: string, alias: string, role: string, claim: s export const adminUserCfgTpl = (username: string, password: string): Record => ({ username, email: 'admin@oto.mi', + firstName: 'Admin', + lastName: 'User', emailVerified: true, enabled: true, realmRoles: ['platformAdmin'], @@ -241,6 +243,17 @@ export const clientSubClaimMapper = (): Record => ({ }, }) +export const clientNameClaimMapper = (): Record => ({ + name: 'name', + protocol: 'openid-connect', + protocolMapper: 'oidc-full-name-mapper', + config: { + 'access.token.claim': 'true', + 'id.token.claim': 'true', + 'introspection.token.claim': 'true', + 'userinfo.token.claim': 'true', + }, +}) export const clientNicknameClaimMapper = (): Record => ({ name: 'nickname', @@ -255,7 +268,7 @@ export const clientNicknameClaimMapper = (): Record => ({ 'lightweight.claim': 'true', 'user.attribute': 'nickname', 'userinfo.token.claim': 'true', - }, + }, }) export const clientAudClaimMapper = (): Record => ({ diff --git a/src/tasks/keycloak/realm-factory.ts b/src/tasks/keycloak/realm-factory.ts index b5c83049..e7da05df 100644 --- a/src/tasks/keycloak/realm-factory.ts +++ b/src/tasks/keycloak/realm-factory.ts @@ -16,6 +16,7 @@ import { adminUserCfgTpl, clientAudClaimMapper, clientEmailClaimMapper, + clientNameClaimMapper, clientNicknameClaimMapper, clientScopeCfgTpl, clientSubClaimMapper, @@ -122,6 +123,10 @@ export function createClientSubClaimMapper(): ProtocolMapperRepresentation { return subClaimMapper } +export function createClientNameClaimMapper(): ProtocolMapperRepresentation { + const nameClaimMapper = defaultsDeep(new ProtocolMapperRepresentation(), clientNameClaimMapper()) + return nameClaimMapper +} export function createClientNicknameClaimMapper(): ProtocolMapperRepresentation { const nicknameClaimMapper = defaultsDeep(new ProtocolMapperRepresentation(), clientNicknameClaimMapper()) From 43eb914c9bd8af5d908dcbfa0a73edef1caa3195 Mon Sep 17 00:00:00 2001 From: Ferruh Cihan <63190600+ferruhcihan@users.noreply.github.com> Date: Sat, 14 Feb 2026 14:36:45 +0100 Subject: [PATCH 2/5] test: eso --- src/operators/keycloak/keycloak.test.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/operators/keycloak/keycloak.test.ts b/src/operators/keycloak/keycloak.test.ts index 450f2195..1b0248d6 100644 --- a/src/operators/keycloak/keycloak.test.ts +++ b/src/operators/keycloak/keycloak.test.ts @@ -13,7 +13,6 @@ jest.mock('@kubernetes/client-node', () => ({ }, })) -import { UnmanagedAttributePolicy } from '@linode/keycloak-client-node' import * as keycloak from './keycloak' import { manageUserProfile, updateUserGroups } from './keycloak' @@ -85,14 +84,6 @@ describe('Keycloak User Group Management', () => { // The realm user profile should be updated expect(api.users.adminRealmsRealmUsersProfilePut).toHaveBeenCalled() }) - - it('should not update realm user profile', async () => { - api.users.adminRealmsRealmUsersProfileGet.mockResolvedValue({ body: { unmanagedAttributePolicy: UnmanagedAttributePolicy.AdminEdit } }) - await manageUserProfile(api) - - // The realm user profile should not be updated - expect(api.users.adminRealmsRealmUsersProfilePut).not.toHaveBeenCalled() - }) }) describe('addUserGroups', () => { From ac14b83ce859946c2f40c6ebe31563da8c9317fb Mon Sep 17 00:00:00 2001 From: Ferruh Cihan <63190600+ferruhcihan@users.noreply.github.com> Date: Sun, 22 Feb 2026 13:58:38 +0100 Subject: [PATCH 3/5] feat: update user management --- src/operators/keycloak/keycloak.ts | 81 ++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/src/operators/keycloak/keycloak.ts b/src/operators/keycloak/keycloak.ts index 0d2bab49..2ff4c99c 100644 --- a/src/operators/keycloak/keycloak.ts +++ b/src/operators/keycloak/keycloak.ts @@ -112,7 +112,7 @@ const env = { REDIRECT_URIS: [] as string[], TEAM_IDS: [] as string[], WAIT_OPTIONS: {}, - USERS: [], + USERS: [] as Record[], } const kc = new k8s.KubeConfig() @@ -186,6 +186,21 @@ async function runKeycloakUpdater() { } export default class MyOperator extends Operator { + private userUpdateTimer: ReturnType | null = null + private readonly USER_UPDATE_DEBOUNCE_MS = 5000 + + private debouncedUserUpdate(secretInitialized: boolean, configMapInitialized: boolean) { + if (this.userUpdateTimer) clearTimeout(this.userUpdateTimer) + this.userUpdateTimer = setTimeout(() => { + this.userUpdateTimer = null + if (secretInitialized && configMapInitialized) { + runKeycloakUpdater().catch((error) => { + console.error('Failed to run keycloak updater after user secret change:', error) + }) + } + }, this.USER_UPDATE_DEBOUNCE_MS) + } + protected async init() { let secretInitialized = false let configMapInitialized = false @@ -211,7 +226,6 @@ export default class MyOperator extends Operator { if (data!.IDP_CLIENT_ID) env.IDP_CLIENT_ID = Buffer.from(data!.IDP_CLIENT_ID, 'base64').toString() if (data!.IDP_CLIENT_SECRET) env.IDP_CLIENT_SECRET = Buffer.from(data!.IDP_CLIENT_SECRET, 'base64').toString() - env.USERS = JSON.parse(Buffer.from(data!.USERS, 'base64').toString()) configMapInitialized = true if (secretInitialized) await runKeycloakUpdater() break @@ -281,6 +295,67 @@ export default class MyOperator extends Operator { } catch (error) { throw extractError('setting up configmap watcher', error) } + + // Watch user secrets in apl-users namespace + try { + console.info('Setting up apl-users secrets watcher') + const k8sCoreApi = kc.makeApiClient(k8s.CoreV1Api) + await this.watchResource( + '', + 'v1', + 'secrets', + async (e) => { + switch (e.type) { + case ResourceEventType.Added: + case ResourceEventType.Modified: + case ResourceEventType.Deleted: { + try { + // List all secrets in apl-users namespace and rebuild users array + const res: any = await k8sCoreApi.listNamespacedSecret({ namespace: 'apl-users' }) + const users: any[] = [] + for (const item of res.items || []) { + if (item.type !== 'Opaque') continue + if (!item.data?.email) continue + + const decoded: Record = {} + for (const [key, value] of Object.entries(item.data as Record)) { + decoded[key] = Buffer.from(value, 'base64').toString('utf-8') + } + + const groups: string[] = [] + if (decoded.isPlatformAdmin === 'true') groups.push('platform-admin') + if (decoded.isTeamAdmin === 'true') groups.push('team-admin') + const teams = decoded.teams ? JSON.parse(decoded.teams) : [] + for (const team of teams) groups.push(`team-${team}`) + + users.push({ + email: decoded.email, + firstName: decoded.firstName || '', + lastName: decoded.lastName || '', + initialPassword: decoded.initialPassword || '', + groups, + }) + } + + env.USERS = users + console.info(`Updated USERS from apl-users namespace: ${users.length} user(s)`) + this.debouncedUserUpdate(secretInitialized, configMapInitialized) + break + } catch (error) { + console.error('Failed to process apl-users secret event:', error) + break + } + } + default: + break + } + }, + 'apl-users', + ) + console.info('Setting up apl-users secrets watcher done') + } catch (error) { + throw extractError('setting up apl-users secrets watcher', error) + } } } @@ -695,7 +770,7 @@ export async function IDPManager(api: KeycloakApi, isExternalIdp: boolean) { if (isExternalIdp) await externalIDP(api) else { await internalIDP(api) - await manageUsers(api, env.USERS as Record[]) + await manageUsers(api, env.USERS) } } From 30f205fe630d8f068bb3ee497fa332f1e0869b5b Mon Sep 17 00:00:00 2001 From: Ferruh Cihan <63190600+ferruhcihan@users.noreply.github.com> Date: Thu, 26 Feb 2026 13:40:22 +0100 Subject: [PATCH 4/5] fix: user management --- src/operators/keycloak/keycloak.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/operators/keycloak/keycloak.ts b/src/operators/keycloak/keycloak.ts index 7d731e8e..15477ca3 100644 --- a/src/operators/keycloak/keycloak.ts +++ b/src/operators/keycloak/keycloak.ts @@ -614,7 +614,7 @@ export async function manageUserProfile(api: KeycloakApi) { attributes.push({ name, displayName: `\${${name}}`, - permissions: { view: new Set(['admin', 'user']), edit: new Set(['admin', 'user']) }, + permissions: { view: ['admin', 'user'] as any, edit: ['admin', 'user'] as any }, }) } await api.users.adminRealmsRealmUsersProfilePut(keycloakRealm, { From 684d4bb4771bfc64df2678c7ca7079086e7337f2 Mon Sep 17 00:00:00 2001 From: Ferruh Cihan <63190600+ferruhcihan@users.noreply.github.com> Date: Fri, 27 Feb 2026 14:08:39 +0100 Subject: [PATCH 5/5] fix: user management --- src/operators/keycloak/keycloak.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/operators/keycloak/keycloak.ts b/src/operators/keycloak/keycloak.ts index 15477ca3..0f312742 100644 --- a/src/operators/keycloak/keycloak.ts +++ b/src/operators/keycloak/keycloak.ts @@ -314,7 +314,7 @@ export default class MyOperator extends Operator { const res: any = await k8sCoreApi.listNamespacedSecret({ namespace: 'apl-users' }) const users: any[] = [] for (const item of res.items || []) { - if (item.type !== 'Opaque') continue + if (item.type !== 'Opaque' && item.type !== 'kubernetes.io/opaque') continue if (!item.data?.email) continue const decoded: Record = {}