fix(auth): resolve IAM client ID from two-axis matrix (iam env × agos env)
All checks were successful
Stuffle/agos-client/pipeline/head This commit looks good

The flat _IAM_CONFIGS map conflated "which IAM server" with "which agOS
deployment", making it impossible to represent all 3 valid scenarios:
  - local agOS + local IAM
  - local agOS + prod IAM
  - prod agOS  + prod IAM

Split into _IAM_ISSUERS (issuer by IAM env) and _IAM_CLIENT_IDS (2D
matrix keyed by [iamEnv][agosEnv]). agOS env is auto-detected from
import.meta.env.MODE so no new env vars are needed.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
2026-08-13 01:55:03 +05:30
parent efbcfaaee2
commit 41b075dc3c
2 changed files with 49 additions and 20 deletions

View File

@@ -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.

View File

@@ -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<string, Record<string, string>> = {
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,