diff --git a/.env.example b/.env.example index 420f3f1..094c788 100644 --- a/.env.example +++ b/.env.example @@ -25,14 +25,18 @@ VITE_API_URL=http://localhost:2000 # local | prod: https://agos.armco.dev # IAM is deployed at https://iam.armco.dev # Issuer claim in JWTs: stuffle-iam | Audience for AGOS API: stuffle-api # -# SPA clients registered (frontend only — no secret, PKCE): -# local IAM (localhost:5000): client_53ab7b22d1d845d5a435b21e9e0b15e1 → http://localhost:2001/callback -# remote dev IAM: client_5bb63e4ae1644c9d97ffc84a5d228c7c → http://localhost:2001/callback -# prod: client_2dbeb454ddf14203b092eb4636a8e3d7 → https://agos.stuffle.ai/callback +# Client ID selection uses TWO axes (resolved automatically in iam.ts): +# 1. VITE_IAM_PROVIDER_ENV — which IAM server ("local" | "prod", default "prod") +# 2. import.meta.env.MODE — "development" (vite dev) or "production" (vite build) # -# Set VITE_IAM_PROVIDER_ENV to select which IAM to use: -# "local" → http://localhost:5000 + local client -# "prod" → https://iam.armco.dev + prod/dev client (default when unset) +# Registered SPA clients (frontend only — no secret, PKCE): +# IAM local + agOS local → client_53ab7b22d1d845d5a435b21e9e0b15e1 (localhost:2001/callback) +# IAM prod + agOS local → client_5bb63e4ae1644c9d97ffc84a5d228c7c (localhost:2001/callback) +# IAM prod + agOS prod → client_2dbeb454ddf14203b092eb4636a8e3d7 (agos.stuffle.ai/callback) +# +# Set VITE_IAM_PROVIDER_ENV to select which IAM server to talk to: +# "local" → http://localhost:5000 (for local IAM dev) +# "prod" → https://iam.armco.dev (default when unset) VITE_IAM_PROVIDER_ENV=prod VITE_IAM_AUDIENCE=stuffle-api # Optional when IAM expects OAuth resource indicators in addition to audience. diff --git a/src/auth/iam.ts b/src/auth/iam.ts index d2e73e5..cae95e8 100644 --- a/src/auth/iam.ts +++ b/src/auth/iam.ts @@ -20,24 +20,48 @@ import { createStuffleIAMClient } from '@armco/iam-client' import { DEV_BYPASS_ACTIVE as _devBypassActive } from './dev-bypass' import { recordAuthTelemetry } from './authTelemetry' -const _IAM_CONFIGS = { - local: { - issuer: 'http://localhost:5000', - clientId: 'client_53ab7b22d1d845d5a435b21e9e0b15e1', - }, - prod: { - issuer: 'https://iam.armco.dev', - clientId: 'client_2dbeb454ddf14203b092eb4636a8e3d7', - }, +// ── IAM issuer lookup (which IAM server) ──────────────────────────────────── +const _IAM_ISSUERS = { + local: 'http://localhost:5000', + prod: 'https://iam.armco.dev', } as const +// ── Client ID matrix: [iamEnv][agosEnv] ───────────────────────────────────── +// Each IAM server has separate registered clients for "agOS running locally" +// vs "agOS running in production" (different redirect URIs). +// +// iamEnv × agosEnv → registered client +// local × local → "agOS local" in local IAM (redirect: localhost:2001) +// prod × local → "agOS local" in prod IAM (redirect: localhost:2001) +// prod × prod → "agOS" in prod IAM (redirect: agos.stuffle.ai) +// local × prod → not a real scenario (prod deploy never talks to local IAM) +const _IAM_CLIENT_IDS: Record> = { + local: { + local: 'client_53ab7b22d1d845d5a435b21e9e0b15e1', + }, + prod: { + local: 'client_5bb63e4ae1644c9d97ffc84a5d228c7c', + prod: 'client_2dbeb454ddf14203b092eb4636a8e3d7', + }, +} + +// VITE_IAM_PROVIDER_ENV selects which IAM server to talk to const _iamProviderEnv = (import.meta.env.VITE_IAM_PROVIDER_ENV as string | undefined) ?? 'prod' if (_iamProviderEnv !== 'local' && _iamProviderEnv !== 'prod') { console.warn(`[iam] Unrecognized VITE_IAM_PROVIDER_ENV="${_iamProviderEnv}". Falling back to "prod".`) } -const _iamConfig = _iamProviderEnv === 'local' ? _IAM_CONFIGS.local : _IAM_CONFIGS.prod -const _issuer = _iamConfig.issuer -const _clientId = _iamConfig.clientId +// import.meta.env.MODE is "development" during `vite dev`, "production" during `vite build` +const _agosEnv = import.meta.env.MODE === 'development' ? 'local' : 'prod' +const _resolvedIamEnv = _iamProviderEnv === 'local' ? 'local' : 'prod' +const _issuer = _IAM_ISSUERS[_resolvedIamEnv] +const _clientId = _IAM_CLIENT_IDS[_resolvedIamEnv]?.[_agosEnv] ?? '' + +if (_clientId === '') { + console.warn( + `[iam] No client ID for iamEnv="${_resolvedIamEnv}" + agosEnv="${_agosEnv}". ` + + 'This combination may not be registered. Check _IAM_CLIENT_IDS in iam.ts.', + ) +} /** Resolved IAM issuer URL for the active provider env. Use this instead of reading env vars directly. */ export const iamIssuer = _issuer @@ -77,7 +101,8 @@ const _createClient = () => { const redirectUri = _getEffectiveRedirectUri() const clientId = _getEffectiveClientId() console.info('[agos:auth] creating IAM client', { - providerEnv: _iamProviderEnv, + iamProviderEnv: _resolvedIamEnv, + agosEnv: _agosEnv, issuer: _issuer ?? '', clientId, redirectUri,