diff --git a/keycloak/justfile b/keycloak/justfile index d2280b8..01fe933 100644 --- a/keycloak/justfile +++ b/keycloak/justfile @@ -576,22 +576,29 @@ show-realm-token-settings realm: export KEYCLOAK_REALM={{ realm }} dotenvx run -q -f ../.env.local -- tsx ./scripts/show-realm-token-settings.ts -# Update realm token settings (access token lifespan, refresh token lifespan, etc.) -update-realm-token-settings realm access_token_lifespan='' refresh_token_lifespan='': +# Update realm token settings (access token lifespan, SSO session settings) +update-realm-token-settings realm access_token_lifespan='' sso_session_idle_timeout='' sso_session_max_lifespan='': #!/bin/bash set -euo pipefail export ACCESS_TOKEN_LIFESPAN={{ access_token_lifespan }} - export REFRESH_TOKEN_LIFESPAN={{ refresh_token_lifespan }} + export SSO_SESSION_IDLE_TIMEOUT={{ sso_session_idle_timeout }} + export SSO_SESSION_MAX_LIFESPAN={{ sso_session_max_lifespan }} while [ -z "${ACCESS_TOKEN_LIFESPAN}" ]; do ACCESS_TOKEN_LIFESPAN=$( - gum input --prompt="Access token lifespan (in seconds): " --width=100 \ + gum input --prompt="Access token lifespan (seconds): " --width=100 \ --placeholder="e.g., 43200 for 12 hours" --value="43200" ) done - while [ -z "${REFRESH_TOKEN_LIFESPAN}" ]; do - REFRESH_TOKEN_LIFESPAN=$( - gum input --prompt="Refresh token lifespan (in seconds): " --width=100 \ - --placeholder="e.g., 86400 for 24 hours" --value="86400" + while [ -z "${SSO_SESSION_IDLE_TIMEOUT}" ]; do + SSO_SESSION_IDLE_TIMEOUT=$( + gum input --prompt="SSO session idle timeout (seconds): " --width=100 \ + --placeholder="e.g., 86400 for 1 day" --value="86400" + ) + done + while [ -z "${SSO_SESSION_MAX_LIFESPAN}" ]; do + SSO_SESSION_MAX_LIFESPAN=$( + gum input --prompt="SSO session max lifespan (seconds): " --width=100 \ + --placeholder="e.g., 604800 for 7 days" --value="604800" ) done export KEYCLOAK_ADMIN_USER=$(just admin-username) diff --git a/keycloak/scripts/create-realm.ts b/keycloak/scripts/create-realm.ts index 8f6de83..8cf4fc2 100644 --- a/keycloak/scripts/create-realm.ts +++ b/keycloak/scripts/create-realm.ts @@ -14,13 +14,10 @@ const main = async () => { const realmName = process.env.KEYCLOAK_REALM; invariant(realmName, "KEYCLOAK_REALM environment variable is required"); - // 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 ssoSessionIdleTimeout = parseInt(process.env.SSO_SESSION_IDLE_TIMEOUT || "7200"); // 2 hours + // Token lifespan settings (with defaults suitable for development/personal use) + const accessTokenLifespan = parseInt(process.env.ACCESS_TOKEN_LIFESPAN || "43200"); // 12 hours + const ssoSessionIdleTimeout = parseInt(process.env.SSO_SESSION_IDLE_TIMEOUT || "86400"); // 1 day + const ssoSessionMaxLifespan = parseInt(process.env.SSO_SESSION_MAX_LIFESPAN || "604800"); // 7 days const kcAdminClient = new KcAdminClient({ baseUrl: `https://${keycloakHost}`, @@ -49,29 +46,27 @@ const main = async () => { // Token lifespan settings accessTokenLifespan: accessTokenLifespan, accessTokenLifespanForImplicitFlow: accessTokenLifespan, + // SSO session settings ssoSessionMaxLifespan: ssoSessionMaxLifespan, - ssoSessionIdleTimeout: Math.min(ssoSessionMaxLifespan, ssoSessionIdleTimeout), + ssoSessionIdleTimeout: ssoSessionIdleTimeout, // Refresh token settings refreshTokenMaxReuse: 0, // Offline session settings offlineSessionMaxLifespan: ssoSessionMaxLifespan * 2, offlineSessionMaxLifespanEnabled: true, - // Client session settings - clientSessionMaxLifespan: accessTokenLifespan, - clientSessionIdleTimeout: Math.min(accessTokenLifespan, ssoSessionIdleTimeout), + // Client session settings (inherit from SSO session) + clientSessionMaxLifespan: ssoSessionMaxLifespan, + clientSessionIdleTimeout: ssoSessionIdleTimeout, }); console.log(`Realm '${realmName}' created successfully with token settings:`); console.log( - ` - Access Token Lifespan: ${accessTokenLifespan} seconds (${accessTokenLifespan / 60} minutes)` + ` - Access Token Lifespan: ${accessTokenLifespan}s (${accessTokenLifespan / 3600}h)` ); console.log( - ` - Refresh Token Lifespan: ${refreshTokenLifespan} seconds (${refreshTokenLifespan / 60} minutes)` + ` - SSO Session Idle Timeout: ${ssoSessionIdleTimeout}s (${ssoSessionIdleTimeout / 3600}h)` ); console.log( - ` - SSO Session Max: ${ssoSessionMaxLifespan} seconds (${ssoSessionMaxLifespan / 60} minutes)` - ); - console.log( - ` - SSO Session Idle: ${ssoSessionIdleTimeout} seconds (${ssoSessionIdleTimeout / 60} minutes)` + ` - SSO Session Max Lifespan: ${ssoSessionMaxLifespan}s (${ssoSessionMaxLifespan / 86400}d)` ); } catch (error) { console.error("An error occurred:", error); diff --git a/keycloak/scripts/update-realm-token-settings.ts b/keycloak/scripts/update-realm-token-settings.ts index c52dde1..7d0a00e 100644 --- a/keycloak/scripts/update-realm-token-settings.ts +++ b/keycloak/scripts/update-realm-token-settings.ts @@ -3,13 +3,26 @@ import KcAdminClient from "@keycloak/keycloak-admin-client"; import invariant from "tiny-invariant"; +const formatDuration = (seconds: number): string => { + if (seconds >= 86400) { + return `${seconds}s (${seconds / 86400}d)`; + } else if (seconds >= 3600) { + return `${seconds}s (${seconds / 3600}h)`; + } else { + return `${seconds}s (${seconds / 60}m)`; + } +}; + const main = async () => { 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"); + + // Token settings with defaults suitable for development/personal use + const accessTokenLifespan = parseInt(process.env.ACCESS_TOKEN_LIFESPAN || "43200"); // 12 hours + const ssoSessionIdleTimeout = parseInt(process.env.SSO_SESSION_IDLE_TIMEOUT || "86400"); // 1 day + const ssoSessionMaxLifespan = parseInt(process.env.SSO_SESSION_MAX_LIFESPAN || "604800"); // 7 days invariant(keycloakHost, "KEYCLOAK_HOST is required"); invariant(adminUser, "KEYCLOAK_ADMIN_USER is required"); @@ -17,12 +30,9 @@ const main = async () => { 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(` Access Token Lifespan: ${formatDuration(accessTokenLifespan)}`); + console.log(` SSO Session Idle Timeout: ${formatDuration(ssoSessionIdleTimeout)}`); + console.log(` SSO Session Max Lifespan: ${formatDuration(ssoSessionMaxLifespan)}`); const kcAdminClient = new KcAdminClient({ baseUrl: `https://${keycloakHost}`, @@ -47,9 +57,9 @@ const main = async () => { } 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`); + console.log(` - Access Token Lifespan: ${formatDuration(currentRealm.accessTokenLifespan || 0)}`); + console.log(` - SSO Session Idle Timeout: ${formatDuration(currentRealm.ssoSessionIdleTimeout || 0)}`); + console.log(` - SSO Session Max Lifespan: ${formatDuration(currentRealm.ssoSessionMaxLifespan || 0)}`); await kcAdminClient.realms.update( { realm }, @@ -58,16 +68,17 @@ const main = async () => { // Access token settings accessTokenLifespan: accessTokenLifespan, accessTokenLifespanForImplicitFlow: accessTokenLifespan, + // SSO session settings + ssoSessionIdleTimeout: ssoSessionIdleTimeout, + ssoSessionMaxLifespan: ssoSessionMaxLifespan, // Refresh token settings refreshTokenMaxReuse: 0, - ssoSessionMaxLifespan: refreshTokenLifespan, - ssoSessionIdleTimeout: Math.min(refreshTokenLifespan, 1800), // Max 30 minutes idle - // Other token settings - offlineSessionMaxLifespan: refreshTokenLifespan * 2, + // Offline session settings + offlineSessionMaxLifespan: ssoSessionMaxLifespan * 2, offlineSessionMaxLifespanEnabled: true, - // Client session settings - clientSessionMaxLifespan: accessTokenLifespan, - clientSessionIdleTimeout: Math.min(accessTokenLifespan, 1800), + // Client session settings (inherit from SSO session) + clientSessionMaxLifespan: ssoSessionMaxLifespan, + clientSessionIdleTimeout: ssoSessionIdleTimeout, } ); @@ -75,9 +86,9 @@ const main = async () => { 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`); + console.log(` - Access Token Lifespan: ${formatDuration(updatedRealm?.accessTokenLifespan || 0)}`); + console.log(` - SSO Session Idle Timeout: ${formatDuration(updatedRealm?.ssoSessionIdleTimeout || 0)}`); + console.log(` - SSO Session Max Lifespan: ${formatDuration(updatedRealm?.ssoSessionMaxLifespan || 0)}`); } catch (error) { console.error("✗ Failed to update realm token settings:", error); process.exit(1);