Module
@open-mercato/enterprise → sso module
File: packages/enterprise/src/modules/sso/setup.ts
Summary
Today the only env-driven SSO setup is SSO_DEV_SEED + SSO_DEV_* variables. These:
- Only run when
NODE_ENV === 'development'
- Seed exactly one config for the default organization
- Cover only the bare minimum fields (
issuer, clientId, clientSecret, allowedDomains)
- Provide no default role, so every JIT-provisioned user lands with zero permissions and is greeted with
You don't have access to this feature (dashboards.view) on first login
- Cannot configure
appRoleMappings, scimEnabled, ssoRequired, or role/group → app role bindings
Anything else requires either a DB migration, backend UI clicks, or a custom subscriber (which is what we had to write in our downstream project).
Why this matters
For self-hosted, infra-as-code, or standalone deployments, the SSO config is operations-team territory — not something that should be configured by clicking through an admin UI on first deploy. Teams using Kubernetes, Terraform, Ansible, Docker Compose, or .env files need the full SSO config to be declarative.
Proposed design
1. Lift the development guard
Make the seed run in any environment when SSO_SEED=true (or equivalent). Optionally keep SSO_DEV_SEED as an alias for back-compat.
2. Support multiple configs via numeric or named suffixes
SSO_CONFIG_1_NAME=twn-systems
SSO_CONFIG_1_ORGANIZATION_SLUG=default
SSO_CONFIG_1_PROTOCOL=oidc
SSO_CONFIG_1_ISSUER=https://auth.twnstack.com/oidc
SSO_CONFIG_1_CLIENT_ID=itgcvsx6adq8sb98btz40
SSO_CONFIG_1_CLIENT_SECRET=...
SSO_CONFIG_1_ALLOWED_DOMAINS=twn.systems,twnstack.com
SSO_CONFIG_1_JIT_ENABLED=true
SSO_CONFIG_1_AUTO_LINK_BY_EMAIL=true
SSO_CONFIG_1_SSO_REQUIRED=false
SSO_CONFIG_1_DEFAULT_ROLE=admin # <— critical missing piece
SSO_CONFIG_1_ROLE_MAPPINGS=admins:admin,support:employee
SSO_CONFIG_1_LOGIN_BUTTON=hrd|always|primary # ties to issue #5
3. Default role for JIT (highest priority, even if the rest is punted)
Add a default_role column on sso_configs, or a per-tenant env var SSO_JIT_DEFAULT_ROLE. When appRoleMappings produces zero roles (either because there are none configured or because the IdP sent no matching groups), fall back to this role instead of leaving the user role-less.
Current code path that creates the problem:
// accountLinkingService.ts
async assignRolesFromSso(em, user, config, tenantId, idpGroups) {
const hasMappings = config.appRoleMappings && Object.keys(config.appRoleMappings).length > 0
if (!hasMappings) return // <— silently leaves user with no roles
...
}
This should be:
if (!hasMappings) {
await this.assignDefaultRole(em, user, config, tenantId)
return
}
4. Idempotent re-apply
On every app start, reconcile env → DB: update mutable fields (allowedDomains, appRoleMappings, isActive, etc.) of configs identified by name or issuer+clientId. Never overwrite a config that has been manually edited unless SSO_ENV_OVERRIDES=true is set.
5. Secret sourcing
SSO_CONFIG_1_CLIENT_SECRET should accept file://..., env://..., vault://... URIs so secrets don't have to sit inline in a .env file on disk.
Current workaround in our downstream app
We had to write a subscriber on sso.identity.created that assigns a default role when the user has none:
const defaultRoleName = process.env.SSO_DEFAULT_ROLE?.trim() || 'admin'
const hasAnyRole = (await em.count(UserRole, { userId: user.id, deletedAt: null })) > 0
if (!hasAnyRole) {
const role = await em.findOne(Role, { tenantId, name: defaultRoleName, deletedAt: null })
if (role) em.persist(em.create(UserRole, { userId: user.id, roleId: role.id, createdAt: new Date() }))
}
This should not be every downstream consumer's job.
Affected files (source repo)
packages/enterprise/src/modules/sso/setup.ts — expand seed logic
packages/enterprise/src/modules/sso/data/entities.ts — add defaultRole column
packages/enterprise/src/modules/sso/migrations/ — new migration
packages/enterprise/src/modules/sso/services/accountLinkingService.ts — fallback role assignment
docs/enterprise/sso.md — document the full env contract
Module
@open-mercato/enterprise→ssomoduleFile:
packages/enterprise/src/modules/sso/setup.tsSummary
Today the only env-driven SSO setup is
SSO_DEV_SEED+SSO_DEV_*variables. These:NODE_ENV === 'development'issuer,clientId,clientSecret,allowedDomains)You don't have access to this feature (dashboards.view)on first loginappRoleMappings,scimEnabled,ssoRequired, or role/group → app role bindingsAnything else requires either a DB migration, backend UI clicks, or a custom subscriber (which is what we had to write in our downstream project).
Why this matters
For self-hosted, infra-as-code, or standalone deployments, the SSO config is operations-team territory — not something that should be configured by clicking through an admin UI on first deploy. Teams using Kubernetes, Terraform, Ansible, Docker Compose, or
.envfiles need the full SSO config to be declarative.Proposed design
1. Lift the
developmentguardMake the seed run in any environment when
SSO_SEED=true(or equivalent). Optionally keepSSO_DEV_SEEDas an alias for back-compat.2. Support multiple configs via numeric or named suffixes
3. Default role for JIT (highest priority, even if the rest is punted)
Add a
default_rolecolumn onsso_configs, or a per-tenant env varSSO_JIT_DEFAULT_ROLE. WhenappRoleMappingsproduces zero roles (either because there are none configured or because the IdP sent no matching groups), fall back to this role instead of leaving the user role-less.Current code path that creates the problem:
This should be:
4. Idempotent re-apply
On every app start, reconcile env → DB: update mutable fields (
allowedDomains,appRoleMappings,isActive, etc.) of configs identified bynameorissuer+clientId. Never overwrite a config that has been manually edited unlessSSO_ENV_OVERRIDES=trueis set.5. Secret sourcing
SSO_CONFIG_1_CLIENT_SECRETshould acceptfile://...,env://...,vault://...URIs so secrets don't have to sit inline in a.envfile on disk.Current workaround in our downstream app
We had to write a subscriber on
sso.identity.createdthat assigns a default role when the user has none:This should not be every downstream consumer's job.
Affected files (source repo)
packages/enterprise/src/modules/sso/setup.ts— expand seed logicpackages/enterprise/src/modules/sso/data/entities.ts— adddefaultRolecolumnpackages/enterprise/src/modules/sso/migrations/— new migrationpackages/enterprise/src/modules/sso/services/accountLinkingService.ts— fallback role assignmentdocs/enterprise/sso.md— document the full env contract