@idp.global/app
Identity infrastructure for apps that need accounts, sessions, organizations, invites, admin tooling, mobile passport approvals, security alerts, OpenID Connect, SAML SSO, and SCIM in one TypeScript codebase.
This repository ships the idp.global server, CLI, and web UI used by the hosted service. Shared public contracts live in @idp.global/interfaces; reusable browser/server SDK code lives in @idp.global/sdk.
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
What It Does
- Runs an identity provider with MongoDB-backed users, sessions, roles, organizations, invitations, OIDC clients, and billing plans.
- Serves a web app for login, registration, account management, org management, billing flows, and global admin views.
- Exposes typed realtime APIs over
typedrequestandtypedsocket. - Implements OIDC/OAuth endpoints including discovery, JWKS, authorization, token, userinfo, and revoke.
- Provides inbound SAML SSO with DNS-verified organization domains, JIT provisioning, group-to-role mapping, and SCIM 2.0 provisioning.
- Supports passport-style mobile device enrollment, signed approval challenges, push registration, security alerts, and NFC/location-backed identity proof flows.
- Includes a reusable browser client and a terminal CLI for common account and org workflows.
Monorepo Modules
| Folder | Purpose |
|---|---|
ts/ |
Backend service entrypoint and the core Reception managers |
ts_idpcli/ |
CLI published as @idp.global/cli |
ts_web/ |
Frontend bundle with login, registration, account, org, billing, and admin views |
../interfaces/ |
Shared request and data contracts published as @idp.global/interfaces |
../sdk/ |
Browser and server SDK published as @idp.global/sdk |
Core Backend Pieces
Reception wires the service together and starts these managers:
JwtManagerfor signing, refreshing, and validating JWTs.LoginSessionManagerfor login state and session lifecycle.RegistrationSessionManagerfor multi-step sign-up flows.UserManagerfor user lookups and account data.OrganizationManagerfor org creation and membership lookup.RoleManagerfor org roles and permissions.UserInvitationManagerfor invites, membership updates, and ownership transfer.BillingPlanManagerfor Paddle-backed billing data.AppManagerandAppConnectionManagerfor app connections and admin app stats.AuditManagerfor privacy-safe structured events, integrity verification, and durable append-only sink delivery.AlertManagerfor passport alerts and organization/global alert rules.AbuseProtectionManagerfor keyed, owner-fenced rate limits on login, recovery, MFA, and OIDC token exchange flows.PassportManagerandPassportPushManagerfor trusted device enrollment, challenge approval, and push notification delivery.OidcManagerfor the OIDC/OAuth provider surface.EnterpriseSsoManagerfor SAML metadata, login, assertion validation, DNS domain ownership, and JIT provisioning.ScimManagerfor org-scoped SCIM users, groups, and bearer-token management.
Quick Start
Prerequisites
- Node.js 22.22.1
- pnpm 11.15.0
- MongoDB
Install
pnpm install
Required Environment
export MONGODB_URL=mongodb://localhost:27017/idp-dev
export IDP_ENV=local
export IDP_BASEURL=http://localhost:2999
export INSTANCE_NAME=idp-dev
export SERVEZONE_PLATFORM_AUTHORIZATION=test
export IDP_TOTP_ENCRYPTION_KEY=replace-with-at-least-32-characters
Startup validates a canonical runtime config before Reception starts. IDP_ENV accepts production, development, test, or local; any other value is rejected. When IDP_ENV is unset, the app defaults to production behavior.
IDP_BASEURL is the canonical public URL for OIDC issuer metadata and generated registration, login, password-reset, welcome, and invitation links. Production requires HTTPS. Local HTTP is only accepted with explicit IDP_ENV=local, development, or test, and only for localhost-style hostnames.
SERVEZONE_PLATFORM_AUTHORIZATION is required. The literal value test switches the platform client to debug mode for local development and tests and is rejected in production.
IDP_TOTP_ENCRYPTION_KEY is required in every runtime mode and must be at least 32 characters.
Production-required backend service token:
IDP_BACKEND_TOKEN— shared secret authenticating backend services on thegetPublicKeysForValidationandpushOrGetJwtIdBlocklisttyped endpoints. It is production-required and must be at least 32 characters. In non-production it can be omitted, and those endpoints remain fail-closed.
Optional:
PADDLE_TOKENPADDLE_PRICE_ID
Build
pnpm build
Run Locally
pnpm watch
This starts the backend watcher from ts/ and rebuilds the frontend bundle from ts_web/. The packaged executable constructs the production structured-audit runtime from environment configuration and fails closed unless OpenBao custody, Lossless object-storage retention, and the serve.zone dead-letter route all qualify. Tests inject the deterministic in-memory runtime explicitly. Do not use that test runtime for shared or production deployments.
Seed Development Data
pnpm run seed
The seed command starts an interactive CLI that writes to the configured local database. The default demo workspace creates a global admin, an organization, demo users, and global OAuth app records.
This seed path is development-only. Never run it against a production database; production first launch uses the explicit bootstrap described below and creates no demo records.
Default development credentials if accepted unchanged:
- Email:
admin@idp.global - Password:
idp.global
Those credentials are for disposable local instances only. Never reuse them for production or any shared environment.
Runtime Surface
Web Routes
| Route | Purpose |
|---|---|
/ |
Welcome page |
/login |
Login flow |
/logout |
Logout flow |
/register |
Registration flow |
/finishregistration |
Multi-step registration completion |
/dash |
Signed-in account area and account subroutes |
OIDC and OAuth Endpoints
| Route | Purpose |
|---|---|
/.well-known/openid-configuration |
Discovery document |
/.well-known/jwks.json |
Public signing keys |
/oauth/authorize |
Authorization endpoint |
/oauth/token |
Token exchange |
/oauth/userinfo |
UserInfo endpoint |
/oauth/revoke |
Token revocation |
Supported scopes in the OIDC manager include openid, profile, email, organizations, and roles.
SAML SSO And SCIM
| Route | Purpose |
|---|---|
/saml/login/:connectionId |
Start an SP-initiated SAML login for a connection with a verified domain |
/saml/acs/:connectionId |
Validate the signed SAML response and complete account linking or JIT provisioning |
/saml/sp/metadata/:connectionId |
Publish service-provider metadata for customer IdPs |
/scim/v2/* |
Org-scoped SCIM 2.0 discovery, user, and group endpoints |
Organization admins configure SAML and SCIM under the SSO & Provisioning view. Every SSO email domain starts pending with a generated DNS TXT challenge. Home-realm discovery, SAML login, existing-account linking, and JIT provisioning remain disabled for that domain until the authoritative claim is verified. Domain claims are globally unique across organizations.
The implemented SCIM profile is deliberately bounded:
| Capability | Declared support |
|---|---|
| Authentication and tenancy | Organization-scoped bearer tokens; every resource lookup and mutation is fenced to the token organization |
| Discovery | ServiceProviderConfig, ResourceTypes, and Schemas |
| Users | Create, retrieve, list, replace, patch, and deprovision; deactivated users remain retrievable |
| Groups | Create, retrieve, list, replace, patch membership, and delete |
| Filters | Equality only: users accept userName, emails.value, externalId, and id; groups accept displayName, externalId, and id |
| Pagination | One-based, default 100, maximum 200, stable database ordering; count=0 returns counts without resources |
| Versioning | Weak ETags in the HTTP ETag header and resource meta.version; single-resource GET supports If-None-Match/304, and PUT, PATCH, and DELETE accept optional If-Match/412 preconditions |
| PATCH compatibility | Case-insensitive operations, Entra boolean strings and no-path dotted user attributes, plus Okta/Entra group-member add, replace, and value-path removal |
| Conflicts | Per-organization user name/email, group display name, and external-ID uniqueness return SCIM 409 errors |
| Not supported | Bulk, sorting, password change, POST search, arbitrary filter grammar, or undeclared PATCH behavior |
Lists use explicitly hinted compound indexes that support their stable sort,
database pagination, and batched user/member projection reads. Real-Mongo
coverage seeds 320 users, groups, memberships, and tokens per tenant; first-page
document reads are bounded by the requested page size, member expansion uses
one link batch plus one user batch, token hashes resolve through one indexed
document, and organization token reads remain tenant-scoped. Production scale
and interoperability qualification still require the exact pinned Corestore
digest plus the Okta and Entra evidence in readme.plan.md.
SCIM bearer-token records use a globally unique token-hash authority, exact revisioned inserts and revocation CAS, monotonic no-upsert usage telemetry, and exact organization-lifecycle deletion. Plaintext remains one-time output only.
Privileged administration uses declared default-deny capabilities for global OIDC clients, organizations, alert rules, users, global-administrator lifecycle, and signing-key rotation, plus tenant-scoped organizations, roles, memberships, app connections, SSO, SCIM tokens, billing, and alert rules. Entry authorization requires an active persisted JWT/session/user chain and, for tenant access, exactly one current role in the target organization. Existing mutation fences recheck live actors for OIDC clients, organization suspension, global user suspension and administrative purge initiation, global-administrator lifecycle, signing-key rotation, and tenant mutations. Global-administrator grant and removal additionally require an exact reviewed user revision, caller-stable operation ID, distinct actor and target, fresh user-verified passkey, one shared last-active-administrator fence, and purpose-bound attempted plus terminal audit events. Administrative user purge, organization suspension, and signing-key rotation use the same two-minute passkey class and reassert the persisted session inside their mutation fences. Tenant invitation creation, cancellation, resend, and bulk creation plus member removal, member-role replacement, and ownership transfer use the five-minute passkey class and reassert both the same persisted session and live tenant capability inside their mutation fences.
Direct platform capability assignments are limited to the declared delegable subset and active non-administrators. Changes require an exact User revision, caller-stable operation ID, distinct actor and target, a passkey authenticated within two minutes, mutation-time reauthorization, and attempted plus terminal audit. Promotion and suspension clear direct assignments. Custom organization roles carry sorted, unique delegable tenant capabilities; malformed definitions fail closed, and organization deletion or ownership transfer cannot be delegated.
Emergency platform access is a separate break-glass lifecycle. An active
non-administrator with the direct global.break-glass.request capability can
request only global.oidc-client.manage, global.organization.manage,
global.alert.manage, or global.user.manage for 60 through 1800 seconds.
Requests use an exact User revision, caller-stable operation ID, a closed
justification code, and a passkey authenticated within two minutes. A distinct
live global administrator must approve within ten minutes. The grant records
custody-protected approver identity, expires automatically, records its first
use, emits structured audit and high/critical global alerts, and must be closed
with a durable confirmed or escalated post-use review. It never grants global
administrator management, account deletion, signing-key rotation, or approval
authority. Promotion to global administrator is blocked while a request or
grant remains open, and review immediately terminates an active grant.
Custom-role definition changes use that same tenant-privilege class. App connection and role-mapping changes, SSO connection and domain-verification changes, and SCIM bearer-token creation or revocation use the five-minute tenant-security class; each reasserts the persisted session, active organization, and live capability inside its guarded mutation. Organization settings use the five-minute tenant-privilege class. Organization deletion uses the five-minute tenant-security class, binds resumable work to the initiating owner, and reasserts a current eligible user plus fresh persisted passkey session before every deletion stage and finalization boundary.
Tenant billing changes and global or tenant alert-rule changes use separate five-minute passkey classes. Billing reasserts the same persisted session, active organization, and live billing capability inside its organization fence. Alert-rule creation, update, and deletion reload and recheck the same strong global or tenant authority inside the existing actor, recipient, and organization fences before persistence.
Confidential global OIDC client creation, client-secret rotation, and delivery acknowledgement require a passkey authenticated within five minutes. The caller supplies a canonical RSA-3072 or RSA-4096 OAEP-SHA-256 public key and stable operation ID. The server atomically persists only the secret hash plus an encrypted, actor/request-bound delivery receipt; exact retries return the same ciphertext and cannot rotate twice. The browser SDK verifies and decrypts the envelope with its caller-held non-exportable private key. Exact acknowledgement then removes the ciphertext irreversibly while retaining a compact idempotency tombstone. No confidential creation or rotation response contains plaintext.
Password and login-identifier changes, TOTP disablement, backup-code regeneration, passkey registration or revocation, and Passport device enrollment or revocation require a passkey authenticated within five minutes. The same persisted session is reasserted inside the credential or identity mutation fence. To avoid a first-factor deadlock, a password, magic-link, or federated primary session may enroll only its first passkey or TOTP factor when that primary authentication is less than five minutes old; refresh activity does not renew this window. Password recovery remains a separate proof path and does not qualify as passkey step-up. A recovery request must carry exactly the current account-bound, ten-minute, one-time reset token and a 15–128 Unicode code-point password, normalized with NFC. Under the account identity fence, the server consumes that proof, invalidates every email-action token and pending user-bound MFA/WebAuthn challenge, revokes all platform sessions, JWTs, and OIDC state, and records the revocation before writing the new password. It then sends a security notification by both email and the recovered account's enrolled Passport push channel without disclosing reset credentials. Every new password in registration, authenticated change, or self-service recovery is checked against the HIBP Pwned Passwords range API. Only the first five hexadecimal SHA-1 characters leave the service, padded range responses are requested and cached for one hour, and provider or response-integrity failure rejects the change. Passkeys, TOTP, backup codes, and Passport devices remain enrolled recovery factors.
Operational Health Endpoints
These unauthenticated endpoints are intended for load balancers, orchestrators, and Docker runtime checks.
| Route | Methods | Status | Purpose |
|---|---|---|---|
/healthz |
GET, HEAD |
200 |
Process liveness once the HTTP server can respond |
/livez |
GET, HEAD |
200 |
Liveness alias for orchestrators that use live/readiness naming |
/readyz |
GET, HEAD |
200 only in the ready lifecycle state with live MongoDB and audit-provider evidence, otherwise 503 |
Readiness for routing traffic to the instance |
GET responses use Cache-Control: no-store and return JSON with ok, status, service, and version. Readiness reports starting, ready, draining, failed, or stopped; new business requests receive 503 outside ready.
The Docker image HEALTHCHECK probes http://127.0.0.1:2999/healthz so a deliberately draining instance is not mistaken for a dead process. Load balancers and orchestrators must use /readyz for traffic admission.
Passport And Mobile Approval Flow
PassportManager powers the trusted-device side of idp.global. A web session can create a passport enrollment challenge, the Swift app completes enrollment through a QR/NFC pairing payload, and later sign-in or identity checks can be approved by the paired device with signed challenge responses.
The typed request surface includes:
createPassportEnrollmentChallengeandcompletePassportEnrollmentfor pairing a trusted device.getPassportDevicesandrevokePassportDevicefor account-level device management.createPassportChallenge,approvePassportChallenge,rejectPassportChallenge, andlistPendingPassportChallengesfor approval flows.getPassportDashboard,listPassportAlerts, andmarkPassportAlertSeenfor mobile app dashboards and notifications.registerPassportPushTokenfor push delivery setup.
Passport devices, challenges, and nonce attempts use exact runtime-validated records under the live-user/account-purge fence. Enrollment and terminal challenge retries are idempotent, devices become operational only after their approved enrollment binding exists, public keys remain globally reserved after revocation while the device record is retained, and nonce replay state covers the full accepted clock-skew window. Challenge push hints use expiring delivery leases with accepted at-least-once retry after an uncertain outcome. Current account authentication eligibility is rechecked inside the fence before new or pending enrollment/challenge grants, signed-device access, or challenge push delivery; exact terminal retries may still repair their stable audit event. Direct deletion is reserved for exact nonce expiry and the account-purge cascade.
MFA And Passkeys
The reception backend supports real multi-factor authentication for account logins:
- TOTP enrollment with
startTotpEnrollmentandfinishTotpEnrollment. - Hashed one-time backup codes through
regenerateBackupCodesandverifyMfaChallenge. - WebAuthn passkey registration, revocation, passwordless login, and MFA step-up through the
startPasskey*andfinishPasskey*request pairs. - Password and magic-link logins return
twoFaNeeded,mfaChallengeToken, andavailableMfaMethodsinstead of a refresh token when MFA is configured.
Every persisted login session carries closed server-derived evidence for its
primary authentication method/time and exact MFA factor/time. Refresh and
transfer-token activity can update lastActive, but cannot advance either
authentication timestamp. The central authorization authority can require a
fresh user-verified passkey using the approved two- or five-minute operation
class. TOTP and backup codes are recovery factors and do not qualify for
global administration; no managed-device strong factor exists yet.
Authentication-method changes use the five-minute passkey class. The only exception is narrowly scoped first-factor bootstrap from a primary authentication less than five minutes old while the relevant active factors do not yet exist. Pending Passport enrollment records retain the exact authorizing session internally until device creation; serializers never expose that binding.
Global-administrator grants and removals use setGlobalAdmin. Callers first
review IGlobalUserAdminDto.revision, then submit that exact revision with a
caller-stable operationId. The server requires a distinct active global
administrator with a passkey authenticated within two minutes, serializes all
lifecycle decisions, preserves at least one active administrator, and emits
durable attempted plus terminal audit outcomes. Global administrators must
have their authority removed by another administrator before deleting their
own account. Administrative account purge, organization suspension, and OIDC
signing-key rotation use the same two-minute passkey class. Rotation first
persists a caller-stable request bound to the reviewed active key and expires it
after ten minutes without changing signing material. A different active global
administrator must approve with a separately fresh passkey session. The server
locks and revalidates both administrators, then atomically rotates the key and
closes an exact custody-tokenized retry receipt in the keyring CAS. Request
and terminal outcomes are purpose-bound in the protected audit trail.
A global administrator who has lost all passkeys may request one controlled replacement-passkey enrollment from a fresh TOTP or backup-code session. The request is exact-revision and caller-stable, expires after ten minutes, and must be approved by a distinct active global administrator using a passkey authenticated within two minutes. Approval opens a 15-minute grant bound to the requesting session. The first replacement passkey closes the grant atomically; retry reconciliation cannot enroll a second passkey. Request, activation, completion, expiry conflicts, demotion, and suspension share the administrator lifecycle fence and emit structured audit plus global security alerts.
TOTP secrets are AES-GCM encrypted. IDP_TOTP_ENCRYPTION_KEY is required and must stay stable across deployments so enrolled credentials remain decryptable.
Domain Surfaces
https://idp.globalis public and remains the permanent issuer, discovery, OIDC, SAML, and SCIM origin.https://login.idp.globalis public and hosts login, registration, MFA, consent, recovery, and invitation flows.https://app.idp.globalis private and hosts the dashboard, full application API, and administrative CLI.
SDK Example
The private application shell uses the dedicated SDK browser entrypoint published by @idp.global/sdk.
Public relying parties should integrate through dynamic OIDC discovery at https://idp.global/.well-known/openid-configuration, not the private TypedRequest API.
import { IdpClient } from '@idp.global/sdk/browser';
const idpClient = new IdpClient(
'https://app.idp.global',
{ appUrl: 'https://app.idp.global/' },
{ loginBaseUrl: 'https://login.idp.global' },
);
await idpClient.enableTypedSocket();
const isLoggedIn = await idpClient.determineLoginStatus();
if (!isLoggedIn) {
const loginResult = await idpClient.requests.loginWithUserNameAndPassword.fire({
username: 'user@example.com',
password: 'correct horse battery staple',
});
if (loginResult.refreshToken) {
await idpClient.refreshJwt(loginResult.refreshToken);
}
}
const whoIs = await idpClient.whoIs();
console.log(whoIs.user.data.email);
CLI Example
The terminal client lives in ts_idpcli/ and is published as @idp.global/cli.
idp login
idp whoami
idp orgs
idp members --org <org-id>
idp invite --org <org-id> --email user@example.com
The CLI defaults to private https://app.idp.global, requires hub/VPN access, stores credential secrets in the local OS keyring, keeps only non-secret metadata in ~/.idp-global/credentials.json, and reads IDP_URL to override the target server.
Shared Interfaces
The sibling @idp.global/interfaces package exports the type contracts shared across the stack:
data/*for users, orgs, roles, JWTs, sessions, devices, billing plans, apps, passport records, alerts, OIDC, SAML, and SCIM payloads.request/*for auth, registration, user, org, invitation, app, admin, billing, JWT, passport, alert, OIDC, SAML, and SCIM request contracts.tags/*for shared tag exports.
Frontend
ts_web/ is the web application bundle. It contains:
- Login and registration prompts.
- A registration stepper.
- Account navigation and account views.
- Organization creation and bulk invite modals.
- Billing and Paddle setup views.
- Organization SAML/SCIM configuration with DNS verification state.
- A global admin view.
Package Scripts
| Command | Purpose |
|---|---|
pnpm build |
Build TypeScript output and frontend bundle |
pnpm watch |
Run backend watch mode and frontend bundle watch |
pnpm test |
Build and run the test suite |
pnpm run preflight:data |
Verify the exact database/schema and identity invariants; this is one subordinate check and never release authorization |
pnpm run preflight:namespace -- --expect=blank|prebootstrap|stable |
Classify a namespace with read-only inspection, verify the expected state, and fail on read drift without installing or repairing anything |
pnpm run build:docker |
Build one clean, configured multi-platform candidate into the project-local tSDocker registry |
pnpm run digest:docker |
Capture and validate the candidate's canonical tSDocker digest JSON and source receipt |
pnpm run test:docker |
Test that exact persisted candidate by expected digest without rebuilding |
pnpm run verify:docker |
Bind native runtime, image-config, platform-manifest, and embedded supply-chain evidence to the exact candidate digest |
pnpm run seal:docker-evidence |
Revalidate and print the deterministic technical evidence seal; it explicitly does not authorize release |
pnpm run supplychain:evidence |
Generate deterministic CycloneDX, dependency-license, root-license, and missing-license evidence for the current platform |
Current-Schema and Data-Preflight API
The backend entrypoint exports CurrentSchemaManager, currentSchemaManifest, currentSchemaManifestDigest, currentSchemaVersion, the identity-preflight analyzers, the count-only release-data preflight functions, and the namespace-preflight API: namespacePreflightExpectations, runNamespacePreflight, and evaluateNamespacePreflight. Embedding and operator tooling can use these APIs to install or verify the one accepted blank/current namespace and to detect schema, identity, or held-fence blockers without logging identifiers. Namespace preflight uses read-only collection, count, schema-state, index, and data-conformance inspection. It maps expectations blank, prebootstrap, and stable to classifications blank, current-prebootstrap, and current-stable; other classifications are nonblank-unmanaged and managed-incompatible. It records before/after counts and fails if the namespace changes during observation.
Current-data verification checks the complete raw shape of all revisioned documents, Apps, the five user-bound OIDC authorization/token/consent collections, two MFA credential collections, three Passport collections, the identity-free OIDC transaction and SAML request collections, and the singleton startup controls. OIDC and Passport inserts are acknowledged and runtime-validated. Passport inserts, user-created global App writes, AppConnection creation, and OIDC consent grants are serialized against the live-user/purge fence. Apps use exact per-type validation and revision CAS; AppConnection inserts reconcile committed acknowledgement loss without inherited upserts. Consent grants and Passport lifecycle transitions use exact no-upsert CAS, while Passport telemetry uses named monotonic mutations.
These checks establish only the database contract. They do not authorize a package, tag, container, deployment, or production route opening; every gate in readme.plan.md still applies.
Docker Candidate Diagnostics
These commands are non-publishing diagnostics, not a qualified release path. They require one fresh project-scoped session ID and one clean source checkout. The wrapper rejects tracked/untracked changes and verifies that every repository-ignored path is excluded from the Docker context. Keep the same ID and checkout for the lifecycle; a new build attempt requires a new ID.
export TSDOCKER_SESSION_ID="idp-app-<source-and-run-specific-id>"
pnpm run build:docker
pnpm run digest:docker
# Run the following two commands natively on linux/amd64 and linux/arm64.
pnpm run test:docker
pnpm run verify:docker
# Run only after both native platform records are present.
pnpm run seal:docker-evidence
The canonical registry workspace remains below
.nogit/docker-registry/$TSDOCKER_SESSION_ID/. The raw tSDocker digest JSON and the
source/tool/test receipt remain below .nogit/release-evidence/. The receipt binds the
candidate to the Git revision, package version, Dockerfile pattern, project-local tSDocker
version, configured platform set, clean tag, and top-level digest. Digest capture retains
the exact raw OCI index plus runnable and attestation manifests, recomputes their digests
and sizes, binds each config and layer descriptor, and re-reads the project-local registry
when evidence is consumed. Native verification records each host platform independently,
binds the local image ID to that platform's config digest, verifies the in-image evidence
artifact hashes, and reaches evidence-sealed only after both linux/amd64 and
linux/arm64 name the same candidate digest. The terminal receipt and verifier output both
carry releaseAuthorized: false. The generated evidence includes a normalized
CycloneDX SBOM, installed production-license inventory, the root license.md hash, and a
fail-closed missing-license gate.
The final successful verify:docker creates or reuses a deterministic technical evidence
seal. A recoverable retry produces identical seal content; a stale receipt lock remains a
fail-closed operator condition. seal:docker-evidence only revalidates and prints that
existing technical seal with releaseAuthorized: false. It is not release acceptance and does not
prove provenance, signatures, vulnerability-policy compliance, reproducibility, qualified
notices, immutable remote publication, or deployment safety. Those gates remain mandatory
in readme.plan.md.
The GitZone Docker release target is intentionally disabled. GitZone CLI 2.23 invokes a
global tsdocker binary and cannot transport tSDocker 3 expected-digest evidence, so it
must not own image publication. There is deliberately no release:docker script.
@git.zone/tsdeploy owns production publication: it reserves a fenced Cloudly operation,
builds and tests with the project-local tSDocker 3.0.0, pushes the exact version tag through
an operation-scoped registry credential, requires source/configuration/index-bound
provenance, SBOM, SSH signature, vulnerability-policy, and explicit release-authorization
evidence, then promotes only Cloudly's independently observed immutable registry release.
The Gitea branch workflow runs the source build and regression suite only. It deliberately does not construct a container candidate or release evidence before Gate 0 reserves an unused version and candidate identity. The stable-tag workflow hard-fails instead of rebuilding: it must eventually verify retained accepted digest/evidence without constructing a new candidate.
Self-Hosting
The app ships as a single Docker image and needs a MongoDB instance next to it. Runtime configuration and database state are fail-closed. Partial, unknown, or malformed bootstrap environment configuration is rejected before the HTTP listener starts. With no bootstrap variables at all, a blank production database reaches the database/bootstrap-state gate after listener startup: /healthz and /livez remain 200, /readyz reports 503 failed, and every business/OIDC route returns 503 with Retry-After: 1 and service_unavailable until the operator stops and restarts the instance with complete bootstrap input.
The packaged executable composes its production audit runtime from environment configuration.
OpenBao Transit protects audit identities, Lossless S3-compatible object storage provides the
compliance-mode retention sink, and serve.zone service mail provides durable dead-letter
alerting. Startup and /readyz require deadline-bounded, challenge-bound evidence for the
exact configured keys, active retention lock and sentinel object, and alert route.
First Production Launch
A fresh production database never creates an administrator implicitly. After the one current-schema installer establishes and verifies the exact empty namespace, the service requires one explicit initial-admin operation. The operation creates exactly one global administrator, one organization, one owner role with manual provenance, canonical email/username ownership records, and one organization-created structured audit event. It creates no demo users, OAuth apps, API tokens, or other defaults.
Set all of these values for the first launch only:
| Variable | Constraint |
|---|---|
IDP_INITIAL_BOOTSTRAP_MODE |
Exactly initial-admin. This is the explicit opt-in. |
IDP_INITIAL_BOOTSTRAP_OPERATION_ID |
Stable operator-chosen id, 1–128 ASCII characters. The first character must be alphanumeric; remaining characters may also use ., _, :, or -. Reuse it after an interrupted attempt. |
IDP_INITIAL_BOOTSTRAP_ADMIN_NAME |
Display name for the initial administrator; 1–200 characters with no control characters. |
IDP_INITIAL_BOOTSTRAP_ADMIN_EMAIL |
Valid administrator email, at most 320 characters; stored in canonical lowercase form. |
IDP_INITIAL_BOOTSTRAP_ADMIN_USERNAME |
Initial username, 1–320 characters with no control characters; stored in canonical lowercase form. |
IDP_INITIAL_BOOTSTRAP_ORGANIZATION_NAME |
Display name for the initial organization; 1–200 characters with no control characters. |
IDP_INITIAL_BOOTSTRAP_ORGANIZATION_SLUG |
Lowercase slug, 3–64 characters, using alphanumerics and single hyphen-separated segments. |
IDP_INITIAL_BOOTSTRAP_PASSWORD_FILE |
Absolute path to the mounted password file described below. The password itself must never be placed in an environment variable or command argument. |
All eight variables are one contract. Companions without the mode, an unknown mode, a missing companion, or malformed metadata fail configuration loading before the HTTP listener starts.
The password path must resolve directly to a regular file; symlinks are rejected. The file must be readable by runtime UID 10001, must not be group- or world-writable, and must contain 15–512 bytes of valid UTF-8 with no control character or trailing newline. After NFC normalization, the password must contain 15–128 Unicode code points. The reader requires operating-system no-follow support and verifies that device, inode, ownership, mode, size, and timestamps do not change while it is read. Docker secrets mounted as regular read-only files are suitable. Kubernetes projected-secret paths are symlinks by default, so expose the selected key as a regular subPath mount and pass that path instead.
Bootstrap accepts only the exact completed current-schema manifest and empty application state. Any user, organization, role, identity authority, audit entry, foreign collection or index, conflicting request, or wrong password blocks without overwriting or deleting that state. Identical concurrent calls converge on the same deterministic graph. An interrupted call must be restarted with the exact same metadata and password; only the bootstrap installer may take over its own expired lease because every effect is deterministic and forward-only.
When /readyz becomes 200, verify administrator login, stop the instance, remove every IDP_INITIAL_BOOTSTRAP_* value and the password mount, and restart. The terminal receipt is retained without its transient password verifier, normal identity data may evolve, and later startup does not need or read the bootstrap password. Keeping matching metadata configured is tolerated after completion, but removal is the required operator cleanup.
Environment Variables
| Variable | Required | Purpose |
|---|---|---|
IDP_ENV |
yes | Runtime mode: production, development, test, or local. Anything unset defaults to production (fail-closed). |
INSTANCE_NAME |
yes | Instance identifier used in logs and metrics. |
MONGODB_URL |
yes | MongoDB connection string. |
IDP_BASEURL |
yes | Public origin (must be https:// in production; http:// only for localhost in non-production). |
SERVEZONE_PLATFORM_AUTHORIZATION |
yes | serve.zone platform binding for outbound email. test is rejected in production. |
IDP_BACKEND_TOKEN |
production | Shared secret (≥32 chars) that backend consumers present for JWT public-key and blocklist retrieval. |
IDP_TOTP_ENCRYPTION_KEY |
yes | Key material (≥32 chars) encrypting TOTP secrets at rest. Never rotate casually — enrolled authenticators become unreadable. |
IDP_METRICS_TOKEN |
no | Bearer token (≥32 chars) gating GET /metrics (Prometheus text format). Unset disables the endpoint (404). |
IDP_SHUTDOWN_GRACE_PERIOD_MS |
no | Grace period for draining admitted requests and background work before reporting a shutdown timeout (integer from 1000 through 900000; default 30000). Cleanup continues after a timeout report. |
IDP_INITIAL_BOOTSTRAP_* |
first production launch only | Complete one-shot administrator/organization configuration from the preceding section. Partial configuration is rejected. Remove all values and the mounted password file after the verified bootstrap restart. |
docker-compose Example
services:
idp:
image: code.foss.global/idp.global/app@sha256:<recorded-release-digest>
restart: unless-stopped
ports:
- '2999:2999'
read_only: true
tmpfs:
- /tmp:size=64m,mode=1777
stop_grace_period: 45s
environment:
IDP_ENV: production
INSTANCE_NAME: idp-selfhosted
MONGODB_URL: mongodb://idp:change-me@mongodb:27017/idp?authSource=admin
IDP_BASEURL: https://idp.example.com
SERVEZONE_PLATFORM_AUTHORIZATION: '<your serve.zone binding>'
IDP_BACKEND_TOKEN: '<random 32+ char secret>'
IDP_TOTP_ENCRYPTION_KEY: '<random 32+ char secret, back it up>'
IDP_SHUTDOWN_GRACE_PERIOD_MS: 30000
IDP_INITIAL_BOOTSTRAP_MODE: initial-admin
IDP_INITIAL_BOOTSTRAP_OPERATION_ID: first-production-launch
IDP_INITIAL_BOOTSTRAP_ADMIN_NAME: '<initial administrator name>'
IDP_INITIAL_BOOTSTRAP_ADMIN_EMAIL: '<initial administrator email>'
IDP_INITIAL_BOOTSTRAP_ADMIN_USERNAME: '<initial administrator username>'
IDP_INITIAL_BOOTSTRAP_ORGANIZATION_NAME: '<initial organization name>'
IDP_INITIAL_BOOTSTRAP_ORGANIZATION_SLUG: '<initial-organization-slug>'
IDP_INITIAL_BOOTSTRAP_PASSWORD_FILE: /run/secrets/idp-bootstrap-password
secrets:
- idp-bootstrap-password
depends_on:
- mongodb
mongodb:
image: mongo:8
restart: unless-stopped
environment:
MONGO_INITDB_ROOT_USERNAME: idp
MONGO_INITDB_ROOT_PASSWORD: change-me
volumes:
- mongodb-data:/data/db
volumes:
mongodb-data:
secrets:
idp-bootstrap-password:
file: ./secrets/idp-bootstrap-password
Create the password file without a trailing newline and protect the host copy before starting Compose. After the successful bootstrap verification, remove the eight bootstrap environment entries and the service secret mount, then restart the service. Use only a released immutable digest; the placeholder above must be replaced before deployment.
Health endpoints for orchestration: GET /healthz (liveness), GET /readyz (readiness; reports the running version and lifecycle state). The image ships a Docker HEALTHCHECK probing /healthz; configure traffic admission against /readyz.
Rollback by Digest
Record the immutable multi-architecture digest and workload tag for every release. Roll back by selecting that recorded immutable artifact in the orchestrator; never move or retag latest.
Cloudly deployments must follow the release and rollback runbook. The first production launch has no legacy-data upgrade or cutover. Before public traffic is enabled, rollback means keeping routing closed, returning to the recorded immutable artifact, and either retaining the untouched empty target or restoring the fresh post-bootstrap backup proven by the isolated restore rehearsal. After real production data exists, every later release must declare its own data-compatibility and backup requirements; an image rollback must never be assumed to reverse schema or data changes.
Observability
- Production logs are JSON lines on stdout (one object per log event, including commitinfo context). Non-production modes log human-readable text.
- Every handled typed request logs method, request id (correlation id), duration and error class. Payloads are never logged.
- Raw HTTP endpoints honor an incoming
x-request-idheader and echo it on the response. GET /metrics(withAuthorization: Bearer $IDP_METRICS_TOKEN) exposes process and system metrics in Prometheus text format.
Operational runbooks (health monitoring, audit export, access reviews, release/rollback) live in docs/soc2/.
Repository Notes
- Package manager:
pnpm - Main backend entrypoint:
ts/index.ts - Frontend entrypoint:
ts_web/index.ts - Browser SDK entrypoint:
@idp.global/sdk/browser - CLI entrypoint:
ts_idpcli/index.ts
License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the license.md file.
Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
Company Information
Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.