diff --git a/apps/opencase/src/infrastructure/config/Config.ts b/apps/opencase/src/infrastructure/config/Config.ts index d705041..c97dba5 100644 --- a/apps/opencase/src/infrastructure/config/Config.ts +++ b/apps/opencase/src/infrastructure/config/Config.ts @@ -39,6 +39,9 @@ export interface AppConfig { smtpHost?: string; smtpPort?: string; smtpFrom?: string; + + // Keycloak realm SSL enforcement ('none' for HTTP dev, 'external' for production HTTPS) + keycloakRealmSslRequired: 'none' | 'external' | 'all'; } export function loadConfig(): AppConfig { @@ -78,6 +81,8 @@ export function loadConfig(): AppConfig { smtpHost: process.env.SMTP_HOST ?? (isProduction ? undefined : 'mailpit'), smtpPort: process.env.SMTP_PORT ?? '1025', smtpFrom: process.env.SMTP_FROM ?? 'noreply@opencase.local', + + keycloakRealmSslRequired: (process.env.KEYCLOAK_SSL_REQUIRED ?? (isProduction ? 'external' : 'none')) as 'none' | 'external' | 'all', }; } diff --git a/apps/opencase/src/infrastructure/keycloak/KeycloakAdminClient.ts b/apps/opencase/src/infrastructure/keycloak/KeycloakAdminClient.ts index 1b3208a..86ac155 100644 --- a/apps/opencase/src/infrastructure/keycloak/KeycloakAdminClient.ts +++ b/apps/opencase/src/infrastructure/keycloak/KeycloakAdminClient.ts @@ -75,6 +75,11 @@ export class KeycloakAdminClient { logger.info({ realm }, 'Configured realm settings (resetPassword, loginWithEmail, SMTP, CASE scopes)') } + async setRealmSslRequired (realm: string, sslRequired: string): Promise { + await this.requestJson('PUT', `/admin/realms/${encodeURIComponent(realm)}`, { sslRequired }) + logger.info({ realm, sslRequired }, 'Set realm sslRequired') + } + async ensureClient (client: { clientId: string publicClient: boolean diff --git a/apps/opencase/src/wiring/container.ts b/apps/opencase/src/wiring/container.ts index 4d8c3e5..07804e2 100644 --- a/apps/opencase/src/wiring/container.ts +++ b/apps/opencase/src/wiring/container.ts @@ -339,6 +339,8 @@ export async function buildContainer(): Promise { try { await keycloakAdmin.ensureRealmExists() await keycloakTenantProvisioner.bootstrapSystemAdmin() + await keycloakAdmin.setRealmSslRequired(config.keycloakRealm, config.keycloakRealmSslRequired) + await keycloakAdmin.setRealmSslRequired(config.keycloakAdminRealm, config.keycloakRealmSslRequired) keycloakReady = true logger.info('Keycloak bootstrap completed successfully') } catch (error: any) { diff --git a/docker-compose.yml b/docker-compose.yml index 41ae0be..bb48948 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,7 +37,6 @@ services: # Hostname configuration - tell Keycloak its external URL - KC_HOSTNAME=${OPENCASE_HOSTNAME:-localhost} - KC_HOSTNAME_STRICT=false - - KC_HOSTNAME_STRICT_HTTPS=${KC_HOSTNAME_STRICT_HTTPS:-false} # Proxy settings for running behind Traefik - KC_PROXY_HEADERS=xforwarded - KC_HTTP_RELATIVE_PATH=/ @@ -124,6 +123,7 @@ services: - SMTP_HOST=${SMTP_HOST:-mailpit} - SMTP_PORT=${SMTP_PORT:-1025} - SMTP_FROM=noreply@${OPENCASE_HOSTNAME:-opencase.local} + - KEYCLOAK_SSL_REQUIRED=${KEYCLOAK_SSL_REQUIRED:-none} restart: on-failure volumes: - ./apps/opencase/data:/app/data diff --git a/docs/env.example b/docs/env.example index 9f5af2e..50b69ec 100644 --- a/docs/env.example +++ b/docs/env.example @@ -16,7 +16,7 @@ TRAEFIK_CONFIG=traefik/traefik-https.yml TRAEFIK_PORTS_WEB=443:443 TRAEFIK_PORTS_ALT=80:80 -KC_HOSTNAME_STRICT_HTTPS=true +KEYCLOAK_SSL_REQUIRED=external OPENCASE_SCHEME=https OPENCASE_PORT_SUFFIX= @@ -24,7 +24,7 @@ OPENCASE_PORT_SUFFIX= # TRAEFIK_CONFIG=traefik/traefik-http.yml # TRAEFIK_PORTS_WEB=3000:3000 # TRAEFIK_PORTS_ALT=8080:8080 -# KC_HOSTNAME_STRICT_HTTPS=false +# KEYCLOAK_SSL_REQUIRED=none # OPENCASE_SCHEME=http # OPENCASE_PORT_SUFFIX=:3000