Skip to content

Enterprise SSO: first-class environment-variable configuration (beyond SSO_DEV_SEED) #6

Description

@yokoszn

Module

@open-mercato/enterprisesso 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions