chore(keycloak): code cleanup

This commit is contained in:
Masaki Yatsu
2025-10-29 15:33:20 +09:00
parent fd0c359407
commit 82f90f621b
35 changed files with 613 additions and 599 deletions

8
.prettierrc.js Normal file
View File

@@ -0,0 +1,8 @@
module.exports = {
printWidth: 100,
singleQuote: false,
semi: true,
trailingComma: 'es5',
tabWidth: 2,
useTabs: false,
}

View File

@@ -685,3 +685,13 @@ add-scope-to-client realm client_id scope_name:
export KEYCLOAK_CLIENT_ID={{ client_id }} export KEYCLOAK_CLIENT_ID={{ client_id }}
export SCOPE_NAME={{ scope_name }} export SCOPE_NAME={{ scope_name }}
dotenvx run -q -f ../.env.local -- tsx ./scripts/add-scope-to-client.ts 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

View File

@@ -26,7 +26,6 @@ const main = async () => {
const attributeDefaultValue = process.env.ATTRIBUTE_DEFAULT_VALUE; const attributeDefaultValue = process.env.ATTRIBUTE_DEFAULT_VALUE;
const mapperName = process.env.MAPPER_NAME || `${attributeDisplayName} Mapper`; const mapperName = process.env.MAPPER_NAME || `${attributeDisplayName} Mapper`;
// Parse permissions from environment variables
const viewPermissions = process.env.ATTRIBUTE_VIEW_PERMISSIONS?.split(",") || ["admin", "user"]; const viewPermissions = process.env.ATTRIBUTE_VIEW_PERMISSIONS?.split(",") || ["admin", "user"];
const editPermissions = process.env.ATTRIBUTE_EDIT_PERMISSIONS?.split(",") || ["admin"]; const editPermissions = process.env.ATTRIBUTE_EDIT_PERMISSIONS?.split(",") || ["admin"];
@@ -58,19 +57,13 @@ const main = async () => {
}); });
console.log("Authentication successful."); console.log("Authentication successful.");
// Set realm to work with
kcAdminClient.setConfig({ kcAdminClient.setConfig({
realmName, realmName,
}); });
// Get current User Profile configuration
const userProfile = await kcAdminClient.users.getProfile(); const userProfile = await kcAdminClient.users.getProfile();
// Check if attribute already exists
const existingAttribute = userProfile.attributes?.find( const existingAttribute = userProfile.attributes?.find(
(attr: any) => attr.name === attributeName (attr: any) => attr.name === attributeName
); );
if (existingAttribute) { if (existingAttribute) {
console.log(`${attributeName} attribute already exists in User Profile.`); console.log(`${attributeName} attribute already exists in User Profile.`);
} else { } else {
@@ -88,16 +81,13 @@ const main = async () => {
}, },
}; };
// Add validations if options are provided
if (attributeOptions && attributeOptions.length > 0) { if (attributeOptions && attributeOptions.length > 0) {
attributeConfig.validations = { attributeConfig.validations = {
options: { options: attributeOptions }, options: { options: attributeOptions },
}; };
} }
userProfile.attributes.push(attributeConfig); userProfile.attributes.push(attributeConfig);
// Update User Profile
await kcAdminClient.users.updateProfile(userProfile); await kcAdminClient.users.updateProfile(userProfile);
console.log( console.log(
`${attributeName} attribute added to User Profile successfully with admin edit permissions.` `${attributeName} attribute added to User Profile successfully with admin edit permissions.`
@@ -114,14 +104,14 @@ const main = async () => {
const clientInternalId = client[0].id; const clientInternalId = client[0].id;
invariant(clientInternalId, "Client internal ID is required"); invariant(clientInternalId, "Client internal ID is required");
// Check if the mapper already exists const mappers = await kcAdminClient.clients.listProtocolMappers({
const mappers = await kcAdminClient.clients.listProtocolMappers({ id: clientInternalId }); id: clientInternalId,
});
const existingMapper = mappers.find((mapper) => mapper.name === mapperName); const existingMapper = mappers.find((mapper) => mapper.name === mapperName);
if (existingMapper) { if (existingMapper) {
console.log(`${mapperName} already exists.`); console.log(`${mapperName} already exists.`);
} else { } else {
// Create the protocol mapper
await kcAdminClient.clients.addProtocolMapper( await kcAdminClient.clients.addProtocolMapper(
{ id: clientInternalId }, { id: clientInternalId },
{ {

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env tsx #!/usr/bin/env tsx
import KcAdminClient from '@keycloak/keycloak-admin-client'; import KcAdminClient from "@keycloak/keycloak-admin-client";
import invariant from 'tiny-invariant'; import invariant from "tiny-invariant";
const main = async () => { const main = async () => {
const KEYCLOAK_HOST = process.env.KEYCLOAK_HOST; const KEYCLOAK_HOST = process.env.KEYCLOAK_HOST;
@@ -11,43 +11,40 @@ const main = async () => {
const scopeName = process.env.SCOPE_NAME; const scopeName = process.env.SCOPE_NAME;
const audience = process.env.KEYCLOAK_AUDIENCE; const audience = process.env.KEYCLOAK_AUDIENCE;
invariant(KEYCLOAK_HOST, 'KEYCLOAK_HOST 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_USER, "KEYCLOAK_ADMIN_USER environment variable is required");
invariant(KEYCLOAK_ADMIN_PASSWORD, 'KEYCLOAK_ADMIN_PASSWORD environment variable is required'); invariant(KEYCLOAK_ADMIN_PASSWORD, "KEYCLOAK_ADMIN_PASSWORD environment variable is required");
invariant(realm, 'KEYCLOAK_REALM environment variable is required'); invariant(realm, "KEYCLOAK_REALM environment variable is required");
invariant(scopeName, 'SCOPE_NAME environment variable is required'); invariant(scopeName, "SCOPE_NAME environment variable is required");
invariant(audience, 'KEYCLOAK_AUDIENCE environment variable is required'); invariant(audience, "KEYCLOAK_AUDIENCE environment variable is required");
const kcAdminClient = new KcAdminClient({ const kcAdminClient = new KcAdminClient({
baseUrl: `https://${KEYCLOAK_HOST}`, baseUrl: `https://${KEYCLOAK_HOST}`,
realmName: 'master', realmName: "master",
}); });
try { try {
await kcAdminClient.auth({ await kcAdminClient.auth({
username: KEYCLOAK_ADMIN_USER, username: KEYCLOAK_ADMIN_USER,
password: KEYCLOAK_ADMIN_PASSWORD, password: KEYCLOAK_ADMIN_PASSWORD,
grantType: 'password', grantType: "password",
clientId: 'admin-cli', clientId: "admin-cli",
}); });
console.log('Authentication successful.'); console.log("Authentication successful.");
// Set target realm
kcAdminClient.setConfig({ kcAdminClient.setConfig({
realmName: realm, realmName: realm,
}); });
// Find the client scope
const clientScopes = await kcAdminClient.clientScopes.find(); const clientScopes = await kcAdminClient.clientScopes.find();
const scope = clientScopes.find(s => s.name === scopeName); const scope = clientScopes.find((s) => s.name === scopeName);
if (!scope) { if (!scope) {
throw new Error(`Client scope '${scopeName}' not found`); throw new Error(`Client scope '${scopeName}' not found`);
} }
invariant(scope.id, 'Client scope ID is not set'); invariant(scope.id, "Client scope ID is not set");
// Check if mapper already exists
const mapperName = `aud-mapper-${audience}`; const mapperName = `aud-mapper-${audience}`;
const existingMappers = await kcAdminClient.clientScopes.listProtocolMappers({ id: scope.id }); const existingMappers = await kcAdminClient.clientScopes.listProtocolMappers({ id: scope.id });
@@ -56,23 +53,21 @@ const main = async () => {
return; return;
} }
// Create audience mapper
const audienceMapper = { const audienceMapper = {
name: mapperName, name: mapperName,
protocol: 'openid-connect', protocol: "openid-connect",
protocolMapper: 'oidc-audience-mapper', protocolMapper: "oidc-audience-mapper",
config: { config: {
'included.client.audience': audience, "included.client.audience": audience,
'id.token.claim': 'false', "id.token.claim": "false",
'access.token.claim': 'true', "access.token.claim": "true",
}, },
}; };
await kcAdminClient.clientScopes.addProtocolMapper({ id: scope.id }, audienceMapper); await kcAdminClient.clientScopes.addProtocolMapper({ id: scope.id }, audienceMapper);
console.log(`Audience mapper '${mapperName}' added to client scope '${scopeName}'.`); console.log(`Audience mapper '${mapperName}' added to client scope '${scopeName}'.`);
} catch (error) { } catch (error) {
console.error('Error adding audience mapper to scope:', error); console.error("Error adding audience mapper to scope:", error);
process.exit(1); process.exit(1);
} }
}; };

View File

@@ -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)) { if (existingMappers.some((mapper) => mapper.name === mapperName)) {
console.warn(`Audience Mapper '${mapperName}' already exists for the client.`); console.warn(`Audience Mapper '${mapperName}' already exists for the client.`);

View File

@@ -35,12 +35,10 @@ async function main() {
clientId: "admin-cli", clientId: "admin-cli",
}); });
// Set realm to work with
kcAdminClient.setConfig({ kcAdminClient.setConfig({
realmName, realmName,
}); });
// Find the client
const clients = await kcAdminClient.clients.find({ clientId }); const clients = await kcAdminClient.clients.find({ clientId });
if (clients.length === 0) { if (clients.length === 0) {
throw new Error(`Client '${clientId}' not found in realm '${realmName}'`); throw new Error(`Client '${clientId}' not found in realm '${realmName}'`);
@@ -49,8 +47,9 @@ async function main() {
const client = clients[0]; const client = clients[0];
const clientInternalId = client.id!; const clientInternalId = client.id!;
// Check if the mapper already exists const mappers = await kcAdminClient.clients.listProtocolMappers({
const mappers = await kcAdminClient.clients.listProtocolMappers({ id: clientInternalId }); id: clientInternalId,
});
const existingMapper = mappers.find((mapper) => mapper.name === mapperName); const existingMapper = mappers.find((mapper) => mapper.name === mapperName);
if (existingMapper) { if (existingMapper) {
@@ -58,7 +57,6 @@ async function main() {
return; return;
} }
// Create the client roles protocol mapper
await kcAdminClient.clients.addProtocolMapper( await kcAdminClient.clients.addProtocolMapper(
{ id: clientInternalId }, { id: clientInternalId },
{ {
@@ -71,13 +69,15 @@ async function main() {
"access.token.claim": "true", "access.token.claim": "true",
"claim.name": claimName, "claim.name": claimName,
"jsonType.label": "String", "jsonType.label": "String",
"multivalued": "true", multivalued: "true",
"usermodel.clientRoleMapping.clientId": clientId, "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(` Claim name: ${claimName}`);
console.log(` Maps client roles from '${clientId}' to JWT token`); console.log(` Maps client roles from '${clientId}' to JWT token`);
} catch (error) { } catch (error) {

View File

@@ -26,38 +26,31 @@ async function main() {
kcAdminClient.setConfig({ realmName: realm }); kcAdminClient.setConfig({ realmName: realm });
try { try {
// Find the profile client scope
const clientScopes = await kcAdminClient.clientScopes.find({ realm }); 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) { if (!profileScope) {
throw new Error("Profile client scope not found"); throw new Error("Profile client scope not found");
} }
console.log(`Found profile scope: ${profileScope.id}`); console.log(`Found profile scope: ${profileScope.id}`);
// Check existing mappers in profile scope
const existingMappers = await kcAdminClient.clientScopes.listProtocolMappers({ const existingMappers = await kcAdminClient.clientScopes.listProtocolMappers({
realm, realm,
id: profileScope.id!, id: profileScope.id!,
}); });
console.log("Existing mappers in profile scope:"); console.log("Existing mappers in profile scope:");
existingMappers.forEach(mapper => { existingMappers.forEach((mapper) => {
console.log(`- ${mapper.name} (${mapper.protocolMapper})`); console.log(`- ${mapper.name} (${mapper.protocolMapper})`);
}); });
// Check if our client roles mapper already exists in profile scope const clientRolesMapper = existingMappers.find(
const clientRolesMapper = existingMappers.find(m => (m) => m.config?.["usermodel.clientRoleMapping.clientId"] === clientId
m.config?.['usermodel.clientRoleMapping.clientId'] === clientId
); );
if (clientRolesMapper) { if (clientRolesMapper) {
console.log(`Client roles mapper already exists in profile scope: ${clientRolesMapper.name}`); console.log(`Client roles mapper already exists in profile scope: ${clientRolesMapper.name}`);
} else { } else {
console.log(`Adding ${clientId} client roles mapper to profile scope...`); console.log(`Adding ${clientId} client roles mapper to profile scope...`);
// Add client roles mapper to profile scope
await kcAdminClient.clientScopes.addProtocolMapper( await kcAdminClient.clientScopes.addProtocolMapper(
{ realm, id: profileScope.id! }, { realm, id: profileScope.id! },
{ {
@@ -70,7 +63,7 @@ async function main() {
"access.token.claim": "true", "access.token.claim": "true",
"claim.name": claimName, "claim.name": claimName,
"jsonType.label": "String", "jsonType.label": "String",
"multivalued": "true", multivalued: "true",
"usermodel.clientRoleMapping.clientId": clientId, "usermodel.clientRoleMapping.clientId": clientId,
}, },
} }
@@ -78,7 +71,6 @@ async function main() {
console.log(`✓ Added ${clientId} client roles mapper to profile scope`); console.log(`✓ Added ${clientId} client roles mapper to profile scope`);
} }
} catch (error) { } catch (error) {
console.error(`Error: ${error}`); console.error(`Error: ${error}`);
process.exit(1); process.exit(1);

View File

@@ -33,32 +33,29 @@ const main = async () => {
kcAdminClient.setConfig({ realmName }); kcAdminClient.setConfig({ realmName });
// Find the client
const clients = await kcAdminClient.clients.find({ clientId }); 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) { if (!client) {
throw new Error(`Client '${clientId}' not found`); throw new Error(`Client '${clientId}' not found`);
} }
// Check if groups mapper already exists
const existingMappers = await kcAdminClient.clients.listProtocolMappers({ const existingMappers = await kcAdminClient.clients.listProtocolMappers({
id: client.id!, id: client.id!,
}); });
const groupsMapper = existingMappers.find(mapper => const groupsMapper = existingMappers.find(
mapper.name === "groups" || mapper.config?.["claim.name"] === "groups" (mapper) => mapper.name === "groups" || mapper.config?.["claim.name"] === "groups"
); );
if (groupsMapper) { if (groupsMapper) {
console.log("Groups mapper already exists for the client."); console.log("Groups mapper already exists for the client.");
return; return;
} }
// Add groups mapper await kcAdminClient.clients.addProtocolMapper(
await kcAdminClient.clients.addProtocolMapper({ {
id: client.id!, id: client.id!,
}, { },
{
name: "groups", name: "groups",
protocol: "openid-connect", protocol: "openid-connect",
protocolMapper: "oidc-group-membership-mapper", protocolMapper: "oidc-group-membership-mapper",
@@ -69,7 +66,8 @@ const main = async () => {
"access.token.claim": "true", "access.token.claim": "true",
"userinfo.token.claim": "true", "userinfo.token.claim": "true",
}, },
}); }
);
console.log("Groups mapper added to the client."); console.log("Groups mapper added to the client.");
} catch (error) { } catch (error) {

View File

@@ -34,15 +34,12 @@ const main = async () => {
}); });
console.log("Authentication successful."); console.log("Authentication successful.");
// Set realm to work with
kcAdminClient.setConfig({ kcAdminClient.setConfig({
realmName, realmName,
}); });
// Get current User Profile configuration
const userProfile = await kcAdminClient.users.getProfile(); const userProfile = await kcAdminClient.users.getProfile();
// Check if minioPolicy attribute already exists
const existingAttribute = userProfile.attributes?.find( const existingAttribute = userProfile.attributes?.find(
(attr: any) => attr.name === "minioPolicy" (attr: any) => attr.name === "minioPolicy"
); );
@@ -50,11 +47,9 @@ const main = async () => {
if (existingAttribute) { if (existingAttribute) {
console.log("minioPolicy attribute already exists in User Profile."); console.log("minioPolicy attribute already exists in User Profile.");
} else { } else {
// Add minioPolicy attribute to User Profile with proper permissions
if (!userProfile.attributes) { if (!userProfile.attributes) {
userProfile.attributes = []; userProfile.attributes = [];
} }
userProfile.attributes.push({ userProfile.attributes.push({
name: "minioPolicy", name: "minioPolicy",
displayName: "MinIO Policy", displayName: "MinIO Policy",
@@ -67,15 +62,15 @@ const main = async () => {
}, },
}); });
// Update User Profile
await kcAdminClient.users.updateProfile(userProfile); await kcAdminClient.users.updateProfile(userProfile);
console.log( console.log(
"minioPolicy attribute added to User Profile successfully with admin edit permissions." "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({
const minioClient = await kcAdminClient.clients.find({ clientId: minioClientId }); clientId: minioClientId,
});
if (minioClient.length === 0) { if (minioClient.length === 0) {
console.error(`Client '${minioClientId}' not found.`); console.error(`Client '${minioClientId}' not found.`);
// eslint-disable-next-line unicorn/no-process-exit // eslint-disable-next-line unicorn/no-process-exit
@@ -84,14 +79,14 @@ const main = async () => {
const clientId = minioClient[0].id; const clientId = minioClient[0].id;
invariant(clientId, "Client ID is required"); invariant(clientId, "Client ID is required");
// Check if the mapper already exists const mappers = await kcAdminClient.clients.listProtocolMappers({
const mappers = await kcAdminClient.clients.listProtocolMappers({ id: clientId }); id: clientId,
});
const existingMapper = mappers.find((mapper) => mapper.name === "MinIO Policy"); const existingMapper = mappers.find((mapper) => mapper.name === "MinIO Policy");
if (existingMapper) { if (existingMapper) {
console.log("MinIO Policy mapper already exists."); console.log("MinIO Policy mapper already exists.");
} else { } else {
// Create the protocol mapper
await kcAdminClient.clients.addProtocolMapper( await kcAdminClient.clients.addProtocolMapper(
{ id: clientId }, { id: clientId },
{ {

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env tsx #!/usr/bin/env tsx
import KcAdminClient from '@keycloak/keycloak-admin-client'; import KcAdminClient from "@keycloak/keycloak-admin-client";
import invariant from 'tiny-invariant'; import invariant from "tiny-invariant";
const main = async () => { const main = async () => {
const KEYCLOAK_HOST = process.env.KEYCLOAK_HOST; const KEYCLOAK_HOST = process.env.KEYCLOAK_HOST;
@@ -11,57 +11,52 @@ const main = async () => {
const clientId = process.env.KEYCLOAK_CLIENT_ID; const clientId = process.env.KEYCLOAK_CLIENT_ID;
const scopeName = process.env.SCOPE_NAME; const scopeName = process.env.SCOPE_NAME;
invariant(KEYCLOAK_HOST, 'KEYCLOAK_HOST 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_USER, "KEYCLOAK_ADMIN_USER environment variable is required");
invariant(KEYCLOAK_ADMIN_PASSWORD, 'KEYCLOAK_ADMIN_PASSWORD environment variable is required'); invariant(KEYCLOAK_ADMIN_PASSWORD, "KEYCLOAK_ADMIN_PASSWORD environment variable is required");
invariant(realm, 'KEYCLOAK_REALM environment variable is required'); invariant(realm, "KEYCLOAK_REALM environment variable is required");
invariant(clientId, 'KEYCLOAK_CLIENT_ID environment variable is required'); invariant(clientId, "KEYCLOAK_CLIENT_ID environment variable is required");
invariant(scopeName, 'SCOPE_NAME environment variable is required'); invariant(scopeName, "SCOPE_NAME environment variable is required");
const kcAdminClient = new KcAdminClient({ const kcAdminClient = new KcAdminClient({
baseUrl: `https://${KEYCLOAK_HOST}`, baseUrl: `https://${KEYCLOAK_HOST}`,
realmName: 'master', realmName: "master",
}); });
try { try {
await kcAdminClient.auth({ await kcAdminClient.auth({
username: KEYCLOAK_ADMIN_USER, username: KEYCLOAK_ADMIN_USER,
password: KEYCLOAK_ADMIN_PASSWORD, password: KEYCLOAK_ADMIN_PASSWORD,
grantType: 'password', grantType: "password",
clientId: 'admin-cli', clientId: "admin-cli",
}); });
console.log('Authentication successful.'); console.log("Authentication successful.");
// Set target realm
kcAdminClient.setConfig({ kcAdminClient.setConfig({
realmName: realm, realmName: realm,
}); });
// Find the client
const clients = await kcAdminClient.clients.find({ clientId }); const clients = await kcAdminClient.clients.find({ clientId });
if (clients.length === 0) { if (clients.length === 0) {
throw new Error(`Client '${clientId}' not found`); throw new Error(`Client '${clientId}' not found`);
} }
const client = clients[0]; const client = clients[0];
// Find the client scope
const clientScopes = await kcAdminClient.clientScopes.find(); const clientScopes = await kcAdminClient.clientScopes.find();
const scope = clientScopes.find(s => s.name === scopeName); const scope = clientScopes.find((s) => s.name === scopeName);
if (!scope) { if (!scope) {
throw new Error(`Client scope '${scopeName}' not found`); throw new Error(`Client scope '${scopeName}' not found`);
} }
// Add scope to client as default scope
await kcAdminClient.clients.addDefaultClientScope({ await kcAdminClient.clients.addDefaultClientScope({
id: client.id!, id: client.id!,
clientScopeId: scope.id!, clientScopeId: scope.id!,
}); });
console.log(`Client scope '${scopeName}' added to client '${clientId}' as default scope.`); console.log(`Client scope '${scopeName}' added to client '${clientId}' as default scope.`);
} catch (error) { } catch (error) {
console.error('Error adding scope to client:', error); console.error("Error adding scope to client:", error);
process.exit(1); process.exit(1);
} }
}; };

View File

@@ -27,18 +27,14 @@ async function main() {
invariant(clientId, "KEYCLOAK_CLIENT_ID is required"); invariant(clientId, "KEYCLOAK_CLIENT_ID is required");
invariant(roleName, "KEYCLOAK_ROLE_NAME is required"); invariant(roleName, "KEYCLOAK_ROLE_NAME is required");
// Find the user
const users = await kcAdminClient.users.find({ realm, username }); const users = await kcAdminClient.users.find({ realm, username });
if (users.length === 0) { if (users.length === 0) {
throw new Error(`User '${username}' not found in realm '${realm}'`); throw new Error(`User '${username}' not found in realm '${realm}'`);
} }
const user = users[0]; const user = users[0];
// Find the client
const clients = await kcAdminClient.clients.find({ realm, clientId }); const clients = await kcAdminClient.clients.find({ realm, clientId });
if (clients.length === 0) { if (clients.length === 0) {
throw new Error(`Client '${clientId}' not found in realm '${realm}'`); throw new Error(`Client '${clientId}' not found in realm '${realm}'`);
} }
@@ -60,35 +56,28 @@ async function main() {
}); });
console.error( console.error(
`Available roles for client '${clientId}':`, `Available roles for client '${clientId}':`,
allRoles.map((r) => r.name), allRoles.map((r) => r.name)
); );
throw new Error( 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) { if (!role) {
throw new Error( throw new Error(`Role '${roleName}' not found for client '${clientId}' in realm '${realm}'`);
`Role '${roleName}' not found for client '${clientId}' in realm '${realm}'`,
);
} }
// Check if user already has this role
const existingRoles = await kcAdminClient.users.listClientRoleMappings({ const existingRoles = await kcAdminClient.users.listClientRoleMappings({
realm, realm,
id: user.id!, id: user.id!,
clientUniqueId: client.id!, clientUniqueId: client.id!,
}); });
const hasRole = existingRoles.some((r) => r.name === roleName); const hasRole = existingRoles.some((r) => r.name === roleName);
if (hasRole) { if (hasRole) {
console.log( console.log(`User '${username}' already has role '${roleName}' for client '${clientId}'`);
`User '${username}' already has role '${roleName}' for client '${clientId}'`,
);
return; return;
} }
// Add role to user
await kcAdminClient.users.addClientRoleMappings({ await kcAdminClient.users.addClientRoleMappings({
realm, realm,
id: user.id!, id: user.id!,
@@ -102,7 +91,7 @@ async function main() {
}); });
console.log( 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}'`
); );
} }

View File

@@ -36,30 +36,26 @@ const main = async () => {
kcAdminClient.setConfig({ realmName }); kcAdminClient.setConfig({ realmName });
// Find user
const users = await kcAdminClient.users.find({ username }); 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) { if (!user) {
throw new Error(`User '${username}' not found`); throw new Error(`User '${username}' not found`);
} }
// Find group
const groups = await kcAdminClient.groups.find({ search: groupName }); 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) { if (!group) {
throw new Error(`Group '${groupName}' not found`); throw new Error(`Group '${groupName}' not found`);
} }
// Check if user is already in group
const userGroups = await kcAdminClient.users.listGroups({ id: user.id! }); 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) { if (isAlreadyMember) {
console.log(`User '${username}' is already a member of group '${groupName}'`); console.log(`User '${username}' is already a member of group '${groupName}'`);
return; return;
} }
// Add user to group
await kcAdminClient.users.addToGroup({ await kcAdminClient.users.addToGroup({
id: user.id!, id: user.id!,
groupId: group.id!, groupId: group.id!,

View File

@@ -23,15 +23,11 @@ async function main() {
invariant(clientId, "KEYCLOAK_CLIENT_ID is required"); invariant(clientId, "KEYCLOAK_CLIENT_ID is required");
try { try {
// Find the client
const clients = await kcAdminClient.clients.find({ realm, clientId }); const clients = await kcAdminClient.clients.find({ realm, clientId });
if (clients.length === 0) { if (clients.length === 0) {
throw new Error(`Client '${clientId}' not found in realm '${realm}'`); throw new Error(`Client '${clientId}' not found in realm '${realm}'`);
} }
const client = clients[0]; const client = clients[0];
// Get all protocol mappers with full details
const mappers = await kcAdminClient.clients.listProtocolMappers({ const mappers = await kcAdminClient.clients.listProtocolMappers({
realm, realm,
id: client.id!, id: client.id!,
@@ -50,25 +46,21 @@ async function main() {
} }
}); });
// Check client scope assignments
console.log(`\n=== Client Scope Assignments ===`); console.log(`\n=== Client Scope Assignments ===`);
// Get default client scopes
const defaultScopes = await kcAdminClient.clients.listDefaultClientScopes({ const defaultScopes = await kcAdminClient.clients.listDefaultClientScopes({
realm, realm,
id: client.id!, 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({ const optionalScopes = await kcAdminClient.clients.listOptionalClientScopes({
realm, realm,
id: client.id!, 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) { } catch (error) {
console.error(`Error: ${error}`); console.error(`Error: ${error}`);
process.exit(1); process.exit(1);

View File

@@ -23,15 +23,14 @@ async function main() {
invariant(clientId, "KEYCLOAK_CLIENT_ID is required"); invariant(clientId, "KEYCLOAK_CLIENT_ID is required");
try { try {
// Find the client by clientId
const clients = await kcAdminClient.clients.find({ realm, clientId }); const clients = await kcAdminClient.clients.find({ realm, clientId });
if (clients.length > 0) { if (clients.length > 0) {
console.log(`Client '${clientId}' exists in realm '${realm}'`); console.log(`Client '${clientId}' exists in realm '${realm}'`);
process.exit(0); // Success - client exists process.exit(0);
} else { } else {
console.log(`Client '${clientId}' does not exist in realm '${realm}'`); console.log(`Client '${clientId}' does not exist in realm '${realm}'`);
process.exit(1); // Client doesn't exist process.exit(1);
} }
} catch (error) { } catch (error) {
console.error(`Error checking client existence: ${error}`); console.error(`Error checking client existence: ${error}`);

View File

@@ -24,16 +24,12 @@ async function main() {
invariant(clientId, "KEYCLOAK_CLIENT_ID is required"); invariant(clientId, "KEYCLOAK_CLIENT_ID is required");
invariant(roleName, "KEYCLOAK_ROLE_NAME is required"); invariant(roleName, "KEYCLOAK_ROLE_NAME is required");
// Find the client by clientId
const clients = await kcAdminClient.clients.find({ realm, clientId }); const clients = await kcAdminClient.clients.find({ realm, clientId });
if (clients.length === 0) { if (clients.length === 0) {
throw new Error(`Client '${clientId}' not found in realm '${realm}'`); throw new Error(`Client '${clientId}' not found in realm '${realm}'`);
} }
const client = clients[0]; const client = clients[0];
// Check if role already exists
try { try {
const existingRole = await kcAdminClient.clients.findRole({ const existingRole = await kcAdminClient.clients.findRole({
realm, realm,
@@ -49,17 +45,13 @@ async function main() {
console.log(`Role '${roleName}' doesn't exist, creating it...`); console.log(`Role '${roleName}' doesn't exist, creating it...`);
} }
// Create the client role
await kcAdminClient.clients.createRole({ await kcAdminClient.clients.createRole({
realm, realm,
id: client.id!, id: client.id!,
name: roleName, name: roleName,
}); });
console.log( console.log(`✓ Client role '${roleName}' created for client '${clientId}' in realm '${realm}'`);
`✓ Client role '${roleName}' created for client '${clientId}' in realm '${realm}'`,
);
} }
main().catch(console.error); main().catch(console.error);

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env tsx #!/usr/bin/env tsx
import KcAdminClient from '@keycloak/keycloak-admin-client'; import KcAdminClient from "@keycloak/keycloak-admin-client";
import invariant from 'tiny-invariant'; import invariant from "tiny-invariant";
const main = async () => { const main = async () => {
const KEYCLOAK_HOST = process.env.KEYCLOAK_HOST; const KEYCLOAK_HOST = process.env.KEYCLOAK_HOST;
@@ -9,55 +9,53 @@ const main = async () => {
const KEYCLOAK_ADMIN_PASSWORD = process.env.KEYCLOAK_ADMIN_PASSWORD; const KEYCLOAK_ADMIN_PASSWORD = process.env.KEYCLOAK_ADMIN_PASSWORD;
const realm = process.env.KEYCLOAK_REALM; const realm = process.env.KEYCLOAK_REALM;
const scopeName = process.env.SCOPE_NAME; const scopeName = process.env.SCOPE_NAME;
const description = process.env.DESCRIPTION || ''; const description = process.env.DESCRIPTION || "";
invariant(KEYCLOAK_HOST, 'KEYCLOAK_HOST 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_USER, "KEYCLOAK_ADMIN_USER environment variable is required");
invariant(KEYCLOAK_ADMIN_PASSWORD, 'KEYCLOAK_ADMIN_PASSWORD environment variable is required'); invariant(KEYCLOAK_ADMIN_PASSWORD, "KEYCLOAK_ADMIN_PASSWORD environment variable is required");
invariant(realm, 'KEYCLOAK_REALM environment variable is required'); invariant(realm, "KEYCLOAK_REALM environment variable is required");
invariant(scopeName, 'SCOPE_NAME environment variable is required'); invariant(scopeName, "SCOPE_NAME environment variable is required");
const kcAdminClient = new KcAdminClient({ const kcAdminClient = new KcAdminClient({
baseUrl: `https://${KEYCLOAK_HOST}`, baseUrl: `https://${KEYCLOAK_HOST}`,
realmName: 'master', realmName: "master",
}); });
try { try {
await kcAdminClient.auth({ await kcAdminClient.auth({
username: KEYCLOAK_ADMIN_USER, username: KEYCLOAK_ADMIN_USER,
password: KEYCLOAK_ADMIN_PASSWORD, password: KEYCLOAK_ADMIN_PASSWORD,
grantType: 'password', grantType: "password",
clientId: 'admin-cli', clientId: "admin-cli",
}); });
console.log('Authentication successful.'); console.log("Authentication successful.");
// Set target realm
kcAdminClient.setConfig({ kcAdminClient.setConfig({
realmName: realm, realmName: realm,
}); });
// Check if scope already exists
const existingScopes = await kcAdminClient.clientScopes.find(); const existingScopes = await kcAdminClient.clientScopes.find();
const existingScope = existingScopes.find(scope => scope.name === scopeName); const existingScope = existingScopes.find((scope) => scope.name === scopeName);
if (existingScope) { if (existingScope) {
console.log(`Client scope '${scopeName}' already exists.`); console.log(`Client scope '${scopeName}' already exists.`);
return; return;
} }
// Create client scope
const result = await kcAdminClient.clientScopes.create({ const result = await kcAdminClient.clientScopes.create({
name: scopeName, name: scopeName,
description: description || `${scopeName} scope`, description: description || `${scopeName} scope`,
protocol: 'openid-connect', protocol: "openid-connect",
includeInTokenScope: true, attributes: {
"include.in.token.scope": "true",
},
}); });
console.log(`Client scope '${scopeName}' created successfully with ID: ${result.id}`); console.log(`Client scope '${scopeName}' created successfully with ID: ${result.id}`);
} catch (error) { } catch (error) {
console.error('Error creating client scope:', error); console.error("Error creating client scope:", error);
process.exit(1); process.exit(1);
} }
}; };

View File

@@ -22,7 +22,7 @@ const main = async () => {
const redirectUrl = process.env.KEYCLOAK_REDIRECT_URL; const redirectUrl = process.env.KEYCLOAK_REDIRECT_URL;
invariant(redirectUrl, "KEYCLOAK_REDIRECT_URL environment variable is required"); 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 sessionIdle = process.env.KEYCLOAK_CLIENT_SESSION_IDLE;
const sessionMax = process.env.KEYCLOAK_CLIENT_SESSION_MAX; const sessionMax = process.env.KEYCLOAK_CLIENT_SESSION_MAX;
@@ -53,59 +53,61 @@ const main = async () => {
return; return;
} }
const isPublicClient = !clientSecret || clientSecret === ''; const isPublicClient = !clientSecret || clientSecret === "";
const clientConfig: any = { const clientConfig: any = {
clientId: clientId, clientId: clientId,
secret: clientSecret, secret: clientSecret,
enabled: true, enabled: true,
redirectUris: redirectUris, redirectUris: redirectUris,
publicClient: isPublicClient, publicClient: isPublicClient,
directAccessGrantsEnabled: directAccessGrants === 'true', directAccessGrantsEnabled: directAccessGrants === "true",
}; };
// Configure PKCE based on environment variable // Configure PKCE based on environment variable
// KEYCLOAK_CLIENT_PKCE_METHOD can be: 'S256', 'plain', or unset/empty (no PKCE) // KEYCLOAK_CLIENT_PKCE_METHOD can be: 'S256', 'plain', or unset/empty (no PKCE)
clientConfig.attributes = {}; clientConfig.attributes = {};
if (pkceMethod && (pkceMethod === 'S256' || pkceMethod === 'plain')) { if (pkceMethod && (pkceMethod === "S256" || pkceMethod === "plain")) {
clientConfig.attributes['pkce.code.challenge.method'] = pkceMethod; clientConfig.attributes["pkce.code.challenge.method"] = pkceMethod;
console.log(`Setting PKCE Code Challenge Method to ${pkceMethod}`); console.log(`Setting PKCE Code Challenge Method to ${pkceMethod}`);
} else if (pkceMethod && pkceMethod !== '') { } else if (pkceMethod && pkceMethod !== "") {
console.warn(`Invalid PKCE method '${pkceMethod}'. Valid options: S256, plain, or empty for no PKCE`); console.warn(
console.log('Creating client without PKCE'); `Invalid PKCE method '${pkceMethod}'. Valid options: S256, plain, or empty for no PKCE`
);
console.log("Creating client without PKCE");
} else { } else {
console.log('Creating client without PKCE'); console.log("Creating client without PKCE");
} }
// Add session timeout settings if provided // Add session timeout settings if provided
if (sessionIdle && sessionIdle !== '') { if (sessionIdle && sessionIdle !== "") {
clientConfig.attributes['client.session.idle.timeout'] = sessionIdle; clientConfig.attributes["client.session.idle.timeout"] = sessionIdle;
console.log(`Setting Client Session Idle Timeout: ${sessionIdle}`); console.log(`Setting Client Session Idle Timeout: ${sessionIdle}`);
} }
if (sessionMax && sessionMax !== '') { if (sessionMax && sessionMax !== "") {
clientConfig.attributes['client.session.max.lifespan'] = sessionMax; clientConfig.attributes["client.session.max.lifespan"] = sessionMax;
console.log(`Setting Client Session Max Lifespan: ${sessionMax}`); console.log(`Setting Client Session Max Lifespan: ${sessionMax}`);
} }
// Add post logout redirect URIs if provided // 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) // 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 = clientConfig.attributes || {};
clientConfig.attributes['post.logout.redirect.uris'] = postLogoutUris.join('##'); clientConfig.attributes["post.logout.redirect.uris"] = postLogoutUris.join("##");
console.log(`Setting Post Logout Redirect URIs: ${postLogoutUris.join(', ')}`); console.log(`Setting Post Logout Redirect URIs: ${postLogoutUris.join(", ")}`);
} }
// Add access token lifespan if provided // Add access token lifespan if provided
if (accessTokenLifespan && accessTokenLifespan !== '') { if (accessTokenLifespan && accessTokenLifespan !== "") {
clientConfig.attributes = clientConfig.attributes || {}; clientConfig.attributes = clientConfig.attributes || {};
clientConfig.attributes['access.token.lifespan'] = accessTokenLifespan; clientConfig.attributes["access.token.lifespan"] = accessTokenLifespan;
console.log(`Setting Access Token Lifespan: ${accessTokenLifespan} seconds`); console.log(`Setting Access Token Lifespan: ${accessTokenLifespan} seconds`);
} }
if (directAccessGrants === 'true') { if (directAccessGrants === "true") {
console.log('Enabling Direct Access Grants (Resource Owner Password Credentials)'); console.log("Enabling Direct Access Grants (Resource Owner Password Credentials)");
} }
const createdClient = await kcAdminClient.clients.create(clientConfig); const createdClient = await kcAdminClient.clients.create(clientConfig);

View File

@@ -36,33 +36,35 @@ const main = async () => {
kcAdminClient.setConfig({ realmName }); kcAdminClient.setConfig({ realmName });
// Check if group already exists const existingGroups = await kcAdminClient.groups.find({
const existingGroups = await kcAdminClient.groups.find({ search: groupName }); search: groupName,
const existingGroup = existingGroups.find(group => group.name === groupName); });
const existingGroup = existingGroups.find((group) => group.name === groupName);
if (existingGroup) { if (existingGroup) {
console.log(`Group '${groupName}' already exists with ID: ${existingGroup.id}`); console.log(`Group '${groupName}' already exists with ID: ${existingGroup.id}`);
return; return;
} }
// Find parent group if specified
let parentGroupId: string | undefined; let parentGroupId: string | undefined;
if (parentGroupName) { if (parentGroupName) {
const parentGroups = await kcAdminClient.groups.find({ search: parentGroupName }); const parentGroups = await kcAdminClient.groups.find({
const parentGroup = parentGroups.find(group => group.name === parentGroupName); search: parentGroupName,
});
const parentGroup = parentGroups.find((group) => group.name === parentGroupName);
if (!parentGroup) { if (!parentGroup) {
throw new Error(`Parent group '${parentGroupName}' not found`); throw new Error(`Parent group '${parentGroupName}' not found`);
} }
parentGroupId = parentGroup.id; parentGroupId = parentGroup.id;
} }
// Create group payload
const groupPayload = { const groupPayload = {
name: groupName, 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.createChildGroup({ id: parentGroupId }, groupPayload)
: await kcAdminClient.groups.create(groupPayload); : await kcAdminClient.groups.create(groupPayload);

View File

@@ -17,7 +17,9 @@ const main = async () => {
// Token lifespan settings (with defaults suitable for JupyterHub) // Token lifespan settings (with defaults suitable for JupyterHub)
const accessTokenLifespan = parseInt(process.env.ACCESS_TOKEN_LIFESPAN || "3600"); // 1 hour 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 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 ssoSessionIdleTimeout = parseInt(process.env.SSO_SESSION_IDLE_TIMEOUT || "7200"); // 2 hours
const kcAdminClient = new KcAdminClient({ const kcAdminClient = new KcAdminClient({
@@ -59,10 +61,18 @@ const main = async () => {
clientSessionIdleTimeout: Math.min(accessTokenLifespan, ssoSessionIdleTimeout), clientSessionIdleTimeout: Math.min(accessTokenLifespan, ssoSessionIdleTimeout),
}); });
console.log(`Realm '${realmName}' created successfully with token settings:`); console.log(`Realm '${realmName}' created successfully with token settings:`);
console.log(` - Access Token Lifespan: ${accessTokenLifespan} seconds (${accessTokenLifespan/60} minutes)`); console.log(
console.log(` - Refresh Token Lifespan: ${refreshTokenLifespan} seconds (${refreshTokenLifespan/60} minutes)`); ` - Access Token Lifespan: ${accessTokenLifespan} seconds (${accessTokenLifespan / 60} minutes)`
console.log(` - SSO Session Max: ${ssoSessionMaxLifespan} seconds (${ssoSessionMaxLifespan/60} minutes)`); );
console.log(` - SSO Session Idle: ${ssoSessionIdleTimeout} seconds (${ssoSessionIdleTimeout/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) { } catch (error) {
console.error("An error occurred:", error); console.error("An error occurred:", error);
// eslint-disable-next-line unicorn/no-process-exit // eslint-disable-next-line unicorn/no-process-exit

View File

@@ -19,7 +19,7 @@ const main = async () => {
const kcAdminClient = new KcAdminClient({ const kcAdminClient = new KcAdminClient({
baseUrl: `https://${keycloakHost}`, baseUrl: `https://${keycloakHost}`,
realmName: 'master', realmName: "master",
}); });
try { try {

View File

@@ -33,36 +33,32 @@ const main = async () => {
kcAdminClient.setConfig({ realmName }); kcAdminClient.setConfig({ realmName });
// Find group to delete
const groups = await kcAdminClient.groups.find({ search: groupName }); 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) { if (!group) {
console.log(`Group '${groupName}' not found`); console.log(`Group '${groupName}' not found`);
return; return;
} }
// Check if group has members
const groupMembers = await kcAdminClient.groups.listMembers({ id: group.id! }); const groupMembers = await kcAdminClient.groups.listMembers({ id: group.id! });
if (groupMembers && groupMembers.length > 0) { if (groupMembers && groupMembers.length > 0) {
console.log(`Warning: Group '${groupName}' has ${groupMembers.length} members:`); 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(` - ${member.username} (${member.firstName} ${member.lastName})`);
}); });
console.log("All members will be removed from the group when it's deleted."); console.log("All members will be removed from the group when it's deleted.");
} }
// Check for subgroups const subGroups = await kcAdminClient.groups.listSubGroups({ parentId: group.id! });
const subGroups = await kcAdminClient.groups.listSubGroups({ id: group.id! });
if (subGroups && subGroups.length > 0) { if (subGroups && subGroups.length > 0) {
console.log(`Warning: Group '${groupName}' has ${subGroups.length} subgroups:`); console.log(`Warning: Group '${groupName}' has ${subGroups.length} subgroups:`);
subGroups.forEach(subGroup => { subGroups.forEach((subGroup) => {
console.log(` - ${subGroup.name}`); console.log(` - ${subGroup.name}`);
}); });
console.log("All subgroups will be deleted as well."); console.log("All subgroups will be deleted as well.");
} }
// Delete group
await kcAdminClient.groups.del({ id: group.id! }); await kcAdminClient.groups.del({ id: group.id! });
console.log(`Group '${groupName}' deleted successfully`); console.log(`Group '${groupName}' deleted successfully`);

View File

@@ -15,7 +15,9 @@ const main = async () => {
invariant(realmNameToDelete, "KEYCLOAK_REALM_TO_DELETE environment variable is required"); invariant(realmNameToDelete, "KEYCLOAK_REALM_TO_DELETE environment variable is required");
if (realmNameToDelete === "master") { 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 // eslint-disable-next-line unicorn/no-process-exit
process.exit(1); process.exit(1);
} }
@@ -45,7 +47,6 @@ const main = async () => {
console.log(`Attempting to delete realm: '${realmNameToDelete}'...`); console.log(`Attempting to delete realm: '${realmNameToDelete}'...`);
await kcAdminClient.realms.del({ realm: realmNameToDelete }); await kcAdminClient.realms.del({ realm: realmNameToDelete });
console.log(`Realm '${realmNameToDelete}' deleted successfully.`); console.log(`Realm '${realmNameToDelete}' deleted successfully.`);
} catch (error) { } catch (error) {
console.error(`An error occurred while trying to delete realm '${realmNameToDelete}':`, error); console.error(`An error occurred while trying to delete realm '${realmNameToDelete}':`, error);
const err = error as any; const err = error as any;
@@ -58,4 +59,3 @@ const main = async () => {
}; };
main(); main();

View File

@@ -36,30 +36,26 @@ const main = async () => {
kcAdminClient.setConfig({ realmName }); kcAdminClient.setConfig({ realmName });
// Find user
const users = await kcAdminClient.users.find({ username }); 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) { if (!user) {
throw new Error(`User '${username}' not found`); throw new Error(`User '${username}' not found`);
} }
// Find group
const groups = await kcAdminClient.groups.find({ search: groupName }); 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) { if (!group) {
throw new Error(`Group '${groupName}' not found`); throw new Error(`Group '${groupName}' not found`);
} }
// Check if user is in group
const userGroups = await kcAdminClient.users.listGroups({ id: user.id! }); 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) { if (!isMember) {
console.log(`User '${username}' is not a member of group '${groupName}'`); console.log(`User '${username}' is not a member of group '${groupName}'`);
return; return;
} }
// Remove user from group
await kcAdminClient.users.delFromGroup({ await kcAdminClient.users.delFromGroup({
id: user.id!, id: user.id!,
groupId: group.id!, groupId: group.id!,

View File

@@ -23,15 +23,12 @@ async function main() {
invariant(clientId, "KEYCLOAK_CLIENT_ID is required"); invariant(clientId, "KEYCLOAK_CLIENT_ID is required");
try { try {
// Find the client
const clients = await kcAdminClient.clients.find({ realm, clientId }); const clients = await kcAdminClient.clients.find({ realm, clientId });
if (clients.length === 0) { if (clients.length === 0) {
throw new Error(`Client '${clientId}' not found in realm '${realm}'`); throw new Error(`Client '${clientId}' not found in realm '${realm}'`);
} }
const client = clients[0]; const client = clients[0];
// Get full client details
const clientDetails = await kcAdminClient.clients.findOne({ const clientDetails = await kcAdminClient.clients.findOne({
realm, realm,
id: client.id!, id: client.id!,
@@ -39,17 +36,16 @@ async function main() {
console.log("=== Client Configuration ==="); console.log("=== Client Configuration ===");
console.log(`Client ID: ${clientDetails?.clientId}`); 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(`Client Authenticator: ${clientDetails?.clientAuthenticatorType}`);
console.log(`Standard Flow Enabled: ${clientDetails?.standardFlowEnabled}`); console.log(`Standard Flow Enabled: ${clientDetails?.standardFlowEnabled}`);
console.log(`Direct Access Grants: ${clientDetails?.directAccessGrantsEnabled}`); console.log(`Direct Access Grants: ${clientDetails?.directAccessGrantsEnabled}`);
console.log(`Service Accounts Enabled: ${clientDetails?.serviceAccountsEnabled}`); console.log(`Service Accounts Enabled: ${clientDetails?.serviceAccountsEnabled}`);
console.log(`Valid Redirect URIs: ${JSON.stringify(clientDetails?.redirectUris, null, 2)}`); console.log(`Valid Redirect URIs: ${JSON.stringify(clientDetails?.redirectUris, null, 2)}`);
console.log(`Base URL: ${clientDetails?.baseUrl || 'Not set'}`); console.log(`Base URL: ${clientDetails?.baseUrl || "Not set"}`);
console.log(`Root URL: ${clientDetails?.rootUrl || 'Not set'}`); console.log(`Root URL: ${clientDetails?.rootUrl || "Not set"}`);
console.log(`Web Origins: ${JSON.stringify(clientDetails?.webOrigins, null, 2)}`); console.log(`Web Origins: ${JSON.stringify(clientDetails?.webOrigins, null, 2)}`);
// Get client secret if confidential
if (!clientDetails?.publicClient) { if (!clientDetails?.publicClient) {
try { try {
const clientSecret = await kcAdminClient.clients.getClientSecret({ const clientSecret = await kcAdminClient.clients.getClientSecret({
@@ -61,7 +57,6 @@ async function main() {
console.log(`Client Secret: Error retrieving - ${error}`); console.log(`Client Secret: Error retrieving - ${error}`);
} }
} }
} catch (error) { } catch (error) {
console.error(`Error retrieving client details: ${error}`); console.error(`Error retrieving client details: ${error}`);
process.exit(1); process.exit(1);

View File

@@ -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();

View File

@@ -23,22 +23,18 @@ async function main() {
invariant(clientId, "KEYCLOAK_CLIENT_ID is required"); invariant(clientId, "KEYCLOAK_CLIENT_ID is required");
try { try {
// Find the client
const clients = await kcAdminClient.clients.find({ realm, clientId }); const clients = await kcAdminClient.clients.find({ realm, clientId });
if (clients.length === 0) { if (clients.length === 0) {
throw new Error(`Client '${clientId}' not found in realm '${realm}'`); throw new Error(`Client '${clientId}' not found in realm '${realm}'`);
} }
const client = clients[0]; const client = clients[0];
// Get client secret
const clientSecret = await kcAdminClient.clients.getClientSecret({ const clientSecret = await kcAdminClient.clients.getClientSecret({
realm, realm,
id: client.id!, id: client.id!,
}); });
console.log(`Client '${clientId}' secret: ${clientSecret.value}`); console.log(`Client '${clientId}' secret: ${clientSecret.value}`);
} catch (error) { } catch (error) {
console.error(`Error retrieving client secret: ${error}`); console.error(`Error retrieving client secret: ${error}`);
process.exit(1); process.exit(1);

View File

@@ -44,8 +44,8 @@ const main = async () => {
console.log(`=== Client Details: ${clientId} ===`); console.log(`=== Client Details: ${clientId} ===`);
console.log(`ID: ${client.id}`); console.log(`ID: ${client.id}`);
console.log(`Client ID: ${client.clientId}`); console.log(`Client ID: ${client.clientId}`);
console.log(`Name: ${client.name || 'N/A'}`); console.log(`Name: ${client.name || "N/A"}`);
console.log(`Description: ${client.description || 'N/A'}`); console.log(`Description: ${client.description || "N/A"}`);
console.log(`Enabled: ${client.enabled}`); console.log(`Enabled: ${client.enabled}`);
console.log(`Protocol: ${client.protocol}`); console.log(`Protocol: ${client.protocol}`);
console.log(`Public Client: ${client.publicClient}`); console.log(`Public Client: ${client.publicClient}`);
@@ -108,7 +108,6 @@ const main = async () => {
console.log(""); console.log("");
} }
// Get protocol mappers
const protocolMappers = await kcAdminClient.clients.listProtocolMappers({ id: client.id! }); const protocolMappers = await kcAdminClient.clients.listProtocolMappers({ id: client.id! });
if (protocolMappers.length > 0) { if (protocolMappers.length > 0) {
console.log("Protocol Mappers:"); console.log("Protocol Mappers:");
@@ -122,7 +121,6 @@ const main = async () => {
}); });
console.log(""); console.log("");
} }
} catch (error) { } catch (error) {
console.error("An error occurred:", error); console.error("An error occurred:", error);
process.exit(1); process.exit(1);

View File

@@ -24,14 +24,12 @@ async function main() {
invariant(username, "USERNAME is required"); invariant(username, "USERNAME is required");
invariant(clientId, "KEYCLOAK_CLIENT_ID is required"); invariant(clientId, "KEYCLOAK_CLIENT_ID is required");
// Find the user
const users = await kcAdminClient.users.find({ realm, username }); const users = await kcAdminClient.users.find({ realm, username });
if (users.length === 0) { if (users.length === 0) {
throw new Error(`User '${username}' not found in realm '${realm}'`); throw new Error(`User '${username}' not found in realm '${realm}'`);
} }
const user = users[0]; const user = users[0];
// Find the client
const clients = await kcAdminClient.clients.find({ realm, clientId }); const clients = await kcAdminClient.clients.find({ realm, clientId });
if (clients.length === 0) { if (clients.length === 0) {
throw new Error(`Client '${clientId}' not found in realm '${realm}'`); throw new Error(`Client '${clientId}' not found in realm '${realm}'`);
@@ -39,16 +37,13 @@ async function main() {
const client = clients[0]; const client = clients[0];
try { try {
// Get a token for this user (impersonation)
console.log(`Getting token for user '${username}' with client '${clientId}'...`); console.log(`Getting token for user '${username}' with client '${clientId}'...`);
// Get client secret
const clientSecret = await kcAdminClient.clients.getClientSecret({ const clientSecret = await kcAdminClient.clients.getClientSecret({
realm, realm,
id: client.id!, id: client.id!,
}); });
// Create a new client instance for the specific realm/client
const userClient = new KcAdminClient({ const userClient = new KcAdminClient({
baseUrl: `https://${process.env.KEYCLOAK_HOST}`, baseUrl: `https://${process.env.KEYCLOAK_HOST}`,
realmName: realm, realmName: realm,
@@ -72,23 +67,21 @@ async function main() {
console.log(`Protocol: ${client.protocol}`); console.log(`Protocol: ${client.protocol}`);
console.log(`Public Client: ${client.publicClient}`); console.log(`Public Client: ${client.publicClient}`);
// Get protocol mappers
const mappers = await kcAdminClient.clients.listProtocolMappers({ const mappers = await kcAdminClient.clients.listProtocolMappers({
realm, realm,
id: client.id!, id: client.id!,
}); });
console.log("\n=== Protocol Mappers ==="); console.log("\n=== Protocol Mappers ===");
mappers.forEach(mapper => { mappers.forEach((mapper) => {
console.log(`- ${mapper.name} (${mapper.protocolMapper})`); console.log(`- ${mapper.name} (${mapper.protocolMapper})`);
if (mapper.config) { if (mapper.config) {
console.log(` Claim: ${mapper.config['claim.name'] || 'N/A'}`); console.log(` Claim: ${mapper.config["claim.name"] || "N/A"}`);
console.log(` Access Token: ${mapper.config['access.token.claim'] || 'false'}`); console.log(` Access Token: ${mapper.config["access.token.claim"] || "false"}`);
console.log(` ID Token: ${mapper.config['id.token.claim'] || 'false'}`); console.log(` ID Token: ${mapper.config["id.token.claim"] || "false"}`);
} }
}); });
// Show user's client roles
const clientRoles = await kcAdminClient.users.listClientRoleMappings({ const clientRoles = await kcAdminClient.users.listClientRoleMappings({
realm, realm,
id: user.id!, id: user.id!,
@@ -99,11 +92,10 @@ async function main() {
if (clientRoles.length === 0) { if (clientRoles.length === 0) {
console.log("No client roles assigned"); console.log("No client roles assigned");
} else { } else {
clientRoles.forEach(role => { clientRoles.forEach((role) => {
console.log(`- ${role.name} (${role.id})`); console.log(`- ${role.name} (${role.id})`);
}); });
} }
} catch (error) { } catch (error) {
console.error(`Error: ${error}`); console.error(`Error: ${error}`);
} }

View File

@@ -39,18 +39,18 @@ const main = async () => {
const status = client.enabled ? "Enabled" : "Disabled"; const status = client.enabled ? "Enabled" : "Disabled";
const protocol = client.protocol || "unknown"; 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(` ID: ${client.id}`);
console.log(` Type: ${clientType}`); console.log(` Type: ${clientType}`);
console.log(` Protocol: ${protocol}`); console.log(` Protocol: ${protocol}`);
console.log(` Status: ${status}`); console.log(` Status: ${status}`);
if (client.redirectUris && client.redirectUris.length > 0) { 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) { if (client.webOrigins && client.webOrigins.length > 0) {
console.log(` Web Origins: ${client.webOrigins.join(', ')}`); console.log(` Web Origins: ${client.webOrigins.join(", ")}`);
} }
console.log(""); console.log("");

View File

@@ -24,18 +24,13 @@ async function main() {
invariant(username, "USERNAME is required"); invariant(username, "USERNAME is required");
invariant(clientId, "KEYCLOAK_CLIENT_ID is required"); invariant(clientId, "KEYCLOAK_CLIENT_ID is required");
// Find the user
const users = await kcAdminClient.users.find({ realm, username }); const users = await kcAdminClient.users.find({ realm, username });
if (users.length === 0) { if (users.length === 0) {
throw new Error(`User '${username}' not found in realm '${realm}'`); throw new Error(`User '${username}' not found in realm '${realm}'`);
} }
const user = users[0]; const user = users[0];
// Find the client
const clients = await kcAdminClient.clients.find({ realm, clientId }); const clients = await kcAdminClient.clients.find({ realm, clientId });
if (clients.length === 0) { if (clients.length === 0) {
throw new Error(`Client '${clientId}' not found in realm '${realm}'`); throw new Error(`Client '${clientId}' not found in realm '${realm}'`);
} }
@@ -43,7 +38,6 @@ async function main() {
const client = clients[0]; const client = clients[0];
try { try {
// Get user's client role mappings
const clientRoles = await kcAdminClient.users.listClientRoleMappings({ const clientRoles = await kcAdminClient.users.listClientRoleMappings({
realm, realm,
id: user.id!, id: user.id!,
@@ -63,7 +57,6 @@ async function main() {
}); });
} }
// Also show available roles for reference
const availableRoles = await kcAdminClient.clients.listRoles({ const availableRoles = await kcAdminClient.clients.listRoles({
realm, realm,
id: client.id!, id: client.id!,
@@ -75,7 +68,6 @@ async function main() {
const status = isAssigned ? "✓ assigned" : " available"; const status = isAssigned ? "✓ assigned" : " available";
console.log(` ${status}: ${role.name}`); console.log(` ${status}: ${role.name}`);
}); });
} catch (error) { } catch (error) {
console.error(`Error retrieving client roles: ${error}`); console.error(`Error retrieving client roles: ${error}`);
process.exit(1); process.exit(1);

View File

@@ -26,32 +26,25 @@ async function main() {
invariant(clientId, "KEYCLOAK_CLIENT_ID is required"); invariant(clientId, "KEYCLOAK_CLIENT_ID is required");
invariant(roleName, "KEYCLOAK_ROLE_NAME is required"); invariant(roleName, "KEYCLOAK_ROLE_NAME is required");
// Find the user
const users = await kcAdminClient.users.find({ realm, username }); const users = await kcAdminClient.users.find({ realm, username });
if (users.length === 0) { if (users.length === 0) {
throw new Error(`User '${username}' not found in realm '${realm}'`); throw new Error(`User '${username}' not found in realm '${realm}'`);
} }
const user = users[0]; const user = users[0];
// Find the client
const clients = await kcAdminClient.clients.find({ realm, clientId }); const clients = await kcAdminClient.clients.find({ realm, clientId });
if (clients.length === 0) { if (clients.length === 0) {
throw new Error(`Client '${clientId}' not found in realm '${realm}'`); throw new Error(`Client '${clientId}' not found in realm '${realm}'`);
} }
const client = clients[0]; const client = clients[0];
// Find the role
const role = await kcAdminClient.clients.findRole({ const role = await kcAdminClient.clients.findRole({
realm, realm,
id: client.id!, id: client.id!,
roleName, roleName,
}); });
// Remove role from user
await kcAdminClient.users.delClientRoleMappings({ await kcAdminClient.users.delClientRoleMappings({
realm, realm,
id: user.id!, id: user.id!,
@@ -65,7 +58,7 @@ async function main() {
}); });
console.log( 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}'`
); );
} }

View File

@@ -4,7 +4,6 @@ import KcAdminClient from "@keycloak/keycloak-admin-client";
import invariant from "tiny-invariant"; import invariant from "tiny-invariant";
const main = async () => { const main = async () => {
// Environment variables
const keycloakHost = process.env.KEYCLOAK_HOST; const keycloakHost = process.env.KEYCLOAK_HOST;
const adminUser = process.env.KEYCLOAK_ADMIN_USER; const adminUser = process.env.KEYCLOAK_ADMIN_USER;
const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD; const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD;
@@ -17,14 +16,12 @@ const main = async () => {
console.log(`Checking token settings for realm: ${realm}`); console.log(`Checking token settings for realm: ${realm}`);
// Initialize Keycloak admin client
const kcAdminClient = new KcAdminClient({ const kcAdminClient = new KcAdminClient({
baseUrl: `https://${keycloakHost}`, baseUrl: `https://${keycloakHost}`,
realmName: "master", realmName: "master",
}); });
try { try {
// Authenticate
await kcAdminClient.auth({ await kcAdminClient.auth({
username: adminUser, username: adminUser,
password: adminPassword, password: adminPassword,
@@ -34,33 +31,46 @@ const main = async () => {
console.log("✓ Authenticated with Keycloak admin"); console.log("✓ Authenticated with Keycloak admin");
// Set the target realm
kcAdminClient.setConfig({ realmName: realm }); kcAdminClient.setConfig({ realmName: realm });
// Get current realm settings
const currentRealm = await kcAdminClient.realms.findOne({ realm }); const currentRealm = await kcAdminClient.realms.findOne({ realm });
if (!currentRealm) { if (!currentRealm) {
throw new Error(`Realm ${realm} not found`); throw new Error(`Realm ${realm} not found`);
} }
console.log(`\n=== Current Token Settings for Realm: ${realm} ===`); 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(
console.log(`Access Token Lifespan (Implicit): ${currentRealm.accessTokenLifespanForImplicitFlow || 'not set'} seconds`); `Access Token Lifespan: ${currentRealm.accessTokenLifespan || "not set"} seconds (${(currentRealm.accessTokenLifespan || 0) / 60} minutes)`
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(
console.log(`Client Session Max Lifespan: ${currentRealm.clientSessionMaxLifespan || 'not set'} seconds`); `Access Token Lifespan (Implicit): ${currentRealm.accessTokenLifespanForImplicitFlow || "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(
`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}`); console.log(`Refresh Token Max Reuse: ${currentRealm.refreshTokenMaxReuse || 0}`);
// Also check specific client settings if JupyterHub client exists
try { try {
const clients = await kcAdminClient.clients.find({ clientId: 'jupyterhub' }); const clients = await kcAdminClient.clients.find({ clientId: "jupyterhub" });
if (clients.length > 0) { if (clients.length > 0) {
const jupyterhubClient = clients[0]; const jupyterhubClient = clients[0];
console.log(`\n=== JupyterHub Client Settings ===`); console.log(`\n=== JupyterHub Client Settings ===`);
console.log(`Client ID: ${jupyterhubClient.clientId}`); console.log(`Client ID: ${jupyterhubClient.clientId}`);
console.log(`Access Token Lifespan: ${jupyterhubClient.attributes?.['access.token.lifespan'] || 'inherit from realm'}`); console.log(
`Access Token Lifespan: ${jupyterhubClient.attributes?.["access.token.lifespan"] || "inherit from realm"}`
);
} }
} catch (clientError) { } catch (clientError) {
console.log(`\n⚠ Could not retrieve JupyterHub client settings: ${clientError}`); console.log(`\n⚠ Could not retrieve JupyterHub client settings: ${clientError}`);
@@ -70,7 +80,6 @@ const main = async () => {
console.log(`Default Access Token Lifespan: 300 seconds (5 minutes)`); 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 Max: 36000 seconds (10 hours)`);
console.log(`Default SSO Session Idle: 1800 seconds (30 minutes)`); console.log(`Default SSO Session Idle: 1800 seconds (30 minutes)`);
} catch (error) { } catch (error) {
console.error("✗ Failed to retrieve realm token settings:", error); console.error("✗ Failed to retrieve realm token settings:", error);
process.exit(1); process.exit(1);

View File

@@ -35,12 +35,10 @@ async function main() {
clientId: "admin-cli", clientId: "admin-cli",
}); });
// Set realm to work with
kcAdminClient.setConfig({ kcAdminClient.setConfig({
realmName, realmName,
}); });
// Find the client
const clients = await kcAdminClient.clients.find({ clientId }); const clients = await kcAdminClient.clients.find({ clientId });
if (clients.length === 0) { if (clients.length === 0) {
throw new Error(`Client '${clientId}' not found in realm '${realmName}'`); throw new Error(`Client '${clientId}' not found in realm '${realmName}'`);
@@ -49,7 +47,6 @@ async function main() {
const client = clients[0]; const client = clients[0];
const clientInternalId = client.id!; const clientInternalId = client.id!;
// Find existing mapper
const mappers = await kcAdminClient.clients.listProtocolMappers({ id: clientInternalId }); const mappers = await kcAdminClient.clients.listProtocolMappers({ id: clientInternalId });
const existingMapper = mappers.find((mapper) => mapper.name === mapperName); 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( await kcAdminClient.clients.addProtocolMapper(
{ id: clientInternalId }, { id: clientInternalId },
{ {
@@ -74,13 +70,15 @@ async function main() {
"access.token.claim": "true", "access.token.claim": "true",
"claim.name": claimName, "claim.name": claimName,
"jsonType.label": "String", "jsonType.label": "String",
"multivalued": "true", multivalued: "true",
"usermodel.clientRoleMapping.clientId": clientId, "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(` Claim name: ${claimName}`);
console.log(` User Info: enabled`); console.log(` User Info: enabled`);
console.log(` Access Token: enabled`); console.log(` Access Token: enabled`);

View File

@@ -4,7 +4,6 @@ import KcAdminClient from "@keycloak/keycloak-admin-client";
import invariant from "tiny-invariant"; import invariant from "tiny-invariant";
const main = async () => { const main = async () => {
// Environment variables
const keycloakHost = process.env.KEYCLOAK_HOST; const keycloakHost = process.env.KEYCLOAK_HOST;
const adminUser = process.env.KEYCLOAK_ADMIN_USER; const adminUser = process.env.KEYCLOAK_ADMIN_USER;
const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD; const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD;
@@ -18,17 +17,19 @@ const main = async () => {
invariant(realm, "KEYCLOAK_REALM is required"); invariant(realm, "KEYCLOAK_REALM is required");
console.log(`Updating token settings for realm: ${realm}`); console.log(`Updating token settings for realm: ${realm}`);
console.log(`Access token lifespan: ${accessTokenLifespan} seconds (${accessTokenLifespan/60} minutes)`); console.log(
console.log(`Refresh token lifespan: ${refreshTokenLifespan} seconds (${refreshTokenLifespan/60} minutes)`); `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({ const kcAdminClient = new KcAdminClient({
baseUrl: `https://${keycloakHost}`, baseUrl: `https://${keycloakHost}`,
realmName: "master", realmName: "master",
}); });
try { try {
// Authenticate
await kcAdminClient.auth({ await kcAdminClient.auth({
username: adminUser, username: adminUser,
password: adminPassword, password: adminPassword,
@@ -38,10 +39,8 @@ const main = async () => {
console.log("✓ Authenticated with Keycloak admin"); console.log("✓ Authenticated with Keycloak admin");
// Set the target realm
kcAdminClient.setConfig({ realmName: realm }); kcAdminClient.setConfig({ realmName: realm });
// Get current realm settings
const currentRealm = await kcAdminClient.realms.findOne({ realm }); const currentRealm = await kcAdminClient.realms.findOne({ realm });
if (!currentRealm) { if (!currentRealm) {
throw new Error(`Realm ${realm} not found`); throw new Error(`Realm ${realm} not found`);
@@ -52,7 +51,6 @@ const main = async () => {
console.log(` - Refresh token lifespan: ${currentRealm.ssoSessionMaxLifespan} seconds`); console.log(` - Refresh token lifespan: ${currentRealm.ssoSessionMaxLifespan} seconds`);
console.log(` - SSO session idle: ${currentRealm.ssoSessionIdleTimeout} seconds`); console.log(` - SSO session idle: ${currentRealm.ssoSessionIdleTimeout} seconds`);
// Update realm settings
await kcAdminClient.realms.update( await kcAdminClient.realms.update(
{ realm }, { realm },
{ {
@@ -75,13 +73,11 @@ const main = async () => {
console.log("✓ Realm token settings updated successfully"); console.log("✓ Realm token settings updated successfully");
// Verify the changes
const updatedRealm = await kcAdminClient.realms.findOne({ realm }); const updatedRealm = await kcAdminClient.realms.findOne({ realm });
console.log(`Updated settings:`); console.log(`Updated settings:`);
console.log(` - Access token lifespan: ${updatedRealm?.accessTokenLifespan} seconds`); console.log(` - Access token lifespan: ${updatedRealm?.accessTokenLifespan} seconds`);
console.log(` - Refresh token lifespan: ${updatedRealm?.ssoSessionMaxLifespan} seconds`); console.log(` - Refresh token lifespan: ${updatedRealm?.ssoSessionMaxLifespan} seconds`);
console.log(` - SSO session idle: ${updatedRealm?.ssoSessionIdleTimeout} seconds`); console.log(` - SSO session idle: ${updatedRealm?.ssoSessionIdleTimeout} seconds`);
} catch (error) { } catch (error) {
console.error("✗ Failed to update realm token settings:", error); console.error("✗ Failed to update realm token settings:", error);
process.exit(1); process.exit(1);