Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
7.5 KiB
7.5 KiB
PRD — MSP Domain-Scoped SSO Discovery
- Slug:
2026-02-24-msp-domain-scoped-sso-discovery - Date:
2026-02-24 - Status: Draft
Summary
Add domain-based tenant discovery to MSP login so SSO provider options are filtered by tenant configuration without pre-auth user lookup.
Behavior target:
- User types email on MSP login.
- System derives email domain and resolves tenant context by domain mapping (not by user existence).
- UI enables only providers configured for that tenant domain.
- If no tenant domain mapping is found, UI falls back to environment-wide provider availability.
- Existing
/auth/msp/signinURLs and existing email links remain valid; no hostname migration is required.
Problem
Current MSP SSO behavior has two issues:
- Public login cannot safely use per-user provider eligibility because that creates user-enumeration risk.
- UI currently enables both Google and Microsoft once email is non-empty, even when a tenant only supports one provider.
We need provider filtering that is tenant-aware, but still safe for a public unauthenticated surface.
Goals
- Filter MSP SSO options by tenant/provider configuration using domain-level discovery, not user-level discovery.
- Preserve anti-enumeration posture for user existence.
- Keep current hosted login paths working (no breaking link migration).
- Keep CE and EE behavior aligned for MSP login.
Non-goals
- Introducing required custom hosted login domains (
<tenant>.algapsa.com) in this phase. - Changing client portal login behavior.
- Reworking OAuth account-linking model or bulk SSO assignment flows.
- Building new analytics/monitoring systems beyond existing logs.
Users and Primary Flows
Personas
- MSP internal user signing into
/auth/msp/signin. - Tenant admin configuring provider settings and tenant login domains.
Primary Flow A — Known Domain, Tenant-Scoped Provider
- User enters
user@acme.com. - Discovery resolves
acme.com-> tenant. - Tenant provider readiness indicates Microsoft only.
- UI enables
Sign in with Microsoftand keeps Google disabled. - Resolver/start continues OAuth using tenant credentials.
Primary Flow B — Known Domain, Multiple Tenant Providers
- User enters
user@example.com. - Discovery resolves domain to tenant.
- Tenant has both Google and Microsoft configured.
- UI enables both providers.
Primary Flow C — Unknown Domain (or unresolved mapping)
- User enters email with unmapped domain.
- Discovery returns app-fallback provider availability only.
- UI reflects app-level providers (if configured) with no user lookup.
Primary Flow D — Credentials Login
- User signs in with email/password.
- Existing credentials path remains unchanged.
UX / UI Notes
- MSP login keeps existing layout and credential form.
- SSO buttons remain disabled until a syntactically valid email is present.
- After discovery, only allowed providers are enabled.
- Unknown/unmapped domain and known domain with no provider should remain neutral in messaging.
- Optionally remember last chosen SSO provider locally for convenience; this must not bypass server eligibility checks.
Requirements
Functional Requirements
- Add tenant login-domain mapping storage that supports many domains per tenant and domain normalization.
- Add provider settings UI/actions to manage tenant login domains.
- Add MSP SSO discovery endpoint that accepts email, derives domain, and returns allowed provider IDs.
- Discovery must use domain->tenant mapping only and must not query by full email for user existence decisions.
- Discovery provider resolution rules:
- If tenant resolved: allowed providers = tenant providers that are configured.
- If tenant unresolved: allowed providers = app-fallback providers configured via
*_OAUTH_*secrets/env.
- Add signed, short-lived discovery context cookie for resolver use (tenant/source/provider set metadata only; no raw secrets).
- Update MSP SSO button component to call discovery and enable provider buttons from discovery response.
- Update resolver/start endpoint to honor discovery context and reject provider attempts not in resolved allowed set.
- Keep resolver/start external failure behavior generic and non-enumerating for user existence.
- Keep OAuth callback/user mapping behavior unchanged: unknown users still fail at auth mapping stage without exposing explicit existence details.
- Keep current
/auth/msp/signinroute and existing deep links/email links unchanged. - Ensure CE and EE share the same domain discovery and provider gating behavior for MSP login.
Non-functional Requirements
- Anti-enumeration: no pre-auth UI or API behavior may vary based on whether a specific user exists.
- Discovery endpoint must be rate-limited.
- Logs must avoid raw email; use domain or hashed identifiers where needed.
- Discovery and resolver cookies must be signed, short-lived, httpOnly, and sameSite-lax.
Data / API / Integrations
Data model
Add a tenant-scoped domain mapping model (table or equivalent persistent store) with:
tenantdomain(normalized lowercase)is_active- audit timestamps/actor metadata
Required query behavior:
- Resolve tenant by domain quickly.
- Detect ambiguous mappings; ambiguous mappings must be treated as unresolved for discovery.
Endpoint
POST /api/auth/msp/sso/discover
Request:
{
"email": "user@example.com"
}
Response:
{
"ok": true,
"providers": ["google", "azure-ad"]
}
Notes:
- Response shape remains invariant.
providersmay be empty.- No user-existence information is returned.
Existing endpoint updates
POST /api/auth/msp/sso/resolve
- Consume discovery context cookie.
- Enforce requested provider is currently eligible for resolved source.
- Keep generic failure schema and anti-enumeration behavior.
Security / Permissions
- Only authorized internal admins can edit tenant login-domain mappings in settings.
- Discovery endpoint is public but rate-limited.
- Domain mapping conflicts/ambiguity must fail closed (no tenant context returned).
- No cookie or response may contain client secrets.
- Unknown-user handling remains non-reactive to avoid user enumeration leaks.
Observability
In-scope:
- Structured logs for discovery source (
tenantvsapp) and provider set size.
Out-of-scope:
- New dashboards/metrics infrastructure.
Rollout / Migration
- Add data migration for tenant login-domain mapping storage.
- Backfill optional starter domain from tenant primary email domain only when unambiguous.
- Keep legacy login URLs and existing email links unchanged.
- Roll out discovery gating without requiring hostname/custom-domain cutover.
Open Questions
- Should duplicate domain claims across tenants be blocked at write-time or allowed and treated as unresolved at read-time?
- Should fallback to app-level providers be enabled for unresolved domains in production by default, or behind a config switch?
- Should “remember provider” be local-storage only or signed cookie-based?
Acceptance Criteria (Definition of Done)
- Tenant admins can manage tenant login domains in Providers settings.
- MSP login enables only tenant-configured providers for known tenant domains.
- MSP login falls back to app-level providers for unresolved domains.
- No pre-auth user existence signal is exposed via UI/API behavior.
- Resolver enforces discovered provider eligibility and preserves generic failures.
- Existing login/deep links continue to work without hostname migration.
- CE and EE MSP login SSO behavior is consistent for domain discovery and provider gating.