From 82f90f621b7a7b5db485cda3b44ad1ae0287a06c Mon Sep 17 00:00:00 2001 From: Masaki Yatsu Date: Wed, 29 Oct 2025 15:33:20 +0900 Subject: [PATCH] chore(keycloak): code cleanup --- .prettierrc.js | 8 + keycloak/justfile | 10 ++ keycloak/scripts/add-attribute-mapper.ts | 16 +- .../scripts/add-audience-mapper-to-scope.ts | 127 ++++++++------- keycloak/scripts/add-audience-mapper.ts | 4 +- keycloak/scripts/add-client-roles-mapper.ts | 16 +- .../add-client-roles-to-profile-scope.ts | 20 +-- keycloak/scripts/add-groups-mapper.ts | 42 +++-- keycloak/scripts/add-minio-policy.ts | 17 +- keycloak/scripts/add-scope-to-client.ts | 107 ++++++------- keycloak/scripts/add-user-to-client-role.ts | 21 +-- keycloak/scripts/add-user-to-group.ts | 14 +- keycloak/scripts/check-mapper-details.ts | 14 +- keycloak/scripts/client-exists.ts | 7 +- keycloak/scripts/create-client-role.ts | 10 +- keycloak/scripts/create-client-scope.ts | 100 ++++++------ keycloak/scripts/create-client.ts | 44 +++--- keycloak/scripts/create-group.ts | 30 ++-- keycloak/scripts/create-realm.ts | 20 ++- keycloak/scripts/delete-client.ts | 2 +- keycloak/scripts/delete-group.ts | 16 +- keycloak/scripts/delete-realm.ts | 6 +- keycloak/scripts/delete-user-from-group.ts | 14 +- keycloak/scripts/enable-service-account.ts | 90 +++++------ keycloak/scripts/get-client-details.ts | 13 +- keycloak/scripts/get-client-scope.ts | 88 +++++++++++ keycloak/scripts/get-client-secret.ts | 6 +- keycloak/scripts/get-client.ts | 8 +- keycloak/scripts/get-user-token.ts | 20 +-- keycloak/scripts/list-clients.ts | 8 +- keycloak/scripts/list-user-client-roles.ts | 10 +- .../scripts/remove-user-from-client-role.ts | 9 +- keycloak/scripts/show-realm-token-settings.ts | 137 ++++++++-------- .../scripts/update-client-roles-mapper.ts | 12 +- .../scripts/update-realm-token-settings.ts | 146 +++++++++--------- 35 files changed, 613 insertions(+), 599 deletions(-) create mode 100644 .prettierrc.js create mode 100755 keycloak/scripts/get-client-scope.ts diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..f33b449 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,8 @@ +module.exports = { + printWidth: 100, + singleQuote: false, + semi: true, + trailingComma: 'es5', + tabWidth: 2, + useTabs: false, +} diff --git a/keycloak/justfile b/keycloak/justfile index 32c86eb..3d1e640 100644 --- a/keycloak/justfile +++ b/keycloak/justfile @@ -685,3 +685,13 @@ add-scope-to-client realm client_id scope_name: export KEYCLOAK_CLIENT_ID={{ client_id }} export SCOPE_NAME={{ scope_name }} dotenvx run -q -f ../.env.local -- tsx ./scripts/add-scope-to-client.ts + +# Get client scope details +get-client-scope realm scope_name: + #!/bin/bash + set -euo pipefail + export KEYCLOAK_ADMIN_USER=$(just admin-username) + export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) + export KEYCLOAK_REALM={{ realm }} + export SCOPE_NAME={{ scope_name }} + dotenvx run -q -f ../.env.local -- tsx ./scripts/get-client-scope.ts diff --git a/keycloak/scripts/add-attribute-mapper.ts b/keycloak/scripts/add-attribute-mapper.ts index fd8ee42..d27fcdb 100644 --- a/keycloak/scripts/add-attribute-mapper.ts +++ b/keycloak/scripts/add-attribute-mapper.ts @@ -26,7 +26,6 @@ const main = async () => { const attributeDefaultValue = process.env.ATTRIBUTE_DEFAULT_VALUE; const mapperName = process.env.MAPPER_NAME || `${attributeDisplayName} Mapper`; - // Parse permissions from environment variables const viewPermissions = process.env.ATTRIBUTE_VIEW_PERMISSIONS?.split(",") || ["admin", "user"]; const editPermissions = process.env.ATTRIBUTE_EDIT_PERMISSIONS?.split(",") || ["admin"]; @@ -58,19 +57,13 @@ const main = async () => { }); console.log("Authentication successful."); - // Set realm to work with kcAdminClient.setConfig({ realmName, }); - - // Get current User Profile configuration const userProfile = await kcAdminClient.users.getProfile(); - - // Check if attribute already exists const existingAttribute = userProfile.attributes?.find( (attr: any) => attr.name === attributeName ); - if (existingAttribute) { console.log(`${attributeName} attribute already exists in User Profile.`); } else { @@ -88,16 +81,13 @@ const main = async () => { }, }; - // Add validations if options are provided if (attributeOptions && attributeOptions.length > 0) { attributeConfig.validations = { options: { options: attributeOptions }, }; } - userProfile.attributes.push(attributeConfig); - // Update User Profile await kcAdminClient.users.updateProfile(userProfile); console.log( `${attributeName} attribute added to User Profile successfully with admin edit permissions.` @@ -114,14 +104,14 @@ const main = async () => { const clientInternalId = client[0].id; invariant(clientInternalId, "Client internal ID is required"); - // Check if the mapper already exists - const mappers = await kcAdminClient.clients.listProtocolMappers({ id: clientInternalId }); + const mappers = await kcAdminClient.clients.listProtocolMappers({ + id: clientInternalId, + }); const existingMapper = mappers.find((mapper) => mapper.name === mapperName); if (existingMapper) { console.log(`${mapperName} already exists.`); } else { - // Create the protocol mapper await kcAdminClient.clients.addProtocolMapper( { id: clientInternalId }, { diff --git a/keycloak/scripts/add-audience-mapper-to-scope.ts b/keycloak/scripts/add-audience-mapper-to-scope.ts index e60f382..2174b19 100755 --- a/keycloak/scripts/add-audience-mapper-to-scope.ts +++ b/keycloak/scripts/add-audience-mapper-to-scope.ts @@ -1,80 +1,75 @@ #!/usr/bin/env tsx -import KcAdminClient from '@keycloak/keycloak-admin-client'; -import invariant from 'tiny-invariant'; +import KcAdminClient from "@keycloak/keycloak-admin-client"; +import invariant from "tiny-invariant"; const main = async () => { - const KEYCLOAK_HOST = process.env.KEYCLOAK_HOST; - const KEYCLOAK_ADMIN_USER = process.env.KEYCLOAK_ADMIN_USER; - const KEYCLOAK_ADMIN_PASSWORD = process.env.KEYCLOAK_ADMIN_PASSWORD; - const realm = process.env.KEYCLOAK_REALM; - const scopeName = process.env.SCOPE_NAME; - const audience = process.env.KEYCLOAK_AUDIENCE; + const KEYCLOAK_HOST = process.env.KEYCLOAK_HOST; + const KEYCLOAK_ADMIN_USER = process.env.KEYCLOAK_ADMIN_USER; + const KEYCLOAK_ADMIN_PASSWORD = process.env.KEYCLOAK_ADMIN_PASSWORD; + const realm = process.env.KEYCLOAK_REALM; + const scopeName = process.env.SCOPE_NAME; + const audience = process.env.KEYCLOAK_AUDIENCE; - invariant(KEYCLOAK_HOST, 'KEYCLOAK_HOST environment variable is required'); - invariant(KEYCLOAK_ADMIN_USER, 'KEYCLOAK_ADMIN_USER environment variable is required'); - invariant(KEYCLOAK_ADMIN_PASSWORD, 'KEYCLOAK_ADMIN_PASSWORD environment variable is required'); - invariant(realm, 'KEYCLOAK_REALM environment variable is required'); - invariant(scopeName, 'SCOPE_NAME environment variable is required'); - invariant(audience, 'KEYCLOAK_AUDIENCE environment variable is required'); + invariant(KEYCLOAK_HOST, "KEYCLOAK_HOST environment variable is required"); + invariant(KEYCLOAK_ADMIN_USER, "KEYCLOAK_ADMIN_USER environment variable is required"); + invariant(KEYCLOAK_ADMIN_PASSWORD, "KEYCLOAK_ADMIN_PASSWORD environment variable is required"); + invariant(realm, "KEYCLOAK_REALM environment variable is required"); + invariant(scopeName, "SCOPE_NAME environment variable is required"); + invariant(audience, "KEYCLOAK_AUDIENCE environment variable is required"); - const kcAdminClient = new KcAdminClient({ - baseUrl: `https://${KEYCLOAK_HOST}`, - realmName: 'master', + const kcAdminClient = new KcAdminClient({ + baseUrl: `https://${KEYCLOAK_HOST}`, + realmName: "master", + }); + + try { + await kcAdminClient.auth({ + username: KEYCLOAK_ADMIN_USER, + password: KEYCLOAK_ADMIN_PASSWORD, + grantType: "password", + clientId: "admin-cli", }); - try { - await kcAdminClient.auth({ - username: KEYCLOAK_ADMIN_USER, - password: KEYCLOAK_ADMIN_PASSWORD, - grantType: 'password', - clientId: 'admin-cli', - }); + console.log("Authentication successful."); - console.log('Authentication successful.'); + kcAdminClient.setConfig({ + realmName: realm, + }); - // Set target realm - kcAdminClient.setConfig({ - realmName: realm, - }); - - // Find the client scope - const clientScopes = await kcAdminClient.clientScopes.find(); - const scope = clientScopes.find(s => s.name === scopeName); - if (!scope) { - throw new Error(`Client scope '${scopeName}' not found`); - } - - invariant(scope.id, 'Client scope ID is not set'); - - // Check if mapper already exists - const mapperName = `aud-mapper-${audience}`; - const existingMappers = await kcAdminClient.clientScopes.listProtocolMappers({ id: scope.id }); - - if (existingMappers.some((mapper) => mapper.name === mapperName)) { - console.warn(`Audience mapper '${mapperName}' already exists in scope '${scopeName}'.`); - return; - } - - // Create audience mapper - const audienceMapper = { - name: mapperName, - protocol: 'openid-connect', - protocolMapper: 'oidc-audience-mapper', - config: { - 'included.client.audience': audience, - 'id.token.claim': 'false', - 'access.token.claim': 'true', - }, - }; - - await kcAdminClient.clientScopes.addProtocolMapper({ id: scope.id }, audienceMapper); - console.log(`Audience mapper '${mapperName}' added to client scope '${scopeName}'.`); - - } catch (error) { - console.error('Error adding audience mapper to scope:', error); - process.exit(1); + const clientScopes = await kcAdminClient.clientScopes.find(); + const scope = clientScopes.find((s) => s.name === scopeName); + if (!scope) { + throw new Error(`Client scope '${scopeName}' not found`); } + + invariant(scope.id, "Client scope ID is not set"); + + const mapperName = `aud-mapper-${audience}`; + const existingMappers = await kcAdminClient.clientScopes.listProtocolMappers({ id: scope.id }); + + if (existingMappers.some((mapper) => mapper.name === mapperName)) { + console.warn(`Audience mapper '${mapperName}' already exists in scope '${scopeName}'.`); + return; + } + + const audienceMapper = { + name: mapperName, + protocol: "openid-connect", + protocolMapper: "oidc-audience-mapper", + config: { + "included.client.audience": audience, + "id.token.claim": "false", + "access.token.claim": "true", + }, + }; + + await kcAdminClient.clientScopes.addProtocolMapper({ id: scope.id }, audienceMapper); + console.log(`Audience mapper '${mapperName}' added to client scope '${scopeName}'.`); + } catch (error) { + console.error("Error adding audience mapper to scope:", error); + process.exit(1); + } }; main(); diff --git a/keycloak/scripts/add-audience-mapper.ts b/keycloak/scripts/add-audience-mapper.ts index 55081ac..f00250d 100644 --- a/keycloak/scripts/add-audience-mapper.ts +++ b/keycloak/scripts/add-audience-mapper.ts @@ -55,7 +55,9 @@ const main = async () => { }, }; - const existingMappers = await kcAdminClient.clients.listProtocolMappers({ id: client.id }); + const existingMappers = await kcAdminClient.clients.listProtocolMappers({ + id: client.id, + }); if (existingMappers.some((mapper) => mapper.name === mapperName)) { console.warn(`Audience Mapper '${mapperName}' already exists for the client.`); diff --git a/keycloak/scripts/add-client-roles-mapper.ts b/keycloak/scripts/add-client-roles-mapper.ts index 84aca88..4d4bc23 100644 --- a/keycloak/scripts/add-client-roles-mapper.ts +++ b/keycloak/scripts/add-client-roles-mapper.ts @@ -35,12 +35,10 @@ async function main() { clientId: "admin-cli", }); - // Set realm to work with kcAdminClient.setConfig({ realmName, }); - // Find the client const clients = await kcAdminClient.clients.find({ clientId }); if (clients.length === 0) { throw new Error(`Client '${clientId}' not found in realm '${realmName}'`); @@ -49,8 +47,9 @@ async function main() { const client = clients[0]; const clientInternalId = client.id!; - // Check if the mapper already exists - const mappers = await kcAdminClient.clients.listProtocolMappers({ id: clientInternalId }); + const mappers = await kcAdminClient.clients.listProtocolMappers({ + id: clientInternalId, + }); const existingMapper = mappers.find((mapper) => mapper.name === mapperName); if (existingMapper) { @@ -58,7 +57,6 @@ async function main() { return; } - // Create the client roles protocol mapper await kcAdminClient.clients.addProtocolMapper( { id: clientInternalId }, { @@ -71,13 +69,15 @@ async function main() { "access.token.claim": "true", "claim.name": claimName, "jsonType.label": "String", - "multivalued": "true", + multivalued: "true", "usermodel.clientRoleMapping.clientId": clientId, }, } ); - console.log(`✓ Client roles mapper '${mapperName}' created for client '${clientId}' in realm '${realmName}'`); + console.log( + `✓ Client roles mapper '${mapperName}' created for client '${clientId}' in realm '${realmName}'` + ); console.log(` Claim name: ${claimName}`); console.log(` Maps client roles from '${clientId}' to JWT token`); } catch (error) { @@ -86,4 +86,4 @@ async function main() { } } -main().catch(console.error); \ No newline at end of file +main().catch(console.error); diff --git a/keycloak/scripts/add-client-roles-to-profile-scope.ts b/keycloak/scripts/add-client-roles-to-profile-scope.ts index bb3edb4..6622f47 100644 --- a/keycloak/scripts/add-client-roles-to-profile-scope.ts +++ b/keycloak/scripts/add-client-roles-to-profile-scope.ts @@ -26,38 +26,31 @@ async function main() { kcAdminClient.setConfig({ realmName: realm }); try { - // Find the profile client scope const clientScopes = await kcAdminClient.clientScopes.find({ realm }); - const profileScope = clientScopes.find(scope => scope.name === 'profile'); + const profileScope = clientScopes.find((scope) => scope.name === "profile"); if (!profileScope) { throw new Error("Profile client scope not found"); } - console.log(`Found profile scope: ${profileScope.id}`); - // Check existing mappers in profile scope const existingMappers = await kcAdminClient.clientScopes.listProtocolMappers({ realm, id: profileScope.id!, }); - console.log("Existing mappers in profile scope:"); - existingMappers.forEach(mapper => { + existingMappers.forEach((mapper) => { console.log(`- ${mapper.name} (${mapper.protocolMapper})`); }); - // Check if our client roles mapper already exists in profile scope - const clientRolesMapper = existingMappers.find(m => - m.config?.['usermodel.clientRoleMapping.clientId'] === clientId + const clientRolesMapper = existingMappers.find( + (m) => m.config?.["usermodel.clientRoleMapping.clientId"] === clientId ); - if (clientRolesMapper) { console.log(`Client roles mapper already exists in profile scope: ${clientRolesMapper.name}`); } else { console.log(`Adding ${clientId} client roles mapper to profile scope...`); - // Add client roles mapper to profile scope await kcAdminClient.clientScopes.addProtocolMapper( { realm, id: profileScope.id! }, { @@ -70,7 +63,7 @@ async function main() { "access.token.claim": "true", "claim.name": claimName, "jsonType.label": "String", - "multivalued": "true", + multivalued: "true", "usermodel.clientRoleMapping.clientId": clientId, }, } @@ -78,11 +71,10 @@ async function main() { console.log(`✓ Added ${clientId} client roles mapper to profile scope`); } - } catch (error) { console.error(`Error: ${error}`); process.exit(1); } } -main().catch(console.error); \ No newline at end of file +main().catch(console.error); diff --git a/keycloak/scripts/add-groups-mapper.ts b/keycloak/scripts/add-groups-mapper.ts index afd0a88..660720c 100644 --- a/keycloak/scripts/add-groups-mapper.ts +++ b/keycloak/scripts/add-groups-mapper.ts @@ -33,43 +33,41 @@ const main = async () => { kcAdminClient.setConfig({ realmName }); - // Find the client const clients = await kcAdminClient.clients.find({ clientId }); - const client = clients.find(c => c.clientId === clientId); - + const client = clients.find((c) => c.clientId === clientId); if (!client) { throw new Error(`Client '${clientId}' not found`); } - // Check if groups mapper already exists const existingMappers = await kcAdminClient.clients.listProtocolMappers({ id: client.id!, }); - const groupsMapper = existingMappers.find(mapper => - mapper.name === "groups" || mapper.config?.["claim.name"] === "groups" + const groupsMapper = existingMappers.find( + (mapper) => mapper.name === "groups" || mapper.config?.["claim.name"] === "groups" ); - if (groupsMapper) { console.log("Groups mapper already exists for the client."); return; } - // Add groups mapper - await kcAdminClient.clients.addProtocolMapper({ - id: client.id!, - }, { - name: "groups", - protocol: "openid-connect", - protocolMapper: "oidc-group-membership-mapper", - config: { - "claim.name": "groups", - "full.path": "false", - "id.token.claim": "true", - "access.token.claim": "true", - "userinfo.token.claim": "true", + await kcAdminClient.clients.addProtocolMapper( + { + id: client.id!, }, - }); + { + name: "groups", + protocol: "openid-connect", + protocolMapper: "oidc-group-membership-mapper", + config: { + "claim.name": "groups", + "full.path": "false", + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true", + }, + } + ); console.log("Groups mapper added to the client."); } catch (error) { @@ -79,4 +77,4 @@ const main = async () => { } }; -main(); \ No newline at end of file +main(); diff --git a/keycloak/scripts/add-minio-policy.ts b/keycloak/scripts/add-minio-policy.ts index c035c9a..61ab78b 100644 --- a/keycloak/scripts/add-minio-policy.ts +++ b/keycloak/scripts/add-minio-policy.ts @@ -34,15 +34,12 @@ const main = async () => { }); console.log("Authentication successful."); - // Set realm to work with kcAdminClient.setConfig({ realmName, }); - // Get current User Profile configuration const userProfile = await kcAdminClient.users.getProfile(); - // Check if minioPolicy attribute already exists const existingAttribute = userProfile.attributes?.find( (attr: any) => attr.name === "minioPolicy" ); @@ -50,11 +47,9 @@ const main = async () => { if (existingAttribute) { console.log("minioPolicy attribute already exists in User Profile."); } else { - // Add minioPolicy attribute to User Profile with proper permissions if (!userProfile.attributes) { userProfile.attributes = []; } - userProfile.attributes.push({ name: "minioPolicy", displayName: "MinIO Policy", @@ -67,15 +62,15 @@ const main = async () => { }, }); - // Update User Profile await kcAdminClient.users.updateProfile(userProfile); console.log( "minioPolicy attribute added to User Profile successfully with admin edit permissions." ); } - // Create protocol mapper for the minioPolicy attribute if it doesn't exist - const minioClient = await kcAdminClient.clients.find({ clientId: minioClientId }); + const minioClient = await kcAdminClient.clients.find({ + clientId: minioClientId, + }); if (minioClient.length === 0) { console.error(`Client '${minioClientId}' not found.`); // eslint-disable-next-line unicorn/no-process-exit @@ -84,14 +79,14 @@ const main = async () => { const clientId = minioClient[0].id; invariant(clientId, "Client ID is required"); - // Check if the mapper already exists - const mappers = await kcAdminClient.clients.listProtocolMappers({ id: clientId }); + const mappers = await kcAdminClient.clients.listProtocolMappers({ + id: clientId, + }); const existingMapper = mappers.find((mapper) => mapper.name === "MinIO Policy"); if (existingMapper) { console.log("MinIO Policy mapper already exists."); } else { - // Create the protocol mapper await kcAdminClient.clients.addProtocolMapper( { id: clientId }, { diff --git a/keycloak/scripts/add-scope-to-client.ts b/keycloak/scripts/add-scope-to-client.ts index 8eb2a4e..c3bbda3 100644 --- a/keycloak/scripts/add-scope-to-client.ts +++ b/keycloak/scripts/add-scope-to-client.ts @@ -1,69 +1,64 @@ #!/usr/bin/env tsx -import KcAdminClient from '@keycloak/keycloak-admin-client'; -import invariant from 'tiny-invariant'; +import KcAdminClient from "@keycloak/keycloak-admin-client"; +import invariant from "tiny-invariant"; const main = async () => { - const KEYCLOAK_HOST = process.env.KEYCLOAK_HOST; - const KEYCLOAK_ADMIN_USER = process.env.KEYCLOAK_ADMIN_USER; - const KEYCLOAK_ADMIN_PASSWORD = process.env.KEYCLOAK_ADMIN_PASSWORD; - const realm = process.env.KEYCLOAK_REALM; - const clientId = process.env.KEYCLOAK_CLIENT_ID; - const scopeName = process.env.SCOPE_NAME; + const KEYCLOAK_HOST = process.env.KEYCLOAK_HOST; + const KEYCLOAK_ADMIN_USER = process.env.KEYCLOAK_ADMIN_USER; + const KEYCLOAK_ADMIN_PASSWORD = process.env.KEYCLOAK_ADMIN_PASSWORD; + const realm = process.env.KEYCLOAK_REALM; + const clientId = process.env.KEYCLOAK_CLIENT_ID; + const scopeName = process.env.SCOPE_NAME; - invariant(KEYCLOAK_HOST, 'KEYCLOAK_HOST environment variable is required'); - invariant(KEYCLOAK_ADMIN_USER, 'KEYCLOAK_ADMIN_USER environment variable is required'); - invariant(KEYCLOAK_ADMIN_PASSWORD, 'KEYCLOAK_ADMIN_PASSWORD environment variable is required'); - invariant(realm, 'KEYCLOAK_REALM environment variable is required'); - invariant(clientId, 'KEYCLOAK_CLIENT_ID environment variable is required'); - invariant(scopeName, 'SCOPE_NAME environment variable is required'); + invariant(KEYCLOAK_HOST, "KEYCLOAK_HOST environment variable is required"); + invariant(KEYCLOAK_ADMIN_USER, "KEYCLOAK_ADMIN_USER environment variable is required"); + invariant(KEYCLOAK_ADMIN_PASSWORD, "KEYCLOAK_ADMIN_PASSWORD environment variable is required"); + invariant(realm, "KEYCLOAK_REALM environment variable is required"); + invariant(clientId, "KEYCLOAK_CLIENT_ID environment variable is required"); + invariant(scopeName, "SCOPE_NAME environment variable is required"); - const kcAdminClient = new KcAdminClient({ - baseUrl: `https://${KEYCLOAK_HOST}`, - realmName: 'master', + const kcAdminClient = new KcAdminClient({ + baseUrl: `https://${KEYCLOAK_HOST}`, + realmName: "master", + }); + + try { + await kcAdminClient.auth({ + username: KEYCLOAK_ADMIN_USER, + password: KEYCLOAK_ADMIN_PASSWORD, + grantType: "password", + clientId: "admin-cli", }); - try { - await kcAdminClient.auth({ - username: KEYCLOAK_ADMIN_USER, - password: KEYCLOAK_ADMIN_PASSWORD, - grantType: 'password', - clientId: 'admin-cli', - }); + console.log("Authentication successful."); - console.log('Authentication successful.'); + kcAdminClient.setConfig({ + realmName: realm, + }); - // Set target realm - kcAdminClient.setConfig({ - realmName: realm, - }); - - // Find the client - const clients = await kcAdminClient.clients.find({ clientId }); - if (clients.length === 0) { - throw new Error(`Client '${clientId}' not found`); - } - const client = clients[0]; - - // Find the client scope - const clientScopes = await kcAdminClient.clientScopes.find(); - const scope = clientScopes.find(s => s.name === scopeName); - if (!scope) { - throw new Error(`Client scope '${scopeName}' not found`); - } - - // Add scope to client as default scope - await kcAdminClient.clients.addDefaultClientScope({ - id: client.id!, - clientScopeId: scope.id!, - }); - - console.log(`Client scope '${scopeName}' added to client '${clientId}' as default scope.`); - - } catch (error) { - console.error('Error adding scope to client:', error); - process.exit(1); + const clients = await kcAdminClient.clients.find({ clientId }); + if (clients.length === 0) { + throw new Error(`Client '${clientId}' not found`); } + const client = clients[0]; + + const clientScopes = await kcAdminClient.clientScopes.find(); + const scope = clientScopes.find((s) => s.name === scopeName); + if (!scope) { + throw new Error(`Client scope '${scopeName}' not found`); + } + + await kcAdminClient.clients.addDefaultClientScope({ + id: client.id!, + clientScopeId: scope.id!, + }); + + console.log(`Client scope '${scopeName}' added to client '${clientId}' as default scope.`); + } catch (error) { + console.error("Error adding scope to client:", error); + process.exit(1); + } }; -main(); \ No newline at end of file +main(); diff --git a/keycloak/scripts/add-user-to-client-role.ts b/keycloak/scripts/add-user-to-client-role.ts index 8a40e14..4cbe943 100755 --- a/keycloak/scripts/add-user-to-client-role.ts +++ b/keycloak/scripts/add-user-to-client-role.ts @@ -27,18 +27,14 @@ async function main() { invariant(clientId, "KEYCLOAK_CLIENT_ID is required"); invariant(roleName, "KEYCLOAK_ROLE_NAME is required"); - // Find the user const users = await kcAdminClient.users.find({ realm, username }); if (users.length === 0) { throw new Error(`User '${username}' not found in realm '${realm}'`); } - const user = users[0]; - // Find the client const clients = await kcAdminClient.clients.find({ realm, clientId }); - if (clients.length === 0) { throw new Error(`Client '${clientId}' not found in realm '${realm}'`); } @@ -60,35 +56,28 @@ async function main() { }); console.error( `Available roles for client '${clientId}':`, - allRoles.map((r) => r.name), + allRoles.map((r) => r.name) ); throw new Error( - `Role '${roleName}' not found for client '${clientId}' in realm '${realm}'. Available roles: ${allRoles.map((r) => r.name).join(", ")}`, + `Role '${roleName}' not found for client '${clientId}' in realm '${realm}'. Available roles: ${allRoles.map((r) => r.name).join(", ")}` ); } if (!role) { - throw new Error( - `Role '${roleName}' not found for client '${clientId}' in realm '${realm}'`, - ); + throw new Error(`Role '${roleName}' not found for client '${clientId}' in realm '${realm}'`); } - // Check if user already has this role const existingRoles = await kcAdminClient.users.listClientRoleMappings({ realm, id: user.id!, clientUniqueId: client.id!, }); - const hasRole = existingRoles.some((r) => r.name === roleName); if (hasRole) { - console.log( - `User '${username}' already has role '${roleName}' for client '${clientId}'`, - ); + console.log(`User '${username}' already has role '${roleName}' for client '${clientId}'`); return; } - // Add role to user await kcAdminClient.users.addClientRoleMappings({ realm, id: user.id!, @@ -102,7 +91,7 @@ async function main() { }); console.log( - `✓ Role '${roleName}' assigned to user '${username}' for client '${clientId}' in realm '${realm}'`, + `✓ Role '${roleName}' assigned to user '${username}' for client '${clientId}' in realm '${realm}'` ); } diff --git a/keycloak/scripts/add-user-to-group.ts b/keycloak/scripts/add-user-to-group.ts index aa02e18..b3f9999 100644 --- a/keycloak/scripts/add-user-to-group.ts +++ b/keycloak/scripts/add-user-to-group.ts @@ -36,30 +36,26 @@ const main = async () => { kcAdminClient.setConfig({ realmName }); - // Find user const users = await kcAdminClient.users.find({ username }); - const user = users.find(u => u.username === username); + const user = users.find((u) => u.username === username); if (!user) { throw new Error(`User '${username}' not found`); } - // Find group const groups = await kcAdminClient.groups.find({ search: groupName }); - const group = groups.find(g => g.name === groupName); + const group = groups.find((g) => g.name === groupName); if (!group) { throw new Error(`Group '${groupName}' not found`); } - // Check if user is already in group const userGroups = await kcAdminClient.users.listGroups({ id: user.id! }); - const isAlreadyMember = userGroups.some(ug => ug.id === group.id); - + const isAlreadyMember = userGroups.some((ug) => ug.id === group.id); + if (isAlreadyMember) { console.log(`User '${username}' is already a member of group '${groupName}'`); return; } - // Add user to group await kcAdminClient.users.addToGroup({ id: user.id!, groupId: group.id!, @@ -73,4 +69,4 @@ const main = async () => { } }; -main(); \ No newline at end of file +main(); diff --git a/keycloak/scripts/check-mapper-details.ts b/keycloak/scripts/check-mapper-details.ts index 593658b..4621eac 100644 --- a/keycloak/scripts/check-mapper-details.ts +++ b/keycloak/scripts/check-mapper-details.ts @@ -23,15 +23,11 @@ async function main() { invariant(clientId, "KEYCLOAK_CLIENT_ID is required"); try { - // Find the client const clients = await kcAdminClient.clients.find({ realm, clientId }); if (clients.length === 0) { throw new Error(`Client '${clientId}' not found in realm '${realm}'`); } - const client = clients[0]; - - // Get all protocol mappers with full details const mappers = await kcAdminClient.clients.listProtocolMappers({ realm, id: client.id!, @@ -50,29 +46,25 @@ async function main() { } }); - // Check client scope assignments console.log(`\n=== Client Scope Assignments ===`); - // Get default client scopes const defaultScopes = await kcAdminClient.clients.listDefaultClientScopes({ realm, id: client.id!, }); - console.log(`Default scopes: ${defaultScopes.map(s => s.name).join(', ')}`); + console.log(`Default scopes: ${defaultScopes.map((s) => s.name).join(", ")}`); - // Get optional client scopes const optionalScopes = await kcAdminClient.clients.listOptionalClientScopes({ realm, id: client.id!, }); - console.log(`Optional scopes: ${optionalScopes.map(s => s.name).join(', ')}`); - + console.log(`Optional scopes: ${optionalScopes.map((s) => s.name).join(", ")}`); } catch (error) { console.error(`Error: ${error}`); process.exit(1); } } -main().catch(console.error); \ No newline at end of file +main().catch(console.error); diff --git a/keycloak/scripts/client-exists.ts b/keycloak/scripts/client-exists.ts index c6a9555..8ba25ae 100644 --- a/keycloak/scripts/client-exists.ts +++ b/keycloak/scripts/client-exists.ts @@ -23,15 +23,14 @@ async function main() { invariant(clientId, "KEYCLOAK_CLIENT_ID is required"); try { - // Find the client by clientId const clients = await kcAdminClient.clients.find({ realm, clientId }); if (clients.length > 0) { console.log(`Client '${clientId}' exists in realm '${realm}'`); - process.exit(0); // Success - client exists + process.exit(0); } else { console.log(`Client '${clientId}' does not exist in realm '${realm}'`); - process.exit(1); // Client doesn't exist + process.exit(1); } } catch (error) { console.error(`Error checking client existence: ${error}`); @@ -39,4 +38,4 @@ async function main() { } } -main().catch(console.error); \ No newline at end of file +main().catch(console.error); diff --git a/keycloak/scripts/create-client-role.ts b/keycloak/scripts/create-client-role.ts index 400f5c2..22ad55f 100755 --- a/keycloak/scripts/create-client-role.ts +++ b/keycloak/scripts/create-client-role.ts @@ -24,16 +24,12 @@ async function main() { invariant(clientId, "KEYCLOAK_CLIENT_ID is required"); invariant(roleName, "KEYCLOAK_ROLE_NAME is required"); - // Find the client by clientId const clients = await kcAdminClient.clients.find({ realm, clientId }); - if (clients.length === 0) { throw new Error(`Client '${clientId}' not found in realm '${realm}'`); } - const client = clients[0]; - // Check if role already exists try { const existingRole = await kcAdminClient.clients.findRole({ realm, @@ -49,17 +45,13 @@ async function main() { console.log(`Role '${roleName}' doesn't exist, creating it...`); } - // Create the client role await kcAdminClient.clients.createRole({ realm, id: client.id!, name: roleName, }); - console.log( - `✓ Client role '${roleName}' created for client '${clientId}' in realm '${realm}'`, - ); + console.log(`✓ Client role '${roleName}' created for client '${clientId}' in realm '${realm}'`); } main().catch(console.error); - diff --git a/keycloak/scripts/create-client-scope.ts b/keycloak/scripts/create-client-scope.ts index 8613de0..e95b9cd 100644 --- a/keycloak/scripts/create-client-scope.ts +++ b/keycloak/scripts/create-client-scope.ts @@ -1,65 +1,63 @@ #!/usr/bin/env tsx -import KcAdminClient from '@keycloak/keycloak-admin-client'; -import invariant from 'tiny-invariant'; +import KcAdminClient from "@keycloak/keycloak-admin-client"; +import invariant from "tiny-invariant"; const main = async () => { - const KEYCLOAK_HOST = process.env.KEYCLOAK_HOST; - const KEYCLOAK_ADMIN_USER = process.env.KEYCLOAK_ADMIN_USER; - const KEYCLOAK_ADMIN_PASSWORD = process.env.KEYCLOAK_ADMIN_PASSWORD; - const realm = process.env.KEYCLOAK_REALM; - const scopeName = process.env.SCOPE_NAME; - const description = process.env.DESCRIPTION || ''; + const KEYCLOAK_HOST = process.env.KEYCLOAK_HOST; + const KEYCLOAK_ADMIN_USER = process.env.KEYCLOAK_ADMIN_USER; + const KEYCLOAK_ADMIN_PASSWORD = process.env.KEYCLOAK_ADMIN_PASSWORD; + const realm = process.env.KEYCLOAK_REALM; + const scopeName = process.env.SCOPE_NAME; + const description = process.env.DESCRIPTION || ""; - invariant(KEYCLOAK_HOST, 'KEYCLOAK_HOST environment variable is required'); - invariant(KEYCLOAK_ADMIN_USER, 'KEYCLOAK_ADMIN_USER environment variable is required'); - invariant(KEYCLOAK_ADMIN_PASSWORD, 'KEYCLOAK_ADMIN_PASSWORD environment variable is required'); - invariant(realm, 'KEYCLOAK_REALM environment variable is required'); - invariant(scopeName, 'SCOPE_NAME environment variable is required'); + invariant(KEYCLOAK_HOST, "KEYCLOAK_HOST environment variable is required"); + invariant(KEYCLOAK_ADMIN_USER, "KEYCLOAK_ADMIN_USER environment variable is required"); + invariant(KEYCLOAK_ADMIN_PASSWORD, "KEYCLOAK_ADMIN_PASSWORD environment variable is required"); + invariant(realm, "KEYCLOAK_REALM environment variable is required"); + invariant(scopeName, "SCOPE_NAME environment variable is required"); - const kcAdminClient = new KcAdminClient({ - baseUrl: `https://${KEYCLOAK_HOST}`, - realmName: 'master', + const kcAdminClient = new KcAdminClient({ + baseUrl: `https://${KEYCLOAK_HOST}`, + realmName: "master", + }); + + try { + await kcAdminClient.auth({ + username: KEYCLOAK_ADMIN_USER, + password: KEYCLOAK_ADMIN_PASSWORD, + grantType: "password", + clientId: "admin-cli", }); - try { - await kcAdminClient.auth({ - username: KEYCLOAK_ADMIN_USER, - password: KEYCLOAK_ADMIN_PASSWORD, - grantType: 'password', - clientId: 'admin-cli', - }); + console.log("Authentication successful."); - console.log('Authentication successful.'); + kcAdminClient.setConfig({ + realmName: realm, + }); - // Set target realm - kcAdminClient.setConfig({ - realmName: realm, - }); + const existingScopes = await kcAdminClient.clientScopes.find(); + const existingScope = existingScopes.find((scope) => scope.name === scopeName); - // Check if scope already exists - const existingScopes = await kcAdminClient.clientScopes.find(); - const existingScope = existingScopes.find(scope => scope.name === scopeName); - - if (existingScope) { - console.log(`Client scope '${scopeName}' already exists.`); - return; - } - - // Create client scope - const result = await kcAdminClient.clientScopes.create({ - name: scopeName, - description: description || `${scopeName} scope`, - protocol: 'openid-connect', - includeInTokenScope: true, - }); - - console.log(`Client scope '${scopeName}' created successfully with ID: ${result.id}`); - - } catch (error) { - console.error('Error creating client scope:', error); - process.exit(1); + if (existingScope) { + console.log(`Client scope '${scopeName}' already exists.`); + return; } + + const result = await kcAdminClient.clientScopes.create({ + name: scopeName, + description: description || `${scopeName} scope`, + protocol: "openid-connect", + attributes: { + "include.in.token.scope": "true", + }, + }); + + console.log(`Client scope '${scopeName}' created successfully with ID: ${result.id}`); + } catch (error) { + console.error("Error creating client scope:", error); + process.exit(1); + } }; -main(); \ No newline at end of file +main(); diff --git a/keycloak/scripts/create-client.ts b/keycloak/scripts/create-client.ts index ff1c8e2..3163841 100644 --- a/keycloak/scripts/create-client.ts +++ b/keycloak/scripts/create-client.ts @@ -22,7 +22,7 @@ const main = async () => { const redirectUrl = process.env.KEYCLOAK_REDIRECT_URL; invariant(redirectUrl, "KEYCLOAK_REDIRECT_URL environment variable is required"); - const redirectUris = redirectUrl.split(',').map(url => url.trim()); + const redirectUris = redirectUrl.split(",").map((url) => url.trim()); const sessionIdle = process.env.KEYCLOAK_CLIENT_SESSION_IDLE; const sessionMax = process.env.KEYCLOAK_CLIENT_SESSION_MAX; @@ -53,59 +53,61 @@ const main = async () => { return; } - const isPublicClient = !clientSecret || clientSecret === ''; + const isPublicClient = !clientSecret || clientSecret === ""; const clientConfig: any = { clientId: clientId, secret: clientSecret, enabled: true, redirectUris: redirectUris, publicClient: isPublicClient, - directAccessGrantsEnabled: directAccessGrants === 'true', + directAccessGrantsEnabled: directAccessGrants === "true", }; // Configure PKCE based on environment variable // KEYCLOAK_CLIENT_PKCE_METHOD can be: 'S256', 'plain', or unset/empty (no PKCE) clientConfig.attributes = {}; - if (pkceMethod && (pkceMethod === 'S256' || pkceMethod === 'plain')) { - clientConfig.attributes['pkce.code.challenge.method'] = pkceMethod; + if (pkceMethod && (pkceMethod === "S256" || pkceMethod === "plain")) { + clientConfig.attributes["pkce.code.challenge.method"] = pkceMethod; console.log(`Setting PKCE Code Challenge Method to ${pkceMethod}`); - } else if (pkceMethod && pkceMethod !== '') { - console.warn(`Invalid PKCE method '${pkceMethod}'. Valid options: S256, plain, or empty for no PKCE`); - console.log('Creating client without PKCE'); + } else if (pkceMethod && pkceMethod !== "") { + console.warn( + `Invalid PKCE method '${pkceMethod}'. Valid options: S256, plain, or empty for no PKCE` + ); + console.log("Creating client without PKCE"); } else { - console.log('Creating client without PKCE'); + console.log("Creating client without PKCE"); } // Add session timeout settings if provided - if (sessionIdle && sessionIdle !== '') { - clientConfig.attributes['client.session.idle.timeout'] = sessionIdle; + if (sessionIdle && sessionIdle !== "") { + clientConfig.attributes["client.session.idle.timeout"] = sessionIdle; console.log(`Setting Client Session Idle Timeout: ${sessionIdle}`); } - if (sessionMax && sessionMax !== '') { - clientConfig.attributes['client.session.max.lifespan'] = sessionMax; + if (sessionMax && sessionMax !== "") { + clientConfig.attributes["client.session.max.lifespan"] = sessionMax; console.log(`Setting Client Session Max Lifespan: ${sessionMax}`); } // Add post logout redirect URIs if provided - if (postLogoutRedirectUris && postLogoutRedirectUris !== '') { + if (postLogoutRedirectUris && postLogoutRedirectUris !== "") { // Split comma-separated URIs and set as array in attributes using ## separator (Keycloak format) - const postLogoutUris = postLogoutRedirectUris.split(',').map(uri => uri.trim()); + const postLogoutUris = postLogoutRedirectUris.split(",").map((uri) => uri.trim()); clientConfig.attributes = clientConfig.attributes || {}; - clientConfig.attributes['post.logout.redirect.uris'] = postLogoutUris.join('##'); - console.log(`Setting Post Logout Redirect URIs: ${postLogoutUris.join(', ')}`); + clientConfig.attributes["post.logout.redirect.uris"] = postLogoutUris.join("##"); + console.log(`Setting Post Logout Redirect URIs: ${postLogoutUris.join(", ")}`); } // Add access token lifespan if provided - if (accessTokenLifespan && accessTokenLifespan !== '') { + if (accessTokenLifespan && accessTokenLifespan !== "") { clientConfig.attributes = clientConfig.attributes || {}; - clientConfig.attributes['access.token.lifespan'] = accessTokenLifespan; + clientConfig.attributes["access.token.lifespan"] = accessTokenLifespan; console.log(`Setting Access Token Lifespan: ${accessTokenLifespan} seconds`); } - if (directAccessGrants === 'true') { - console.log('Enabling Direct Access Grants (Resource Owner Password Credentials)'); + if (directAccessGrants === "true") { + console.log("Enabling Direct Access Grants (Resource Owner Password Credentials)"); } const createdClient = await kcAdminClient.clients.create(clientConfig); diff --git a/keycloak/scripts/create-group.ts b/keycloak/scripts/create-group.ts index 8b87f2a..d551622 100644 --- a/keycloak/scripts/create-group.ts +++ b/keycloak/scripts/create-group.ts @@ -36,39 +36,41 @@ const main = async () => { kcAdminClient.setConfig({ realmName }); - // Check if group already exists - const existingGroups = await kcAdminClient.groups.find({ search: groupName }); - const existingGroup = existingGroups.find(group => group.name === groupName); - + const existingGroups = await kcAdminClient.groups.find({ + search: groupName, + }); + const existingGroup = existingGroups.find((group) => group.name === groupName); + if (existingGroup) { console.log(`Group '${groupName}' already exists with ID: ${existingGroup.id}`); return; } - // Find parent group if specified let parentGroupId: string | undefined; if (parentGroupName) { - const parentGroups = await kcAdminClient.groups.find({ search: parentGroupName }); - const parentGroup = parentGroups.find(group => group.name === parentGroupName); + const parentGroups = await kcAdminClient.groups.find({ + search: parentGroupName, + }); + const parentGroup = parentGroups.find((group) => group.name === parentGroupName); if (!parentGroup) { throw new Error(`Parent group '${parentGroupName}' not found`); } parentGroupId = parentGroup.id; } - // Create group payload const groupPayload = { name: groupName, - ...(groupDescription && { attributes: { description: [groupDescription] } }), + ...(groupDescription && { + attributes: { description: [groupDescription] }, + }), }; - // Create group - const group = parentGroupId + const group = parentGroupId ? await kcAdminClient.groups.createChildGroup({ id: parentGroupId }, groupPayload) : await kcAdminClient.groups.create(groupPayload); - + console.log(`Group '${groupName}' created successfully with ID: ${group.id}`); - + if (parentGroupName) { console.log(`Group '${groupName}' created as child of '${parentGroupName}'`); } @@ -79,4 +81,4 @@ const main = async () => { } }; -main(); \ No newline at end of file +main(); diff --git a/keycloak/scripts/create-realm.ts b/keycloak/scripts/create-realm.ts index eb00e38..8f6de83 100644 --- a/keycloak/scripts/create-realm.ts +++ b/keycloak/scripts/create-realm.ts @@ -17,7 +17,9 @@ const main = async () => { // Token lifespan settings (with defaults suitable for JupyterHub) const accessTokenLifespan = parseInt(process.env.ACCESS_TOKEN_LIFESPAN || "3600"); // 1 hour const refreshTokenLifespan = parseInt(process.env.REFRESH_TOKEN_LIFESPAN || "14400"); // 4 hours - changed from 30min - const ssoSessionMaxLifespan = parseInt(process.env.SSO_SESSION_MAX_LIFESPAN || refreshTokenLifespan.toString()); // Use refreshTokenLifespan + const ssoSessionMaxLifespan = parseInt( + process.env.SSO_SESSION_MAX_LIFESPAN || refreshTokenLifespan.toString() + ); // Use refreshTokenLifespan const ssoSessionIdleTimeout = parseInt(process.env.SSO_SESSION_IDLE_TIMEOUT || "7200"); // 2 hours const kcAdminClient = new KcAdminClient({ @@ -59,10 +61,18 @@ const main = async () => { clientSessionIdleTimeout: Math.min(accessTokenLifespan, ssoSessionIdleTimeout), }); console.log(`Realm '${realmName}' created successfully with token settings:`); - console.log(` - Access Token Lifespan: ${accessTokenLifespan} seconds (${accessTokenLifespan/60} minutes)`); - console.log(` - Refresh Token Lifespan: ${refreshTokenLifespan} seconds (${refreshTokenLifespan/60} minutes)`); - console.log(` - SSO Session Max: ${ssoSessionMaxLifespan} seconds (${ssoSessionMaxLifespan/60} minutes)`); - console.log(` - SSO Session Idle: ${ssoSessionIdleTimeout} seconds (${ssoSessionIdleTimeout/60} minutes)`); + console.log( + ` - Access Token Lifespan: ${accessTokenLifespan} seconds (${accessTokenLifespan / 60} minutes)` + ); + console.log( + ` - Refresh Token Lifespan: ${refreshTokenLifespan} seconds (${refreshTokenLifespan / 60} minutes)` + ); + console.log( + ` - SSO Session Max: ${ssoSessionMaxLifespan} seconds (${ssoSessionMaxLifespan / 60} minutes)` + ); + console.log( + ` - SSO Session Idle: ${ssoSessionIdleTimeout} seconds (${ssoSessionIdleTimeout / 60} minutes)` + ); } catch (error) { console.error("An error occurred:", error); // eslint-disable-next-line unicorn/no-process-exit diff --git a/keycloak/scripts/delete-client.ts b/keycloak/scripts/delete-client.ts index 7d720ec..e994979 100644 --- a/keycloak/scripts/delete-client.ts +++ b/keycloak/scripts/delete-client.ts @@ -19,7 +19,7 @@ const main = async () => { const kcAdminClient = new KcAdminClient({ baseUrl: `https://${keycloakHost}`, - realmName: 'master', + realmName: "master", }); try { diff --git a/keycloak/scripts/delete-group.ts b/keycloak/scripts/delete-group.ts index 62fce80..8c193fe 100644 --- a/keycloak/scripts/delete-group.ts +++ b/keycloak/scripts/delete-group.ts @@ -33,36 +33,32 @@ const main = async () => { kcAdminClient.setConfig({ realmName }); - // Find group to delete const groups = await kcAdminClient.groups.find({ search: groupName }); - const group = groups.find(g => g.name === groupName); - + const group = groups.find((g) => g.name === groupName); + if (!group) { console.log(`Group '${groupName}' not found`); return; } - // Check if group has members const groupMembers = await kcAdminClient.groups.listMembers({ id: group.id! }); if (groupMembers && groupMembers.length > 0) { console.log(`Warning: Group '${groupName}' has ${groupMembers.length} members:`); - groupMembers.forEach(member => { + groupMembers.forEach((member) => { console.log(` - ${member.username} (${member.firstName} ${member.lastName})`); }); console.log("All members will be removed from the group when it's deleted."); } - // Check for subgroups - const subGroups = await kcAdminClient.groups.listSubGroups({ id: group.id! }); + const subGroups = await kcAdminClient.groups.listSubGroups({ parentId: group.id! }); if (subGroups && subGroups.length > 0) { console.log(`Warning: Group '${groupName}' has ${subGroups.length} subgroups:`); - subGroups.forEach(subGroup => { + subGroups.forEach((subGroup) => { console.log(` - ${subGroup.name}`); }); console.log("All subgroups will be deleted as well."); } - // Delete group await kcAdminClient.groups.del({ id: group.id! }); console.log(`Group '${groupName}' deleted successfully`); @@ -73,4 +69,4 @@ const main = async () => { } }; -main(); \ No newline at end of file +main(); diff --git a/keycloak/scripts/delete-realm.ts b/keycloak/scripts/delete-realm.ts index acaabf2..0a49a9d 100644 --- a/keycloak/scripts/delete-realm.ts +++ b/keycloak/scripts/delete-realm.ts @@ -15,7 +15,9 @@ const main = async () => { invariant(realmNameToDelete, "KEYCLOAK_REALM_TO_DELETE environment variable is required"); if (realmNameToDelete === "master") { - console.error("Error: Deleting the 'master' realm is a highly destructive operation and is not allowed by this script."); + console.error( + "Error: Deleting the 'master' realm is a highly destructive operation and is not allowed by this script." + ); // eslint-disable-next-line unicorn/no-process-exit process.exit(1); } @@ -45,7 +47,6 @@ const main = async () => { console.log(`Attempting to delete realm: '${realmNameToDelete}'...`); await kcAdminClient.realms.del({ realm: realmNameToDelete }); console.log(`Realm '${realmNameToDelete}' deleted successfully.`); - } catch (error) { console.error(`An error occurred while trying to delete realm '${realmNameToDelete}':`, error); const err = error as any; @@ -58,4 +59,3 @@ const main = async () => { }; main(); - diff --git a/keycloak/scripts/delete-user-from-group.ts b/keycloak/scripts/delete-user-from-group.ts index 312c6a4..163d1b9 100644 --- a/keycloak/scripts/delete-user-from-group.ts +++ b/keycloak/scripts/delete-user-from-group.ts @@ -36,30 +36,26 @@ const main = async () => { kcAdminClient.setConfig({ realmName }); - // Find user const users = await kcAdminClient.users.find({ username }); - const user = users.find(u => u.username === username); + const user = users.find((u) => u.username === username); if (!user) { throw new Error(`User '${username}' not found`); } - // Find group const groups = await kcAdminClient.groups.find({ search: groupName }); - const group = groups.find(g => g.name === groupName); + const group = groups.find((g) => g.name === groupName); if (!group) { throw new Error(`Group '${groupName}' not found`); } - // Check if user is in group const userGroups = await kcAdminClient.users.listGroups({ id: user.id! }); - const isMember = userGroups.some(ug => ug.id === group.id); - + const isMember = userGroups.some((ug) => ug.id === group.id); + if (!isMember) { console.log(`User '${username}' is not a member of group '${groupName}'`); return; } - // Remove user from group await kcAdminClient.users.delFromGroup({ id: user.id!, groupId: group.id!, @@ -73,4 +69,4 @@ const main = async () => { } }; -main(); \ No newline at end of file +main(); diff --git a/keycloak/scripts/enable-service-account.ts b/keycloak/scripts/enable-service-account.ts index 5fd3a90..016225b 100644 --- a/keycloak/scripts/enable-service-account.ts +++ b/keycloak/scripts/enable-service-account.ts @@ -2,60 +2,60 @@ import KcAdminClient from "@keycloak/keycloak-admin-client"; import invariant from "tiny-invariant"; const main = async () => { - const keycloakHost = process.env.KEYCLOAK_HOST; - invariant(keycloakHost, "KEYCLOAK_HOST environment variable is required."); + const keycloakHost = process.env.KEYCLOAK_HOST; + invariant(keycloakHost, "KEYCLOAK_HOST environment variable is required."); - const adminUsername = process.env.KEYCLOAK_ADMIN_USER; - invariant(adminUsername, "KEYCLOAK_ADMIN_USER environment variable is required."); + const adminUsername = process.env.KEYCLOAK_ADMIN_USER; + invariant(adminUsername, "KEYCLOAK_ADMIN_USER environment variable is required."); - const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD; - invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD environment variable is required"); + const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD; + invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD environment variable is required"); - const realmName = process.env.KEYCLOAK_REALM; - invariant(realmName, "KEYCLOAK_REALM environment variable is required"); + const realmName = process.env.KEYCLOAK_REALM; + invariant(realmName, "KEYCLOAK_REALM environment variable is required"); - const clientId = process.env.KEYCLOAK_CLIENT_ID; - invariant(clientId, "KEYCLOAK_CLIENT_ID environment variable is required"); + const clientId = process.env.KEYCLOAK_CLIENT_ID; + invariant(clientId, "KEYCLOAK_CLIENT_ID environment variable is required"); - const kcAdminClient = new KcAdminClient({ - baseUrl: `https://${keycloakHost}`, - realmName: "master", + const kcAdminClient = new KcAdminClient({ + baseUrl: `https://${keycloakHost}`, + realmName: "master", + }); + + try { + await kcAdminClient.auth({ + username: adminUsername, + password: adminPassword, + grantType: "password", + clientId: "admin-cli", }); + console.log("Authentication successful."); - try { - await kcAdminClient.auth({ - username: adminUsername, - password: adminPassword, - grantType: "password", - clientId: "admin-cli", - }); - console.log("Authentication successful."); + kcAdminClient.setConfig({ realmName }); - kcAdminClient.setConfig({ realmName }); - - const existingClients = await kcAdminClient.clients.find({ clientId }); - if (existingClients.length === 0) { - console.error(`Client '${clientId}' not found.`); - process.exit(1); - } - - const client = existingClients[0]; - invariant(client.id, "Client ID is missing"); - - await kcAdminClient.clients.update( - { id: client.id }, - { - ...client, - serviceAccountsEnabled: true, - authorizationServicesEnabled: false, - } - ); - - console.log(`Service Accounts enabled for client '${clientId}'`); - } catch (error) { - console.error("An error occurred:", error); - process.exit(1); + const existingClients = await kcAdminClient.clients.find({ clientId }); + if (existingClients.length === 0) { + console.error(`Client '${clientId}' not found.`); + process.exit(1); } + + const client = existingClients[0]; + invariant(client.id, "Client ID is missing"); + + await kcAdminClient.clients.update( + { id: client.id }, + { + ...client, + serviceAccountsEnabled: true, + authorizationServicesEnabled: false, + } + ); + + console.log(`Service Accounts enabled for client '${clientId}'`); + } catch (error) { + console.error("An error occurred:", error); + process.exit(1); + } }; main(); diff --git a/keycloak/scripts/get-client-details.ts b/keycloak/scripts/get-client-details.ts index 945c3b8..fde171d 100755 --- a/keycloak/scripts/get-client-details.ts +++ b/keycloak/scripts/get-client-details.ts @@ -23,15 +23,12 @@ async function main() { invariant(clientId, "KEYCLOAK_CLIENT_ID is required"); try { - // Find the client const clients = await kcAdminClient.clients.find({ realm, clientId }); if (clients.length === 0) { throw new Error(`Client '${clientId}' not found in realm '${realm}'`); } - const client = clients[0]; - // Get full client details const clientDetails = await kcAdminClient.clients.findOne({ realm, id: client.id!, @@ -39,17 +36,16 @@ async function main() { console.log("=== Client Configuration ==="); console.log(`Client ID: ${clientDetails?.clientId}`); - console.log(`Access Type: ${clientDetails?.publicClient ? 'public' : 'confidential'}`); + console.log(`Access Type: ${clientDetails?.publicClient ? "public" : "confidential"}`); console.log(`Client Authenticator: ${clientDetails?.clientAuthenticatorType}`); console.log(`Standard Flow Enabled: ${clientDetails?.standardFlowEnabled}`); console.log(`Direct Access Grants: ${clientDetails?.directAccessGrantsEnabled}`); console.log(`Service Accounts Enabled: ${clientDetails?.serviceAccountsEnabled}`); console.log(`Valid Redirect URIs: ${JSON.stringify(clientDetails?.redirectUris, null, 2)}`); - console.log(`Base URL: ${clientDetails?.baseUrl || 'Not set'}`); - console.log(`Root URL: ${clientDetails?.rootUrl || 'Not set'}`); + console.log(`Base URL: ${clientDetails?.baseUrl || "Not set"}`); + console.log(`Root URL: ${clientDetails?.rootUrl || "Not set"}`); console.log(`Web Origins: ${JSON.stringify(clientDetails?.webOrigins, null, 2)}`); - // Get client secret if confidential if (!clientDetails?.publicClient) { try { const clientSecret = await kcAdminClient.clients.getClientSecret({ @@ -61,11 +57,10 @@ async function main() { console.log(`Client Secret: Error retrieving - ${error}`); } } - } catch (error) { console.error(`Error retrieving client details: ${error}`); process.exit(1); } } -main().catch(console.error); \ No newline at end of file +main().catch(console.error); diff --git a/keycloak/scripts/get-client-scope.ts b/keycloak/scripts/get-client-scope.ts new file mode 100755 index 0000000..b748398 --- /dev/null +++ b/keycloak/scripts/get-client-scope.ts @@ -0,0 +1,88 @@ +#!/usr/bin/env tsx + +import KcAdminClient from "@keycloak/keycloak-admin-client"; +import invariant from "tiny-invariant"; + +const main = async () => { + const KEYCLOAK_HOST = process.env.KEYCLOAK_HOST; + const KEYCLOAK_ADMIN_USER = process.env.KEYCLOAK_ADMIN_USER; + const KEYCLOAK_ADMIN_PASSWORD = process.env.KEYCLOAK_ADMIN_PASSWORD; + const realm = process.env.KEYCLOAK_REALM; + const scopeName = process.env.SCOPE_NAME; + + invariant(KEYCLOAK_HOST, "KEYCLOAK_HOST environment variable is required"); + invariant(KEYCLOAK_ADMIN_USER, "KEYCLOAK_ADMIN_USER environment variable is required"); + invariant(KEYCLOAK_ADMIN_PASSWORD, "KEYCLOAK_ADMIN_PASSWORD environment variable is required"); + invariant(realm, "KEYCLOAK_REALM environment variable is required"); + invariant(scopeName, "SCOPE_NAME environment variable is required"); + + const kcAdminClient = new KcAdminClient({ + baseUrl: `https://${KEYCLOAK_HOST}`, + realmName: "master", + }); + + try { + await kcAdminClient.auth({ + username: KEYCLOAK_ADMIN_USER, + password: KEYCLOAK_ADMIN_PASSWORD, + grantType: "password", + clientId: "admin-cli", + }); + + kcAdminClient.setConfig({ + realmName: realm, + }); + + const clientScopes = await kcAdminClient.clientScopes.find(); + const scope = clientScopes.find((s) => s.name === scopeName); + + if (!scope) { + console.error(`Client scope '${scopeName}' not found in realm '${realm}'.`); + process.exit(1); + } + + invariant(scope.id, "Client scope ID is not set"); + + const protocolMappers = await kcAdminClient.clientScopes.listProtocolMappers({ id: scope.id }); + + console.log("\n=== Client Scope Details ==="); + console.log(`Name: ${scope.name}`); + console.log(`ID: ${scope.id}`); + console.log(`Description: ${scope.description || "(none)"}`); + console.log(`Protocol: ${scope.protocol}`); + console.log("\n=== Attributes ==="); + if (scope.attributes && Object.keys(scope.attributes).length > 0) { + Object.entries(scope.attributes).forEach(([key, value]) => { + console.log(` ${key}: ${value}`); + }); + } else { + console.log(" (none)"); + } + + console.log("\n=== Protocol Mappers ==="); + if (protocolMappers.length > 0) { + protocolMappers.forEach((mapper, index) => { + console.log(`\nMapper ${index + 1}:`); + console.log(` Name: ${mapper.name}`); + console.log(` Protocol: ${mapper.protocol}`); + console.log(` Protocol Mapper: ${mapper.protocolMapper}`); + console.log(` Config:`); + if (mapper.config && Object.keys(mapper.config).length > 0) { + Object.entries(mapper.config).forEach(([key, value]) => { + console.log(` ${key}: ${value}`); + }); + } + }); + } else { + console.log(" (none)"); + } + + console.log("\n=== Raw JSON ==="); + console.log(JSON.stringify({ ...scope, protocolMappers }, null, 2)); + } catch (error) { + console.error("Error retrieving client scope:", error); + process.exit(1); + } +}; + +main(); diff --git a/keycloak/scripts/get-client-secret.ts b/keycloak/scripts/get-client-secret.ts index 77a5d4c..d47167f 100644 --- a/keycloak/scripts/get-client-secret.ts +++ b/keycloak/scripts/get-client-secret.ts @@ -23,26 +23,22 @@ async function main() { invariant(clientId, "KEYCLOAK_CLIENT_ID is required"); try { - // Find the client const clients = await kcAdminClient.clients.find({ realm, clientId }); if (clients.length === 0) { throw new Error(`Client '${clientId}' not found in realm '${realm}'`); } - const client = clients[0]; - // Get client secret const clientSecret = await kcAdminClient.clients.getClientSecret({ realm, id: client.id!, }); console.log(`Client '${clientId}' secret: ${clientSecret.value}`); - } catch (error) { console.error(`Error retrieving client secret: ${error}`); process.exit(1); } } -main().catch(console.error); \ No newline at end of file +main().catch(console.error); diff --git a/keycloak/scripts/get-client.ts b/keycloak/scripts/get-client.ts index 499f13a..f29d257 100644 --- a/keycloak/scripts/get-client.ts +++ b/keycloak/scripts/get-client.ts @@ -44,8 +44,8 @@ const main = async () => { console.log(`=== Client Details: ${clientId} ===`); console.log(`ID: ${client.id}`); console.log(`Client ID: ${client.clientId}`); - console.log(`Name: ${client.name || 'N/A'}`); - console.log(`Description: ${client.description || 'N/A'}`); + console.log(`Name: ${client.name || "N/A"}`); + console.log(`Description: ${client.description || "N/A"}`); console.log(`Enabled: ${client.enabled}`); console.log(`Protocol: ${client.protocol}`); console.log(`Public Client: ${client.publicClient}`); @@ -108,7 +108,6 @@ const main = async () => { console.log(""); } - // Get protocol mappers const protocolMappers = await kcAdminClient.clients.listProtocolMappers({ id: client.id! }); if (protocolMappers.length > 0) { console.log("Protocol Mappers:"); @@ -122,11 +121,10 @@ const main = async () => { }); console.log(""); } - } catch (error) { console.error("An error occurred:", error); process.exit(1); } }; -main(); \ No newline at end of file +main(); diff --git a/keycloak/scripts/get-user-token.ts b/keycloak/scripts/get-user-token.ts index f592a88..021bd59 100644 --- a/keycloak/scripts/get-user-token.ts +++ b/keycloak/scripts/get-user-token.ts @@ -24,14 +24,12 @@ async function main() { invariant(username, "USERNAME is required"); invariant(clientId, "KEYCLOAK_CLIENT_ID is required"); - // Find the user const users = await kcAdminClient.users.find({ realm, username }); if (users.length === 0) { throw new Error(`User '${username}' not found in realm '${realm}'`); } const user = users[0]; - // Find the client const clients = await kcAdminClient.clients.find({ realm, clientId }); if (clients.length === 0) { throw new Error(`Client '${clientId}' not found in realm '${realm}'`); @@ -39,16 +37,13 @@ async function main() { const client = clients[0]; try { - // Get a token for this user (impersonation) console.log(`Getting token for user '${username}' with client '${clientId}'...`); - // Get client secret const clientSecret = await kcAdminClient.clients.getClientSecret({ realm, id: client.id!, }); - // Create a new client instance for the specific realm/client const userClient = new KcAdminClient({ baseUrl: `https://${process.env.KEYCLOAK_HOST}`, realmName: realm, @@ -72,23 +67,21 @@ async function main() { console.log(`Protocol: ${client.protocol}`); console.log(`Public Client: ${client.publicClient}`); - // Get protocol mappers const mappers = await kcAdminClient.clients.listProtocolMappers({ realm, id: client.id!, }); console.log("\n=== Protocol Mappers ==="); - mappers.forEach(mapper => { + mappers.forEach((mapper) => { console.log(`- ${mapper.name} (${mapper.protocolMapper})`); if (mapper.config) { - console.log(` Claim: ${mapper.config['claim.name'] || 'N/A'}`); - console.log(` Access Token: ${mapper.config['access.token.claim'] || 'false'}`); - console.log(` ID Token: ${mapper.config['id.token.claim'] || 'false'}`); + console.log(` Claim: ${mapper.config["claim.name"] || "N/A"}`); + console.log(` Access Token: ${mapper.config["access.token.claim"] || "false"}`); + console.log(` ID Token: ${mapper.config["id.token.claim"] || "false"}`); } }); - // Show user's client roles const clientRoles = await kcAdminClient.users.listClientRoleMappings({ realm, id: user.id!, @@ -99,14 +92,13 @@ async function main() { if (clientRoles.length === 0) { console.log("No client roles assigned"); } else { - clientRoles.forEach(role => { + clientRoles.forEach((role) => { console.log(`- ${role.name} (${role.id})`); }); } - } catch (error) { console.error(`Error: ${error}`); } } -main().catch(console.error); \ No newline at end of file +main().catch(console.error); diff --git a/keycloak/scripts/list-clients.ts b/keycloak/scripts/list-clients.ts index 0838e4a..3bd1c71 100644 --- a/keycloak/scripts/list-clients.ts +++ b/keycloak/scripts/list-clients.ts @@ -39,18 +39,18 @@ const main = async () => { const status = client.enabled ? "Enabled" : "Disabled"; const protocol = client.protocol || "unknown"; - console.log(`${(index + 1).toString().padStart(2, ' ')}. ${client.clientId}`); + console.log(`${(index + 1).toString().padStart(2, " ")}. ${client.clientId}`); console.log(` ID: ${client.id}`); console.log(` Type: ${clientType}`); console.log(` Protocol: ${protocol}`); console.log(` Status: ${status}`); if (client.redirectUris && client.redirectUris.length > 0) { - console.log(` Redirect URIs: ${client.redirectUris.join(', ')}`); + console.log(` Redirect URIs: ${client.redirectUris.join(", ")}`); } if (client.webOrigins && client.webOrigins.length > 0) { - console.log(` Web Origins: ${client.webOrigins.join(', ')}`); + console.log(` Web Origins: ${client.webOrigins.join(", ")}`); } console.log(""); @@ -61,4 +61,4 @@ const main = async () => { } }; -main(); \ No newline at end of file +main(); diff --git a/keycloak/scripts/list-user-client-roles.ts b/keycloak/scripts/list-user-client-roles.ts index 34fd0dc..c586b14 100644 --- a/keycloak/scripts/list-user-client-roles.ts +++ b/keycloak/scripts/list-user-client-roles.ts @@ -24,18 +24,13 @@ async function main() { invariant(username, "USERNAME is required"); invariant(clientId, "KEYCLOAK_CLIENT_ID is required"); - // Find the user const users = await kcAdminClient.users.find({ realm, username }); - if (users.length === 0) { throw new Error(`User '${username}' not found in realm '${realm}'`); } - const user = users[0]; - // Find the client const clients = await kcAdminClient.clients.find({ realm, clientId }); - if (clients.length === 0) { throw new Error(`Client '${clientId}' not found in realm '${realm}'`); } @@ -43,7 +38,6 @@ async function main() { const client = clients[0]; try { - // Get user's client role mappings const clientRoles = await kcAdminClient.users.listClientRoleMappings({ realm, id: user.id!, @@ -63,7 +57,6 @@ async function main() { }); } - // Also show available roles for reference const availableRoles = await kcAdminClient.clients.listRoles({ realm, id: client.id!, @@ -75,11 +68,10 @@ async function main() { const status = isAssigned ? "✓ assigned" : " available"; console.log(` ${status}: ${role.name}`); }); - } catch (error) { console.error(`Error retrieving client roles: ${error}`); process.exit(1); } } -main().catch(console.error); \ No newline at end of file +main().catch(console.error); diff --git a/keycloak/scripts/remove-user-from-client-role.ts b/keycloak/scripts/remove-user-from-client-role.ts index 0c7de16..39a65a8 100755 --- a/keycloak/scripts/remove-user-from-client-role.ts +++ b/keycloak/scripts/remove-user-from-client-role.ts @@ -26,32 +26,25 @@ async function main() { invariant(clientId, "KEYCLOAK_CLIENT_ID is required"); invariant(roleName, "KEYCLOAK_ROLE_NAME is required"); - // Find the user const users = await kcAdminClient.users.find({ realm, username }); - if (users.length === 0) { throw new Error(`User '${username}' not found in realm '${realm}'`); } - const user = users[0]; - // Find the client const clients = await kcAdminClient.clients.find({ realm, clientId }); - if (clients.length === 0) { throw new Error(`Client '${clientId}' not found in realm '${realm}'`); } const client = clients[0]; - // Find the role const role = await kcAdminClient.clients.findRole({ realm, id: client.id!, roleName, }); - // Remove role from user await kcAdminClient.users.delClientRoleMappings({ realm, id: user.id!, @@ -65,7 +58,7 @@ async function main() { }); console.log( - `✓ Role '${roleName}' removed from user '${username}' for client '${clientId}' in realm '${realm}'`, + `✓ Role '${roleName}' removed from user '${username}' for client '${clientId}' in realm '${realm}'` ); } diff --git a/keycloak/scripts/show-realm-token-settings.ts b/keycloak/scripts/show-realm-token-settings.ts index 8b2fbbe..60ad949 100644 --- a/keycloak/scripts/show-realm-token-settings.ts +++ b/keycloak/scripts/show-realm-token-settings.ts @@ -4,77 +4,86 @@ import KcAdminClient from "@keycloak/keycloak-admin-client"; import invariant from "tiny-invariant"; const main = async () => { - // Environment variables - const keycloakHost = process.env.KEYCLOAK_HOST; - const adminUser = process.env.KEYCLOAK_ADMIN_USER; - const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD; - const realm = process.env.KEYCLOAK_REALM; + const keycloakHost = process.env.KEYCLOAK_HOST; + const adminUser = process.env.KEYCLOAK_ADMIN_USER; + const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD; + const realm = process.env.KEYCLOAK_REALM; - invariant(keycloakHost, "KEYCLOAK_HOST is required"); - invariant(adminUser, "KEYCLOAK_ADMIN_USER is required"); - invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD is required"); - invariant(realm, "KEYCLOAK_REALM is required"); + invariant(keycloakHost, "KEYCLOAK_HOST is required"); + invariant(adminUser, "KEYCLOAK_ADMIN_USER is required"); + invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD is required"); + invariant(realm, "KEYCLOAK_REALM is required"); - console.log(`Checking token settings for realm: ${realm}`); + console.log(`Checking token settings for realm: ${realm}`); - // Initialize Keycloak admin client - const kcAdminClient = new KcAdminClient({ - baseUrl: `https://${keycloakHost}`, - realmName: "master", + const kcAdminClient = new KcAdminClient({ + baseUrl: `https://${keycloakHost}`, + realmName: "master", + }); + + try { + await kcAdminClient.auth({ + username: adminUser, + password: adminPassword, + grantType: "password", + clientId: "admin-cli", }); - try { - // Authenticate - await kcAdminClient.auth({ - username: adminUser, - password: adminPassword, - grantType: "password", - clientId: "admin-cli", - }); + console.log("✓ Authenticated with Keycloak admin"); - console.log("✓ Authenticated with Keycloak admin"); + kcAdminClient.setConfig({ realmName: realm }); - // Set the target realm - kcAdminClient.setConfig({ realmName: realm }); - - // Get current realm settings - const currentRealm = await kcAdminClient.realms.findOne({ realm }); - if (!currentRealm) { - throw new Error(`Realm ${realm} not found`); - } - - console.log(`\n=== Current Token Settings for Realm: ${realm} ===`); - console.log(`Access Token Lifespan: ${currentRealm.accessTokenLifespan || 'not set'} seconds (${(currentRealm.accessTokenLifespan || 0)/60} minutes)`); - console.log(`Access Token Lifespan (Implicit): ${currentRealm.accessTokenLifespanForImplicitFlow || 'not set'} seconds`); - console.log(`SSO Session Max Lifespan: ${currentRealm.ssoSessionMaxLifespan || 'not set'} seconds (${(currentRealm.ssoSessionMaxLifespan || 0)/60} minutes)`); - console.log(`SSO Session Idle Timeout: ${currentRealm.ssoSessionIdleTimeout || 'not set'} seconds (${(currentRealm.ssoSessionIdleTimeout || 0)/60} minutes)`); - console.log(`Client Session Max Lifespan: ${currentRealm.clientSessionMaxLifespan || 'not set'} seconds`); - console.log(`Client Session Idle Timeout: ${currentRealm.clientSessionIdleTimeout || 'not set'} seconds`); - console.log(`Offline Session Max Lifespan: ${currentRealm.offlineSessionMaxLifespan || 'not set'} seconds`); - console.log(`Refresh Token Max Reuse: ${currentRealm.refreshTokenMaxReuse || 0}`); - - // Also check specific client settings if JupyterHub client exists - try { - const clients = await kcAdminClient.clients.find({ clientId: 'jupyterhub' }); - if (clients.length > 0) { - const jupyterhubClient = clients[0]; - console.log(`\n=== JupyterHub Client Settings ===`); - console.log(`Client ID: ${jupyterhubClient.clientId}`); - console.log(`Access Token Lifespan: ${jupyterhubClient.attributes?.['access.token.lifespan'] || 'inherit from realm'}`); - } - } catch (clientError) { - console.log(`\n⚠️ Could not retrieve JupyterHub client settings: ${clientError}`); - } - - console.log(`\n=== Keycloak Default Values (for reference) ===`); - console.log(`Default Access Token Lifespan: 300 seconds (5 minutes)`); - console.log(`Default SSO Session Max: 36000 seconds (10 hours)`); - console.log(`Default SSO Session Idle: 1800 seconds (30 minutes)`); - - } catch (error) { - console.error("✗ Failed to retrieve realm token settings:", error); - process.exit(1); + const currentRealm = await kcAdminClient.realms.findOne({ realm }); + if (!currentRealm) { + throw new Error(`Realm ${realm} not found`); } + + console.log(`\n=== Current Token Settings for Realm: ${realm} ===`); + console.log( + `Access Token Lifespan: ${currentRealm.accessTokenLifespan || "not set"} seconds (${(currentRealm.accessTokenLifespan || 0) / 60} minutes)` + ); + console.log( + `Access Token Lifespan (Implicit): ${currentRealm.accessTokenLifespanForImplicitFlow || "not set"} seconds` + ); + console.log( + `SSO Session Max Lifespan: ${currentRealm.ssoSessionMaxLifespan || "not set"} seconds (${(currentRealm.ssoSessionMaxLifespan || 0) / 60} minutes)` + ); + console.log( + `SSO Session Idle Timeout: ${currentRealm.ssoSessionIdleTimeout || "not set"} seconds (${(currentRealm.ssoSessionIdleTimeout || 0) / 60} minutes)` + ); + console.log( + `Client Session Max Lifespan: ${currentRealm.clientSessionMaxLifespan || "not set"} seconds` + ); + console.log( + `Client Session Idle Timeout: ${currentRealm.clientSessionIdleTimeout || "not set"} seconds` + ); + console.log( + `Offline Session Max Lifespan: ${currentRealm.offlineSessionMaxLifespan || "not set"} seconds` + ); + console.log(`Refresh Token Max Reuse: ${currentRealm.refreshTokenMaxReuse || 0}`); + + try { + const clients = await kcAdminClient.clients.find({ clientId: "jupyterhub" }); + if (clients.length > 0) { + const jupyterhubClient = clients[0]; + console.log(`\n=== JupyterHub Client Settings ===`); + console.log(`Client ID: ${jupyterhubClient.clientId}`); + console.log( + `Access Token Lifespan: ${jupyterhubClient.attributes?.["access.token.lifespan"] || "inherit from realm"}` + ); + } + } catch (clientError) { + console.log(`\n⚠️ Could not retrieve JupyterHub client settings: ${clientError}`); + } + + console.log(`\n=== Keycloak Default Values (for reference) ===`); + console.log(`Default Access Token Lifespan: 300 seconds (5 minutes)`); + console.log(`Default SSO Session Max: 36000 seconds (10 hours)`); + console.log(`Default SSO Session Idle: 1800 seconds (30 minutes)`); + } catch (error) { + console.error("✗ Failed to retrieve realm token settings:", error); + process.exit(1); + } }; -main().catch(console.error); \ No newline at end of file +main().catch(console.error); diff --git a/keycloak/scripts/update-client-roles-mapper.ts b/keycloak/scripts/update-client-roles-mapper.ts index 6a3da1a..537a944 100644 --- a/keycloak/scripts/update-client-roles-mapper.ts +++ b/keycloak/scripts/update-client-roles-mapper.ts @@ -35,12 +35,10 @@ async function main() { clientId: "admin-cli", }); - // Set realm to work with kcAdminClient.setConfig({ realmName, }); - // Find the client const clients = await kcAdminClient.clients.find({ clientId }); if (clients.length === 0) { throw new Error(`Client '${clientId}' not found in realm '${realmName}'`); @@ -49,7 +47,6 @@ async function main() { const client = clients[0]; const clientInternalId = client.id!; - // Find existing mapper const mappers = await kcAdminClient.clients.listProtocolMappers({ id: clientInternalId }); const existingMapper = mappers.find((mapper) => mapper.name === mapperName); @@ -61,7 +58,6 @@ async function main() { }); } - // Create updated client roles protocol mapper await kcAdminClient.clients.addProtocolMapper( { id: clientInternalId }, { @@ -74,13 +70,15 @@ async function main() { "access.token.claim": "true", "claim.name": claimName, "jsonType.label": "String", - "multivalued": "true", + multivalued: "true", "usermodel.clientRoleMapping.clientId": clientId, }, } ); - console.log(`✓ Client roles mapper '${mapperName}' updated for client '${clientId}' in realm '${realmName}'`); + console.log( + `✓ Client roles mapper '${mapperName}' updated for client '${clientId}' in realm '${realmName}'` + ); console.log(` Claim name: ${claimName}`); console.log(` User Info: enabled`); console.log(` Access Token: enabled`); @@ -91,4 +89,4 @@ async function main() { } } -main().catch(console.error); \ No newline at end of file +main().catch(console.error); diff --git a/keycloak/scripts/update-realm-token-settings.ts b/keycloak/scripts/update-realm-token-settings.ts index 60cc034..c52dde1 100644 --- a/keycloak/scripts/update-realm-token-settings.ts +++ b/keycloak/scripts/update-realm-token-settings.ts @@ -4,88 +4,84 @@ import KcAdminClient from "@keycloak/keycloak-admin-client"; import invariant from "tiny-invariant"; const main = async () => { - // Environment variables - const keycloakHost = process.env.KEYCLOAK_HOST; - const adminUser = process.env.KEYCLOAK_ADMIN_USER; - const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD; - const realm = process.env.KEYCLOAK_REALM; - const accessTokenLifespan = parseInt(process.env.ACCESS_TOKEN_LIFESPAN || "3600"); - const refreshTokenLifespan = parseInt(process.env.REFRESH_TOKEN_LIFESPAN || "1800"); + const keycloakHost = process.env.KEYCLOAK_HOST; + const adminUser = process.env.KEYCLOAK_ADMIN_USER; + const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD; + const realm = process.env.KEYCLOAK_REALM; + const accessTokenLifespan = parseInt(process.env.ACCESS_TOKEN_LIFESPAN || "3600"); + const refreshTokenLifespan = parseInt(process.env.REFRESH_TOKEN_LIFESPAN || "1800"); - invariant(keycloakHost, "KEYCLOAK_HOST is required"); - invariant(adminUser, "KEYCLOAK_ADMIN_USER is required"); - invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD is required"); - invariant(realm, "KEYCLOAK_REALM is required"); + invariant(keycloakHost, "KEYCLOAK_HOST is required"); + invariant(adminUser, "KEYCLOAK_ADMIN_USER is required"); + invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD is required"); + invariant(realm, "KEYCLOAK_REALM is required"); - console.log(`Updating token settings for realm: ${realm}`); - console.log(`Access token lifespan: ${accessTokenLifespan} seconds (${accessTokenLifespan/60} minutes)`); - console.log(`Refresh token lifespan: ${refreshTokenLifespan} seconds (${refreshTokenLifespan/60} minutes)`); + console.log(`Updating token settings for realm: ${realm}`); + console.log( + `Access token lifespan: ${accessTokenLifespan} seconds (${accessTokenLifespan / 60} minutes)` + ); + console.log( + `Refresh token lifespan: ${refreshTokenLifespan} seconds (${refreshTokenLifespan / 60} minutes)` + ); - // Initialize Keycloak admin client - const kcAdminClient = new KcAdminClient({ - baseUrl: `https://${keycloakHost}`, - realmName: "master", + const kcAdminClient = new KcAdminClient({ + baseUrl: `https://${keycloakHost}`, + realmName: "master", + }); + + try { + await kcAdminClient.auth({ + username: adminUser, + password: adminPassword, + grantType: "password", + clientId: "admin-cli", }); - try { - // Authenticate - await kcAdminClient.auth({ - username: adminUser, - password: adminPassword, - grantType: "password", - clientId: "admin-cli", - }); + console.log("✓ Authenticated with Keycloak admin"); - console.log("✓ Authenticated with Keycloak admin"); + kcAdminClient.setConfig({ realmName: realm }); - // Set the target realm - kcAdminClient.setConfig({ realmName: realm }); - - // Get current realm settings - const currentRealm = await kcAdminClient.realms.findOne({ realm }); - if (!currentRealm) { - throw new Error(`Realm ${realm} not found`); - } - - console.log(`Current settings:`); - console.log(` - Access token lifespan: ${currentRealm.accessTokenLifespan} seconds`); - console.log(` - Refresh token lifespan: ${currentRealm.ssoSessionMaxLifespan} seconds`); - console.log(` - SSO session idle: ${currentRealm.ssoSessionIdleTimeout} seconds`); - - // Update realm settings - await kcAdminClient.realms.update( - { realm }, - { - ...currentRealm, - // Access token settings - accessTokenLifespan: accessTokenLifespan, - accessTokenLifespanForImplicitFlow: accessTokenLifespan, - // Refresh token settings - refreshTokenMaxReuse: 0, - ssoSessionMaxLifespan: refreshTokenLifespan, - ssoSessionIdleTimeout: Math.min(refreshTokenLifespan, 1800), // Max 30 minutes idle - // Other token settings - offlineSessionMaxLifespan: refreshTokenLifespan * 2, - offlineSessionMaxLifespanEnabled: true, - // Client session settings - clientSessionMaxLifespan: accessTokenLifespan, - clientSessionIdleTimeout: Math.min(accessTokenLifespan, 1800), - } - ); - - console.log("✓ Realm token settings updated successfully"); - - // Verify the changes - const updatedRealm = await kcAdminClient.realms.findOne({ realm }); - console.log(`Updated settings:`); - console.log(` - Access token lifespan: ${updatedRealm?.accessTokenLifespan} seconds`); - console.log(` - Refresh token lifespan: ${updatedRealm?.ssoSessionMaxLifespan} seconds`); - console.log(` - SSO session idle: ${updatedRealm?.ssoSessionIdleTimeout} seconds`); - - } catch (error) { - console.error("✗ Failed to update realm token settings:", error); - process.exit(1); + const currentRealm = await kcAdminClient.realms.findOne({ realm }); + if (!currentRealm) { + throw new Error(`Realm ${realm} not found`); } + + console.log(`Current settings:`); + console.log(` - Access token lifespan: ${currentRealm.accessTokenLifespan} seconds`); + console.log(` - Refresh token lifespan: ${currentRealm.ssoSessionMaxLifespan} seconds`); + console.log(` - SSO session idle: ${currentRealm.ssoSessionIdleTimeout} seconds`); + + await kcAdminClient.realms.update( + { realm }, + { + ...currentRealm, + // Access token settings + accessTokenLifespan: accessTokenLifespan, + accessTokenLifespanForImplicitFlow: accessTokenLifespan, + // Refresh token settings + refreshTokenMaxReuse: 0, + ssoSessionMaxLifespan: refreshTokenLifespan, + ssoSessionIdleTimeout: Math.min(refreshTokenLifespan, 1800), // Max 30 minutes idle + // Other token settings + offlineSessionMaxLifespan: refreshTokenLifespan * 2, + offlineSessionMaxLifespanEnabled: true, + // Client session settings + clientSessionMaxLifespan: accessTokenLifespan, + clientSessionIdleTimeout: Math.min(accessTokenLifespan, 1800), + } + ); + + console.log("✓ Realm token settings updated successfully"); + + const updatedRealm = await kcAdminClient.realms.findOne({ realm }); + console.log(`Updated settings:`); + console.log(` - Access token lifespan: ${updatedRealm?.accessTokenLifespan} seconds`); + console.log(` - Refresh token lifespan: ${updatedRealm?.ssoSessionMaxLifespan} seconds`); + console.log(` - SSO session idle: ${updatedRealm?.ssoSessionIdleTimeout} seconds`); + } catch (error) { + console.error("✗ Failed to update realm token settings:", error); + process.exit(1); + } }; -main().catch(console.error); \ No newline at end of file +main().catch(console.error);