Stopped leaking secrets #168

Merged
khwezi merged 1 commits from finishline into main 2026-10-24 15:44:08 +02:00
10 changed files with 18 additions and 36 deletions
@@ -12,7 +12,6 @@ APP_URL="MY_APP_URL"
VITE_OIDC_AUTHORITY="https://sts.security.khongisa.co.za" VITE_OIDC_AUTHORITY="https://sts.security.khongisa.co.za"
VITE_OIDC_AUDIENCE="pfm-api-dev" VITE_OIDC_AUDIENCE="pfm-api-dev"
VITE_OIDC_CLIENT_ID="pfm-scalar-dev" VITE_OIDC_CLIENT_ID="pfm-scalar-dev"
VITE_OIDC_CLIENT_SECRET="secret_71f88570a46eb22eb5270b47f69e3332c84d8db85d008f0276cb7431c8872d27"
# PFM Backend API Base URL # PFM Backend API Base URL
VITE_API_BASE_URL="https://api.ifri.khongisa.co.za/" VITE_API_BASE_URL="https://api.ifri.khongisa.co.za/"
@@ -26,13 +26,10 @@ OIDC_AUDIENCE="${VITE_OIDC_AUDIENCE:-${OIDC_AUDIENCE:-${SecuritySettings__Audien
# 4. OIDC Client ID # 4. OIDC Client ID
OIDC_CLIENT_ID="${VITE_OIDC_CLIENT_ID:-${OIDC_CLIENT_ID:-${SecuritySettings__ClientId:-${SecuritySettings_ClientId:-${CLIENT_ID:-}}}}}" OIDC_CLIENT_ID="${VITE_OIDC_CLIENT_ID:-${OIDC_CLIENT_ID:-${SecuritySettings__ClientId:-${SecuritySettings_ClientId:-${CLIENT_ID:-}}}}}"
# 5. OIDC Client Secret # 5. OIDC Scope
OIDC_CLIENT_SECRET="${VITE_OIDC_CLIENT_SECRET:-${OIDC_CLIENT_SECRET:-${SecuritySettings__ClientSecret:-${SecuritySettings_ClientSecret:-${CLIENT_SECRET:-}}}}}"
# 6. OIDC Scope
OIDC_SCOPE="${VITE_OIDC_SCOPE:-${OIDC_SCOPE:-${SecuritySettings__Scope:-${SecuritySettings_Scope:-${SCOPE:-}}}}}" OIDC_SCOPE="${VITE_OIDC_SCOPE:-${OIDC_SCOPE:-${SecuritySettings__Scope:-${SecuritySettings_Scope:-${SCOPE:-}}}}}"
# 7. Hasher Settings # 6. Hasher Settings
HASHER_SALT="${VITE_HASHER_SALT:-${HASHER_SALT:-${HasherSettings__Salt:-${HasherSettings_Salt:-}}}}}" HASHER_SALT="${VITE_HASHER_SALT:-${HASHER_SALT:-${HasherSettings__Salt:-${HasherSettings_Salt:-}}}}}"
HASHER_MIN_LENGTH="${VITE_HASHER_MIN_LENGTH:-${HASHER_MIN_LENGTH:-${HasherSettings__MinLength:-${HasherSettings_MinLength:-}}}}}" HASHER_MIN_LENGTH="${VITE_HASHER_MIN_LENGTH:-${HASHER_MIN_LENGTH:-${HasherSettings__MinLength:-${HasherSettings_MinLength:-}}}}}"
@@ -40,8 +37,10 @@ if [ -d "$TARGET_DIR" ]; then
echo "[docker-entrypoint] Generating runtime configuration files in $TARGET_DIR..." echo "[docker-entrypoint] Generating runtime configuration files in $TARGET_DIR..."
# Generate env-config.js for instant, synchronous window.__RUNTIME_CONFIG__ initialization # Generate env-config.js for instant, synchronous window.__RUNTIME_CONFIG__ initialization
# Public SPA clients use Authorization Code flow with PKCE; client secrets must NEVER be injected or exposed here.
cat <<EOF > "$TARGET_DIR/env-config.js" cat <<EOF > "$TARGET_DIR/env-config.js"
// Generated dynamically at container startup from deployment environment variables // Generated dynamically at container startup from deployment environment variables
// Note: Secrets such as client secrets or private keys must NEVER be exposed here.
window.__RUNTIME_CONFIG__ = { window.__RUNTIME_CONFIG__ = {
API_BASE_URL: "${API_BASE_URL}", API_BASE_URL: "${API_BASE_URL}",
VITE_API_BASE_URL: "${API_BASE_URL}", VITE_API_BASE_URL: "${API_BASE_URL}",
@@ -51,8 +50,6 @@ window.__RUNTIME_CONFIG__ = {
VITE_OIDC_AUDIENCE: "${OIDC_AUDIENCE}", VITE_OIDC_AUDIENCE: "${OIDC_AUDIENCE}",
OIDC_CLIENT_ID: "${OIDC_CLIENT_ID}", OIDC_CLIENT_ID: "${OIDC_CLIENT_ID}",
VITE_OIDC_CLIENT_ID: "${OIDC_CLIENT_ID}", VITE_OIDC_CLIENT_ID: "${OIDC_CLIENT_ID}",
OIDC_CLIENT_SECRET: "${OIDC_CLIENT_SECRET}",
VITE_OIDC_CLIENT_SECRET: "${OIDC_CLIENT_SECRET}",
OIDC_SCOPE: "${OIDC_SCOPE}", OIDC_SCOPE: "${OIDC_SCOPE}",
VITE_OIDC_SCOPE: "${OIDC_SCOPE}", VITE_OIDC_SCOPE: "${OIDC_SCOPE}",
HASHER_SALT: "${HASHER_SALT}", HASHER_SALT: "${HASHER_SALT}",
@@ -66,7 +63,6 @@ window.__RUNTIME_CONFIG__ = {
Authority: "${OIDC_AUTHORITY}", Authority: "${OIDC_AUTHORITY}",
Audience: "${OIDC_AUDIENCE}", Audience: "${OIDC_AUDIENCE}",
ClientId: "${OIDC_CLIENT_ID}", ClientId: "${OIDC_CLIENT_ID}",
ClientSecret: "${OIDC_CLIENT_SECRET}",
Scope: "${OIDC_SCOPE}" Scope: "${OIDC_SCOPE}"
}, },
HasherSettings: { HasherSettings: {
@@ -86,7 +82,6 @@ EOF
"Authority": "${OIDC_AUTHORITY}", "Authority": "${OIDC_AUTHORITY}",
"Audience": "${OIDC_AUDIENCE}", "Audience": "${OIDC_AUDIENCE}",
"ClientId": "${OIDC_CLIENT_ID}", "ClientId": "${OIDC_CLIENT_ID}",
"ClientSecret": "${OIDC_CLIENT_SECRET}",
"Scope": "${OIDC_SCOPE}" "Scope": "${OIDC_SCOPE}"
}, },
"HasherSettings": { "HasherSettings": {
@@ -6,7 +6,6 @@
"Authority": "", "Authority": "",
"Audience": "", "Audience": "",
"ClientId": "", "ClientId": "",
"ClientSecret": "",
"Scope": "" "Scope": ""
}, },
"HasherSettings": { "HasherSettings": {
@@ -320,7 +320,7 @@ export function getUserManager(): UserManager {
userManagerInstance = new UserManager({ userManagerInstance = new UserManager({
authority: sec.Authority, authority: sec.Authority,
client_id: sec.ClientId, client_id: sec.ClientId,
client_secret: sec.ClientSecret, ...(sec.ClientSecret ? { client_secret: sec.ClientSecret } : {}),
redirect_uri: `${origin}/dashboard`, redirect_uri: `${origin}/dashboard`,
post_logout_redirect_uri: `${origin}/`, post_logout_redirect_uri: `${origin}/`,
response_type: 'code', response_type: 'code',
+4 -6
View File
@@ -4,7 +4,7 @@ export interface SecuritySettings {
Authority: string; Authority: string;
Audience: string; Audience: string;
ClientId: string; ClientId: string;
ClientSecret: string; ClientSecret?: string;
Scope?: string; Scope?: string;
} }
@@ -36,7 +36,6 @@ export const defaultSecuritySettings: SecuritySettings = {
Authority: 'https://sts.security.khongisa.co.za', Authority: 'https://sts.security.khongisa.co.za',
Audience: 'pfm-api-dev', Audience: 'pfm-api-dev',
ClientId: 'pfm-console-dev', ClientId: 'pfm-console-dev',
ClientSecret: 'secret_dcaeaa28097d9ba5b06ec324d9eac9d9c81de96e0a203cf2c6154c6dd7c89e94',
Scope: 'openid profile roles email address pfm-api-dev', Scope: 'openid profile roles email address pfm-api-dev',
}; };
@@ -255,9 +254,8 @@ function resolveConfig(fetchedJson?: Record<string, any>): AppConfig {
const clientSecret = pickFirstNonEmpty( const clientSecret = pickFirstNonEmpty(
windowConfig.clientSecret, windowConfig.clientSecret,
fetchedConfig.clientSecret, fetchedConfig.clientSecret,
buildEnvConfig.clientSecret, buildEnvConfig.clientSecret
defaultSecuritySettings.ClientSecret );
)!;
const scope = pickFirstNonEmpty( const scope = pickFirstNonEmpty(
windowConfig.scope, windowConfig.scope,
@@ -291,7 +289,7 @@ function resolveConfig(fetchedJson?: Record<string, any>): AppConfig {
Authority: normalizeAuthorityUrl(authority), Authority: normalizeAuthorityUrl(authority),
Audience: audience, Audience: audience,
ClientId: clientId, ClientId: clientId,
ClientSecret: clientSecret, ...(clientSecret ? { ClientSecret: clientSecret } : {}),
Scope: scope, Scope: scope,
}, },
ApiSettings: { ApiSettings: {
-1
View File
@@ -4,7 +4,6 @@ interface ImportMetaEnv {
readonly VITE_OIDC_AUTHORITY?: string; readonly VITE_OIDC_AUTHORITY?: string;
readonly VITE_OIDC_AUDIENCE?: string; readonly VITE_OIDC_AUDIENCE?: string;
readonly VITE_OIDC_CLIENT_ID?: string; readonly VITE_OIDC_CLIENT_ID?: string;
readonly VITE_OIDC_CLIENT_SECRET?: string;
readonly VITE_API_BASE_URL?: string; readonly VITE_API_BASE_URL?: string;
} }
@@ -26,13 +26,10 @@ OIDC_AUDIENCE="${VITE_OIDC_AUDIENCE:-${OIDC_AUDIENCE:-${SecuritySettings__Audien
# 4. OIDC Client ID # 4. OIDC Client ID
OIDC_CLIENT_ID="${VITE_OIDC_CLIENT_ID:-${OIDC_CLIENT_ID:-${SecuritySettings__ClientId:-${SecuritySettings_ClientId:-${CLIENT_ID:-}}}}}" OIDC_CLIENT_ID="${VITE_OIDC_CLIENT_ID:-${OIDC_CLIENT_ID:-${SecuritySettings__ClientId:-${SecuritySettings_ClientId:-${CLIENT_ID:-}}}}}"
# 5. OIDC Client Secret # 5. OIDC Scope
OIDC_CLIENT_SECRET="${VITE_OIDC_CLIENT_SECRET:-${OIDC_CLIENT_SECRET:-${SecuritySettings__ClientSecret:-${SecuritySettings_ClientSecret:-${CLIENT_SECRET:-}}}}}"
# 6. OIDC Scope
OIDC_SCOPE="${VITE_OIDC_SCOPE:-${OIDC_SCOPE:-${SecuritySettings__Scope:-${SecuritySettings_Scope:-${SCOPE:-}}}}}" OIDC_SCOPE="${VITE_OIDC_SCOPE:-${OIDC_SCOPE:-${SecuritySettings__Scope:-${SecuritySettings_Scope:-${SCOPE:-}}}}}"
# 7. Hasher Settings # 6. Hasher Settings
HASHER_SALT="${VITE_HASHER_SALT:-${HASHER_SALT:-${HasherSettings__Salt:-${HasherSettings_Salt:-}}}}}" HASHER_SALT="${VITE_HASHER_SALT:-${HASHER_SALT:-${HasherSettings__Salt:-${HasherSettings_Salt:-}}}}}"
HASHER_MIN_LENGTH="${VITE_HASHER_MIN_LENGTH:-${HASHER_MIN_LENGTH:-${HasherSettings__MinLength:-${HasherSettings_MinLength:-}}}}}" HASHER_MIN_LENGTH="${VITE_HASHER_MIN_LENGTH:-${HASHER_MIN_LENGTH:-${HasherSettings__MinLength:-${HasherSettings_MinLength:-}}}}}"
@@ -40,8 +37,10 @@ if [ -d "$TARGET_DIR" ]; then
echo "[docker-entrypoint] Generating runtime configuration files in $TARGET_DIR..." echo "[docker-entrypoint] Generating runtime configuration files in $TARGET_DIR..."
# Generate env-config.js for instant, synchronous window.__RUNTIME_CONFIG__ initialization # Generate env-config.js for instant, synchronous window.__RUNTIME_CONFIG__ initialization
# Public SPA clients use Authorization Code flow with PKCE; client secrets must NEVER be injected or exposed here.
cat <<EOF > "$TARGET_DIR/env-config.js" cat <<EOF > "$TARGET_DIR/env-config.js"
// Generated dynamically at container startup from deployment environment variables // Generated dynamically at container startup from deployment environment variables
// Note: Secrets such as client secrets or private keys must NEVER be exposed here.
window.__RUNTIME_CONFIG__ = { window.__RUNTIME_CONFIG__ = {
API_BASE_URL: "${API_BASE_URL}", API_BASE_URL: "${API_BASE_URL}",
VITE_API_BASE_URL: "${API_BASE_URL}", VITE_API_BASE_URL: "${API_BASE_URL}",
@@ -51,8 +50,6 @@ window.__RUNTIME_CONFIG__ = {
VITE_OIDC_AUDIENCE: "${OIDC_AUDIENCE}", VITE_OIDC_AUDIENCE: "${OIDC_AUDIENCE}",
OIDC_CLIENT_ID: "${OIDC_CLIENT_ID}", OIDC_CLIENT_ID: "${OIDC_CLIENT_ID}",
VITE_OIDC_CLIENT_ID: "${OIDC_CLIENT_ID}", VITE_OIDC_CLIENT_ID: "${OIDC_CLIENT_ID}",
OIDC_CLIENT_SECRET: "${OIDC_CLIENT_SECRET}",
VITE_OIDC_CLIENT_SECRET: "${OIDC_CLIENT_SECRET}",
OIDC_SCOPE: "${OIDC_SCOPE}", OIDC_SCOPE: "${OIDC_SCOPE}",
VITE_OIDC_SCOPE: "${OIDC_SCOPE}", VITE_OIDC_SCOPE: "${OIDC_SCOPE}",
HASHER_SALT: "${HASHER_SALT}", HASHER_SALT: "${HASHER_SALT}",
@@ -66,7 +63,6 @@ window.__RUNTIME_CONFIG__ = {
Authority: "${OIDC_AUTHORITY}", Authority: "${OIDC_AUTHORITY}",
Audience: "${OIDC_AUDIENCE}", Audience: "${OIDC_AUDIENCE}",
ClientId: "${OIDC_CLIENT_ID}", ClientId: "${OIDC_CLIENT_ID}",
ClientSecret: "${OIDC_CLIENT_SECRET}",
Scope: "${OIDC_SCOPE}" Scope: "${OIDC_SCOPE}"
}, },
HasherSettings: { HasherSettings: {
@@ -86,7 +82,6 @@ EOF
"Authority": "${OIDC_AUTHORITY}", "Authority": "${OIDC_AUTHORITY}",
"Audience": "${OIDC_AUDIENCE}", "Audience": "${OIDC_AUDIENCE}",
"ClientId": "${OIDC_CLIENT_ID}", "ClientId": "${OIDC_CLIENT_ID}",
"ClientSecret": "${OIDC_CLIENT_SECRET}",
"Scope": "${OIDC_SCOPE}" "Scope": "${OIDC_SCOPE}"
}, },
"HasherSettings": { "HasherSettings": {
@@ -4,7 +4,6 @@
"Authority": "", "Authority": "",
"Audience": "", "Audience": "",
"ClientId": "", "ClientId": "",
"ClientSecret": "",
"Scope": "" "Scope": ""
}, },
"ApiSettings": { "ApiSettings": {
@@ -322,7 +322,7 @@ export function getUserManager(): UserManager {
userManagerInstance = new UserManager({ userManagerInstance = new UserManager({
authority: sec.Authority, authority: sec.Authority,
client_id: sec.ClientId, client_id: sec.ClientId,
client_secret: sec.ClientSecret, ...(sec.ClientSecret ? { client_secret: sec.ClientSecret } : {}),
redirect_uri: `${origin}/dashboard`, redirect_uri: `${origin}/dashboard`,
post_logout_redirect_uri: `${origin}/`, post_logout_redirect_uri: `${origin}/`,
response_type: 'code', response_type: 'code',
+4 -6
View File
@@ -5,7 +5,7 @@ export interface SecuritySettings {
Authority: string; Authority: string;
Audience: string; Audience: string;
ClientId: string; ClientId: string;
ClientSecret: string; ClientSecret?: string;
Scope?: string; Scope?: string;
} }
@@ -32,7 +32,6 @@ export const defaultSecuritySettings: SecuritySettings = {
Authority: 'https://sts.security.khongisa.co.za', Authority: 'https://sts.security.khongisa.co.za',
Audience: 'pfm-api-dev', Audience: 'pfm-api-dev',
ClientId: 'pfm-console-dev', ClientId: 'pfm-console-dev',
ClientSecret: 'secret_dcaeaa28097d9ba5b06ec324d9eac9d9c81de96e0a203cf2c6154c6dd7c89e94',
Scope: 'openid profile roles email address pfm-api-dev', Scope: 'openid profile roles email address pfm-api-dev',
}; };
@@ -218,9 +217,8 @@ function resolveConfig(fetchedJson?: Record<string, any>): AppConfig {
const clientSecret = pickFirstNonEmpty( const clientSecret = pickFirstNonEmpty(
winCfg.clientSecret, winCfg.clientSecret,
fetchCfg.clientSecret, fetchCfg.clientSecret,
envCfg.clientSecret, envCfg.clientSecret
defaultSecuritySettings.ClientSecret );
)!;
const scope = pickFirstNonEmpty( const scope = pickFirstNonEmpty(
winCfg.scope, winCfg.scope,
fetchCfg.scope, fetchCfg.scope,
@@ -247,7 +245,7 @@ function resolveConfig(fetchedJson?: Record<string, any>): AppConfig {
Authority: authority, Authority: authority,
Audience: audience, Audience: audience,
ClientId: clientId, ClientId: clientId,
ClientSecret: clientSecret, ...(clientSecret ? { ClientSecret: clientSecret } : {}),
Scope: scope, Scope: scope,
}, },
ApiSettings: { ApiSettings: {