@runeditrun/rer 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +154 -2
- package/bin/rer.mjs +3 -0
- package/cli/README.md +243 -0
- package/cli/adapter.mjs +10 -0
- package/cli/ai-local.mjs +65 -0
- package/cli/archive-path.mjs +52 -0
- package/cli/archive-vite.mjs +55 -0
- package/cli/archive.mjs +8 -0
- package/cli/auth-copy.mjs +80 -0
- package/cli/bounded.mjs +70 -0
- package/cli/cache-doctor.mjs +63 -0
- package/cli/container-transfer.mjs +35 -0
- package/cli/container.mjs +62 -0
- package/cli/contract.mjs +25 -0
- package/cli/dependencies.mjs +45 -0
- package/cli/dependency-approval.mjs +33 -0
- package/cli/dependency-doctor.mjs +34 -0
- package/cli/dependency-input.mjs +43 -0
- package/cli/deploy-config.mjs +118 -0
- package/cli/deploy.mjs +37 -0
- package/cli/dev-identity.mjs +80 -0
- package/cli/errors.mjs +14 -0
- package/cli/export.mjs +42 -0
- package/cli/font-cache.mjs +29 -0
- package/cli/hosted-output.mjs +110 -0
- package/cli/independent.mjs +29 -0
- package/cli/inspect.mjs +174 -0
- package/cli/jsonc.mjs +111 -0
- package/cli/lifecycle.mjs +148 -0
- package/cli/local.mjs +100 -0
- package/cli/main.mjs +57 -0
- package/cli/managed-server.mjs +143 -0
- package/cli/node-runtime-cache.mjs +68 -0
- package/cli/node-runtime-discovery.mjs +56 -0
- package/cli/node-runtime-download.mjs +59 -0
- package/cli/node-runtime.mjs +50 -0
- package/cli/output.mjs +22 -0
- package/cli/parse.mjs +77 -0
- package/cli/port.mjs +52 -0
- package/cli/portability-worker.mjs +56 -0
- package/cli/portability.mjs +66 -0
- package/cli/preview-cache.mjs +99 -0
- package/cli/project.mjs +48 -0
- package/cli/release.mjs +24 -0
- package/cli/runtime-front-door.mjs +46 -0
- package/cli/scheduled.mjs +81 -0
- package/cli/server-activity.mjs +12 -0
- package/cli/server-proxy.mjs +93 -0
- package/cli/server-registry.mjs +177 -0
- package/cli/server-resources.mjs +22 -0
- package/cli/settings.mjs +15 -0
- package/cli/setup-answers.mjs +29 -0
- package/cli/setup-input.mjs +113 -0
- package/cli/shared.mjs +7 -0
- package/cli/transfers.mjs +33 -0
- package/cli/version.mjs +36 -0
- package/composition/git-source.mjs +111 -0
- package/edition-authoring/files.mjs +81 -0
- package/edition-authoring/manifest.mjs +65 -0
- package/global/README.md +155 -0
- package/global/checkout.mjs +72 -0
- package/global/cli/command.mjs +13 -0
- package/global/cli/install.mjs +75 -0
- package/global/cli/layout.mjs +45 -0
- package/global/cli/status.mjs +54 -0
- package/global/cli/uninstall.mjs +36 -0
- package/global/delegate.mjs +13 -0
- package/global/launch/capacity.mjs +72 -0
- package/global/launch/dev.mjs +129 -0
- package/global/launch/facts.mjs +93 -0
- package/global/launch/failure.mjs +5 -0
- package/global/launch/local-defaults.mjs +4 -0
- package/global/launch/prepare.mjs +61 -0
- package/global/launch/render.mjs +44 -0
- package/global/launch/state.mjs +97 -0
- package/global/launch/steps.mjs +81 -0
- package/global/launch/worker.mjs +43 -0
- package/global/list.mjs +59 -0
- package/global/main.mjs +53 -0
- package/global/package-info.mjs +38 -0
- package/global/registry.mjs +24 -0
- package/global/rer.mjs +29 -0
- package/global/update/archive.mjs +89 -0
- package/global/update/command.mjs +90 -0
- package/global/update/latest.mjs +6 -0
- package/global/update/notice.mjs +58 -0
- package/global/update/registry.mjs +47 -0
- package/global/version.mjs +29 -0
- package/install.sh +144 -0
- package/modules/ai/LICENSE +21 -0
- package/modules/ai/README.md +90 -0
- package/modules/ai/client.mjs +71 -0
- package/modules/ai/config.mjs +39 -0
- package/modules/ai/dev/index.d.mts +6 -0
- package/modules/ai/dev/index.mjs +64 -0
- package/modules/ai/dev/outbox.mjs +33 -0
- package/modules/ai/errors.mjs +28 -0
- package/modules/ai/fake/index.d.mts +3 -0
- package/modules/ai/fake/index.mjs +82 -0
- package/modules/ai/index.d.mts +10 -0
- package/modules/ai/index.mjs +3 -0
- package/modules/ai/package.json +6 -0
- package/modules/container/README.md +156 -0
- package/modules/container/adapters/d1.mjs +34 -0
- package/modules/container/adapters/egress.mjs +40 -0
- package/modules/container/adapters/jobs.mjs +47 -0
- package/modules/container/adapters/objects.mjs +84 -0
- package/modules/container/adapters/retained.mjs +14 -0
- package/modules/container/adapters/sqlite-error.mjs +7 -0
- package/modules/container/adapters/sqlite-worker.mjs +26 -0
- package/modules/container/assets.mjs +14 -0
- package/modules/container/bindings.mjs +1 -0
- package/modules/container/boundary.mjs +38 -0
- package/modules/container/build.mjs +65 -0
- package/modules/container/deploy-input.mjs +17 -0
- package/modules/container/entrypoint.sh +5 -0
- package/modules/container/host.mjs +43 -0
- package/modules/container/ingress.mjs +23 -0
- package/modules/container/lock.mjs +11 -0
- package/modules/container/migrations.mjs +16 -0
- package/modules/container/node-engine.mjs +39 -0
- package/modules/container/portability-schema.mjs +14 -0
- package/modules/container/remote/deploy.py +76 -0
- package/modules/container/remote/files.py +275 -0
- package/modules/container/remote/resources.py +157 -0
- package/modules/container/runner.mjs +10 -0
- package/modules/container/ssh-plan.mjs +44 -0
- package/modules/container/ssh.mjs +63 -0
- package/modules/container/transfer.mjs +27 -0
- package/modules/container/worker-engine.mjs +32 -0
- package/modules/dependencies/README.md +211 -0
- package/modules/dependencies/approval.mjs +51 -0
- package/modules/dependencies/auth.mjs +40 -0
- package/modules/dependencies/authority.mjs +14 -0
- package/modules/dependencies/crypto.mjs +13 -0
- package/modules/dependencies/index.d.mts +23 -0
- package/modules/dependencies/index.mjs +113 -0
- package/modules/dependencies/logout.mjs +42 -0
- package/modules/dependencies/native.mjs +9 -0
- package/modules/dependencies/protocol.mjs +23 -0
- package/modules/dependencies/schema/approval-attempt.sql +3 -0
- package/modules/dependencies/schema/dependencies.sql +15 -0
- package/modules/dependencies/store.mjs +19 -0
- package/modules/deploy-kit/README.md +308 -0
- package/modules/deploy-kit/artifact.mjs +47 -0
- package/modules/deploy-kit/build-modules.mjs +21 -0
- package/modules/deploy-kit/check.mjs +13 -0
- package/modules/deploy-kit/cli.mjs +50 -0
- package/modules/deploy-kit/container-config.mjs +8 -0
- package/modules/deploy-kit/default-stage.mjs +14 -0
- package/modules/deploy-kit/deploy.mjs +80 -0
- package/modules/deploy-kit/health-request.mjs +86 -0
- package/modules/deploy-kit/health.mjs +99 -0
- package/modules/deploy-kit/index.mjs +12 -0
- package/modules/deploy-kit/local-only.mjs +10 -0
- package/modules/deploy-kit/migrations.mjs +48 -0
- package/modules/deploy-kit/owner-cli.mjs +63 -0
- package/modules/deploy-kit/owner.mjs +297 -0
- package/modules/deploy-kit/prepare.mjs +62 -0
- package/modules/deploy-kit/provider.mjs +99 -0
- package/modules/deploy-kit/queue-consumer.mjs +10 -0
- package/modules/deploy-kit/receipt.mjs +16 -0
- package/modules/deploy-kit/render.mjs +47 -0
- package/modules/deploy-kit/resources.mjs +106 -0
- package/modules/deploy-kit/service-grants.mjs +25 -0
- package/modules/deploy-kit/sql-safety.mjs +94 -0
- package/modules/deploy-kit/sql-tokens.mjs +49 -0
- package/modules/deploy-kit/teardown.mjs +100 -0
- package/modules/deploy-kit/templates.mjs +31 -0
- package/modules/deploy-kit/validation.mjs +58 -0
- package/modules/deploy-kit/worker.mjs +105 -0
- package/modules/erasure/CONTRACT.md +90 -0
- package/modules/erasure/README.md +221 -0
- package/modules/erasure/audit.mjs +23 -0
- package/modules/erasure/blobs.mjs +45 -0
- package/modules/erasure/cascade.mjs +91 -0
- package/modules/erasure/completion.mjs +62 -0
- package/modules/erasure/declaration.mjs +51 -0
- package/modules/erasure/fence.mjs +19 -0
- package/modules/erasure/index.d.mts +62 -0
- package/modules/erasure/index.mjs +85 -0
- package/modules/erasure/order.mjs +56 -0
- package/modules/erasure/plan.mjs +121 -0
- package/modules/erasure/rate.mjs +57 -0
- package/modules/erasure/receipt.mjs +32 -0
- package/modules/erasure/root.mjs +21 -0
- package/modules/erasure/schema/erasure-rates.sql +5 -0
- package/modules/erasure/schema/erasure.sql +27 -0
- package/modules/erasure/scope.mjs +29 -0
- package/modules/erasure/sql.mjs +23 -0
- package/modules/erasure/verification.mjs +69 -0
- package/modules/integrations/core/CONTRACT.md +50 -0
- package/modules/integrations/core/README.md +27 -0
- package/modules/integrations/core/crypto.ts +19 -0
- package/modules/integrations/core/database.ts +21 -0
- package/modules/integrations/core/delivery.ts +97 -0
- package/modules/integrations/core/destination.ts +13 -0
- package/modules/integrations/core/index.ts +7 -0
- package/modules/integrations/core/node-transport.ts +72 -0
- package/modules/integrations/core/public-address.ts +57 -0
- package/modules/integrations/core/repository.ts +74 -0
- package/modules/integrations/core/schema.sql +36 -0
- package/modules/integrations/core/sqlite.ts +20 -0
- package/modules/integrations/core/timestamped-body-signature.ts +38 -0
- package/modules/integrations/core/tokens.ts +71 -0
- package/modules/integrations/core/types.ts +52 -0
- package/modules/integrations/core/upgrade-token-usage-context.sql +5 -0
- package/modules/integrations/core/webhook.ts +73 -0
- package/modules/job-queue/README.md +173 -0
- package/modules/job-queue/cli.mjs +64 -0
- package/modules/job-queue/command.mjs +64 -0
- package/modules/job-queue/config.mjs +48 -0
- package/modules/job-queue/identity.mjs +48 -0
- package/modules/job-queue/index.mjs +135 -0
- package/modules/job-queue/membership.mjs +30 -0
- package/modules/job-queue/module.json +10 -0
- package/modules/job-queue/policy.mjs +29 -0
- package/modules/job-queue/processes.mjs +80 -0
- package/modules/job-queue/resources.mjs +37 -0
- package/modules/job-queue/storage.mjs +64 -0
- package/modules/job-queue/supervisor.mjs +30 -0
- package/modules/job-queue/waiting.mjs +16 -0
- package/modules/mail/LICENSE +21 -0
- package/modules/mail/README.md +336 -0
- package/modules/mail/doctor.mjs +81 -0
- package/modules/mail/index.mjs +8 -0
- package/modules/mail/live-proof/README.md +113 -0
- package/modules/mail/live-proof/run.mjs +55 -0
- package/modules/mail/package.json +19 -0
- package/modules/mail/pnpm-lock.yaml +537 -0
- package/modules/mail/src/adapters/cloudflare/README.md +84 -0
- package/modules/mail/src/adapters/cloudflare/headers.mjs +63 -0
- package/modules/mail/src/adapters/cloudflare/index.mjs +47 -0
- package/modules/mail/src/adapters/cloudflare/response.mjs +99 -0
- package/modules/mail/src/adapters/messaging/README.md +92 -0
- package/modules/mail/src/adapters/messaging/contract.mjs +66 -0
- package/modules/mail/src/adapters/messaging/event-schema.mjs +28 -0
- package/modules/mail/src/adapters/messaging/index.mjs +61 -0
- package/modules/mail/src/adapters/messaging/jws.mjs +68 -0
- package/modules/mail/src/adapters/messaging/request.mjs +52 -0
- package/modules/mail/src/adapters/messaging/serialize.mjs +21 -0
- package/modules/mail/src/adapters/resend/README.md +120 -0
- package/modules/mail/src/adapters/resend/index.mjs +22 -0
- package/modules/mail/src/adapters/resend/response.mjs +54 -0
- package/modules/mail/src/adapters/resend/serialize.mjs +46 -0
- package/modules/mail/src/adapters/resend/size.mjs +99 -0
- package/modules/mail/src/adapters/sendgrid/README.md +75 -0
- package/modules/mail/src/adapters/sendgrid/index.mjs +35 -0
- package/modules/mail/src/adapters/sendgrid/serialize.mjs +104 -0
- package/modules/mail/src/core/errors.mjs +9 -0
- package/modules/mail/src/core/transport.mjs +33 -0
- package/modules/mail/src/core/validation.mjs +172 -0
- package/modules/mail/src/development/index.d.mts +37 -0
- package/modules/mail/src/development/index.mjs +35 -0
- package/modules/mail/src/development/outbox.mjs +78 -0
- package/modules/mail/src/development/schema.sql +9 -0
- package/modules/mail/src/development/viewer-html.mjs +42 -0
- package/modules/mail/src/development/viewer.mjs +65 -0
- package/modules/mail/src/doctor/README.md +107 -0
- package/modules/mail/src/doctor/common.mjs +64 -0
- package/modules/mail/src/doctor/dns.mjs +102 -0
- package/modules/mail/src/doctor/index.mjs +28 -0
- package/modules/mail/src/doctor/probe.mjs +66 -0
- package/modules/mail/src/doctor/receive.mjs +81 -0
- package/modules/mail/src/doctor/records.mjs +45 -0
- package/modules/mail/src/events/README.md +268 -0
- package/modules/mail/src/events/d1-schema.sql +25 -0
- package/modules/mail/src/events/d1-storage.mjs +76 -0
- package/modules/mail/src/events/index.mjs +3 -0
- package/modules/mail/src/events/intake.mjs +201 -0
- package/modules/mail/src/events/storage.mjs +52 -0
- package/modules/mail/src/events/verify.mjs +92 -0
- package/modules/mail/src/inbound/README.md +137 -0
- package/modules/mail/src/inbound/bytes.mjs +91 -0
- package/modules/mail/src/inbound/headers.mjs +143 -0
- package/modules/mail/src/inbound/index.mjs +29 -0
- package/modules/mail/src/inbound/mime.mjs +36 -0
- package/modules/mail/src/inbound/multipart.mjs +27 -0
- package/modules/mail/src/inbound/resend-attachment.mjs +43 -0
- package/modules/mail/src/inbound/resend.mjs +54 -0
- package/modules/mail/src/inbound/sendgrid.mjs +60 -0
- package/modules/mail/src/sender.mjs +33 -0
- package/modules/mail/src/sendgrid-email/index.d.mts +33 -0
- package/modules/mail/src/sendgrid-email/index.mjs +86 -0
- package/modules/mail/src/unsubscribe/index.mjs +142 -0
- package/modules/operator-auth/README.md +599 -0
- package/modules/operator-auth/accounts/adapter.mjs +41 -0
- package/modules/operator-auth/accounts/admin.mjs +159 -0
- package/modules/operator-auth/accounts/credentials.mjs +27 -0
- package/modules/operator-auth/accounts/editor-owner.mjs +13 -0
- package/modules/operator-auth/accounts/index.d.mts +9 -0
- package/modules/operator-auth/accounts/index.mjs +9 -0
- package/modules/operator-auth/accounts/native.d.mts +38 -0
- package/modules/operator-auth/accounts/native.mjs +171 -0
- package/modules/operator-auth/accounts/oa.mjs +90 -0
- package/modules/operator-auth/accounts/provider-control.mjs +50 -0
- package/modules/operator-auth/accounts/provider-pin.mjs +14 -0
- package/modules/operator-auth/accounts/roles.mjs +26 -0
- package/modules/operator-auth/accounts/schema/dependency-setup.sql +17 -0
- package/modules/operator-auth/accounts/schema/dev-sessions.sql +22 -0
- package/modules/operator-auth/accounts/schema/domain-rules-step-up.sql +20 -0
- package/modules/operator-auth/accounts/schema/invitation-cancellation.sql +17 -0
- package/modules/operator-auth/accounts/schema/operator-auth.sql +142 -0
- package/modules/operator-auth/accounts/schema/provider-control.sql +30 -0
- package/modules/operator-auth/accounts/schema/provider-pins.sql +6 -0
- package/modules/operator-auth/accounts/settings-page.mjs +4 -0
- package/modules/operator-auth/accounts/store.mjs +246 -0
- package/modules/operator-auth/branding.mjs +66 -0
- package/modules/operator-auth/config.mjs +35 -0
- package/modules/operator-auth/conformance/README.md +81 -0
- package/modules/operator-auth/conformance/index.d.mts +43 -0
- package/modules/operator-auth/conformance/index.mjs +300 -0
- package/modules/operator-auth/conformance/presentation.mjs +57 -0
- package/modules/operator-auth/crypto.mjs +17 -0
- package/modules/operator-auth/dev/badge.mjs +62 -0
- package/modules/operator-auth/dev/index.d.mts +14 -0
- package/modules/operator-auth/dev/index.mjs +137 -0
- package/modules/operator-auth/dev/personas.mjs +17 -0
- package/modules/operator-auth/dev/sign-in-as.d.mts +3 -0
- package/modules/operator-auth/dev/sign-in-as.mjs +25 -0
- package/modules/operator-auth/dev-admission.d.mts +4 -0
- package/modules/operator-auth/dev-admission.mjs +10 -0
- package/modules/operator-auth/dev-gate.mjs +86 -0
- package/modules/operator-auth/docs/README.md +78 -0
- package/modules/operator-auth/docs/native-accounts.md +313 -0
- package/modules/operator-auth/docs/tanstack-start.md +218 -0
- package/modules/operator-auth/docs/vite-spa.md +144 -0
- package/modules/operator-auth/errors.mjs +9 -0
- package/modules/operator-auth/http.mjs +42 -0
- package/modules/operator-auth/identity/admission.mjs +105 -0
- package/modules/operator-auth/identity/after-confirm.mjs +18 -0
- package/modules/operator-auth/identity/email-link/confirmation.mjs +14 -0
- package/modules/operator-auth/identity/email-link/index.d.mts +48 -0
- package/modules/operator-auth/identity/email-link/index.mjs +128 -0
- package/modules/operator-auth/identity/email-link/message.mjs +16 -0
- package/modules/operator-auth/identity/index.d.mts +7 -0
- package/modules/operator-auth/identity/index.mjs +5 -0
- package/modules/operator-auth/identity/oidc/README.md +171 -0
- package/modules/operator-auth/identity/oidc/client-auth.mjs +16 -0
- package/modules/operator-auth/identity/oidc/config.mjs +102 -0
- package/modules/operator-auth/identity/oidc/crypto.mjs +29 -0
- package/modules/operator-auth/identity/oidc/errors.mjs +5 -0
- package/modules/operator-auth/identity/oidc/index.d.mts +33 -0
- package/modules/operator-auth/identity/oidc/index.mjs +287 -0
- package/modules/operator-auth/identity/oidc/jwt.mjs +102 -0
- package/modules/operator-auth/identity/oidc/network.mjs +93 -0
- package/modules/operator-auth/identity/oidc/presets.mjs +26 -0
- package/modules/operator-auth/identity/oidc/schema/oidc.sql +50 -0
- package/modules/operator-auth/identity/oidc/store.mjs +68 -0
- package/modules/operator-auth/identity/principal.mjs +28 -0
- package/modules/operator-auth/index.d.mts +223 -0
- package/modules/operator-auth/index.mjs +11 -0
- package/modules/operator-auth/input.mjs +55 -0
- package/modules/operator-auth/local-cli.mjs +16 -0
- package/modules/operator-auth/page.mjs +34 -0
- package/modules/operator-auth/policy.mjs +20 -0
- package/modules/operator-auth/routes.mjs +91 -0
- package/modules/operator-auth/service.mjs +152 -0
- package/modules/operator-auth/signin/index.mjs +67 -0
- package/modules/owner-claim/README.md +137 -0
- package/modules/owner-claim/idp.mjs +40 -0
- package/modules/owner-claim/index.d.mts +27 -0
- package/modules/owner-claim/index.mjs +39 -0
- package/modules/owner-claim/page.mjs +11 -0
- package/modules/owner-claim/schema/idp.sql +4 -0
- package/modules/owner-claim/schema/owner-claim.sql +18 -0
- package/modules/owner-claim/transaction.mjs +87 -0
- package/modules/owner-claim/verify.mjs +80 -0
- package/modules/portability/README.md +444 -0
- package/modules/portability/bridge.mjs +102 -0
- package/modules/portability/catalogue.mjs +118 -0
- package/modules/portability/cli-options.mjs +38 -0
- package/modules/portability/cli.mjs +56 -0
- package/modules/portability/copies.mjs +68 -0
- package/modules/portability/dev-data.mjs +32 -0
- package/modules/portability/diff.mjs +33 -0
- package/modules/portability/export.mjs +129 -0
- package/modules/portability/files.mjs +65 -0
- package/modules/portability/format.mjs +122 -0
- package/modules/portability/generated.mjs +60 -0
- package/modules/portability/hash.mjs +79 -0
- package/modules/portability/http.mjs +42 -0
- package/modules/portability/import.mjs +185 -0
- package/modules/portability/index.d.mts +58 -0
- package/modules/portability/index.mjs +22 -0
- package/modules/portability/journal.mjs +49 -0
- package/modules/portability/legacy-files.mjs +43 -0
- package/modules/portability/legacy-safety.mjs +27 -0
- package/modules/portability/lines.mjs +36 -0
- package/modules/portability/migration-history.mjs +18 -0
- package/modules/portability/migration-statements.mjs +23 -0
- package/modules/portability/payloads.mjs +52 -0
- package/modules/portability/provider-data.mjs +25 -0
- package/modules/portability/restore-safety.mjs +30 -0
- package/modules/portability/schema/portability.sql +25 -0
- package/modules/portability/storage.mjs +49 -0
- package/modules/release/README.md +212 -0
- package/modules/release/catalogue.mjs +79 -0
- package/modules/release/export.mjs +51 -0
- package/modules/release/produce.mjs +136 -0
- package/modules/release/public-child-transport-state.cjs +29 -0
- package/modules/release/public-child.mjs +71 -0
- package/modules/release/public-git.mjs +21 -0
- package/modules/release/publication-scan.mjs +22 -0
- package/modules/release/publish-handoff.mjs +37 -0
- package/modules/release/source.mjs +71 -0
- package/modules/release/test-summary.mjs +50 -0
- package/onboarding/README.md +163 -0
- package/onboarding/activity.mjs +31 -0
- package/onboarding/adapter-auth.mjs +88 -0
- package/onboarding/adapter-hosting.mjs +35 -0
- package/onboarding/adapter-labels.mjs +13 -0
- package/onboarding/adapter-settings.mjs +129 -0
- package/onboarding/adapter.mjs +238 -0
- package/onboarding/agent-handoff.mjs +9 -0
- package/onboarding/app-processes.mjs +21 -0
- package/onboarding/archive-output.d.mts +3 -0
- package/onboarding/archive-output.mjs +54 -0
- package/onboarding/command-custody.mjs +24 -0
- package/onboarding/command-recovery.mjs +8 -0
- package/onboarding/commands.mjs +166 -0
- package/onboarding/dependency-approval.mjs +36 -0
- package/onboarding/dev-identity.mjs +23 -0
- package/onboarding/email.mjs +24 -0
- package/onboarding/failures.mjs +42 -0
- package/onboarding/input-limits.mjs +7 -0
- package/onboarding/installer-inputs.mjs +24 -0
- package/onboarding/launcher.mjs +34 -0
- package/onboarding/local/build-files.mjs +64 -0
- package/onboarding/local/build.mjs +49 -0
- package/onboarding/local/environment.mjs +30 -0
- package/onboarding/local/files.mjs +35 -0
- package/onboarding/local/operation.mjs +166 -0
- package/onboarding/local/owner.mjs +34 -0
- package/onboarding/local/process-table.mjs +114 -0
- package/onboarding/local/recovery.mjs +79 -0
- package/onboarding/local/state.mjs +31 -0
- package/onboarding/metadata.mjs +7 -0
- package/onboarding/operator-install.mjs +45 -0
- package/onboarding/operator-installer.mjs +39 -0
- package/onboarding/operator-prerequisites.mjs +27 -0
- package/onboarding/package.json +6 -0
- package/onboarding/prefill.mjs +16 -0
- package/onboarding/private-files.mjs +97 -0
- package/onboarding/private.mjs +35 -0
- package/onboarding/process-cleanup.mjs +134 -0
- package/onboarding/process-diagnostics.mjs +91 -0
- package/onboarding/process-scope.mjs +118 -0
- package/onboarding/process-table.mjs +118 -0
- package/onboarding/process-tree.mjs +36 -0
- package/onboarding/production-modules.mjs +22 -0
- package/onboarding/production-options.mjs +40 -0
- package/onboarding/production-plan.mjs +8 -0
- package/onboarding/production-runtime.mjs +30 -0
- package/onboarding/project-vite.mjs +14 -0
- package/onboarding/providers.mjs +289 -0
- package/onboarding/queue.mjs +53 -0
- package/onboarding/queued-entry.mjs +34 -0
- package/onboarding/readiness.mjs +59 -0
- package/onboarding/redaction-jwt.mjs +32 -0
- package/onboarding/redaction.mjs +298 -0
- package/onboarding/schema/SETUP.schema.json +514 -0
- package/onboarding/schema/dependencies.mjs +35 -0
- package/onboarding/schema/json-schema.mjs +202 -0
- package/onboarding/schema/setup-validation.mjs +325 -0
- package/onboarding/secrets.mjs +21 -0
- package/onboarding/server.mjs +188 -0
- package/onboarding/setup-manifest.mjs +116 -0
- package/onboarding/state.mjs +46 -0
- package/onboarding/theme-files.mjs +37 -0
- package/onboarding/theme.mjs +118 -0
- package/onboarding/ui/activity.js +98 -0
- package/onboarding/ui/address-client.mjs +33 -0
- package/onboarding/ui/app.js +377 -0
- package/onboarding/ui/index.html +1 -0
- package/onboarding/ui/model-core.mjs +127 -0
- package/onboarding/ui/model.mjs +5 -0
- package/onboarding/ui/style.css +63 -0
- package/onboarding/ui/theme.css +37 -0
- package/onboarding/ui/workspace-art.js +30 -0
- package/onboarding/wrangler-auth.mjs +43 -0
- package/operations/README.md +246 -0
- package/operations/operations.mjs +168 -0
- package/operations/project-operations.mjs +157 -0
- package/package.json +11 -4
- package/scripts/publish-check/README.md +72 -0
- package/scripts/publish-check/checksum_maps.py +52 -0
- package/scripts/publish-check/cli.py +34 -0
- package/scripts/publish-check/command_arguments.py +51 -0
- package/scripts/publish-check/command_shell_quotes.py +41 -0
- package/scripts/publish-check/command_source_quotes.py +120 -0
- package/scripts/publish-check/content_identities.py +250 -0
- package/scripts/publish-check/copy_identities.py +41 -0
- package/scripts/publish-check/der.py +61 -0
- package/scripts/publish-check/destination.py +117 -0
- package/scripts/publish-check/env_values.py +74 -0
- package/scripts/publish-check/font_licence.py +23 -0
- package/scripts/publish-check/git_history.py +115 -0
- package/scripts/publish-check/github_refs.py +217 -0
- package/scripts/publish-check/gitleaks.py +72 -0
- package/scripts/publish-check/gitleaks.toml +40 -0
- package/scripts/publish-check/history_scan.py +176 -0
- package/scripts/publish-check/hit_location.py +18 -0
- package/scripts/publish-check/hygiene_config.py +42 -0
- package/scripts/publish-check/hygiene_text.py +11 -0
- package/scripts/publish-check/identity-recipe.mjs +54 -0
- package/scripts/publish-check/json_identity.py +79 -0
- package/scripts/publish-check/license-exceptions.json +11 -0
- package/scripts/publish-check/licenses.py +49 -0
- package/scripts/publish-check/policy.py +18 -0
- package/scripts/publish-check/project_identity.py +89 -0
- package/scripts/publish-check/provenance.py +91 -0
- package/scripts/publish-check/provenance_pins.py +33 -0
- package/scripts/publish-check/public_base.py +63 -0
- package/scripts/publish-check/public_hygiene.py +80 -0
- package/scripts/publish-check/publication-config.json +12 -0
- package/scripts/publish-check/publication_config.py +34 -0
- package/scripts/publish-check/report.py +52 -0
- package/scripts/publish-check/repository_commands.py +75 -0
- package/scripts/publish-check/repository_intent.py +52 -0
- package/scripts/publish-check/reviewed.py +41 -0
- package/scripts/publish-check/source_hashes.mjs +6 -0
- package/scripts/publish-check/tree_scan.py +88 -0
- package/scripts/publish-check/trust_bundle.py +87 -0
- package/scripts/publish-check.mjs +58 -0
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
# Operator auth (IF-055-CORE-v1)
|
|
2
|
+
|
|
3
|
+
Portable server-only ESM: import `createOperatorAuth`, `createAuthHandler` and
|
|
4
|
+
`createSignInHandler` from `modules/operator-auth/index.mjs`. TypeScript
|
|
5
|
+
declarations are `index.d.mts`. No framework or mail transport is bundled. Apply
|
|
6
|
+
`accounts/schema/operator-auth.sql` once for a fresh auth database (and
|
|
7
|
+
`identity/oidc/schema/oidc.sql` when the app offers identity providers). Preserve applied migration history in an existing
|
|
8
|
+
store and follow “Adopting this version” below for its additive upgrade. Tables
|
|
9
|
+
use the `oa_` namespace. This module does not seed any product data.
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
import { createOperatorAuth, createAuthHandler } from './modules/operator-auth/index.mjs';
|
|
13
|
+
import { createMailSender } from './modules/mail/index.mjs';
|
|
14
|
+
// Secrets are Worker secret bindings, never source text.
|
|
15
|
+
const { SESSION_SECRET, SETUP_TOKEN, MAIL_API_KEY: apiKey } = env;
|
|
16
|
+
const auth = createOperatorAuth({
|
|
17
|
+
db: env.DB,
|
|
18
|
+
config: {
|
|
19
|
+
origin: 'https://app.example.com', OWNER_EMAIL: env.OWNER_EMAIL,
|
|
20
|
+
SESSION_SECRET, SETUP_TOKEN,
|
|
21
|
+
MAIL_FROM: env.MAIL_FROM,
|
|
22
|
+
// Optional; omit for neutral workspace wording and /signin recovery.
|
|
23
|
+
branding: { productName: 'Example', accentColor: '#334155', signInPath: '/signin' },
|
|
24
|
+
// Optional explicit address list further restricts active operators.
|
|
25
|
+
// allowList: ['owner@example.com', 'staff@example.com'],
|
|
26
|
+
},
|
|
27
|
+
mailSender: createMailSender({ provider: env.MAIL_PROVIDER, mode: 'production', apiKey,
|
|
28
|
+
...(env.MAIL_PROVIDER === 'cloudflare' ? { accountId: env.MAIL_ACCOUNT_ID } : {}) }),
|
|
29
|
+
// Use only infrastructure-authenticated client addresses. In Cloudflare's
|
|
30
|
+
// public Worker boundary CF-Connecting-IP is edge-owned. Do not reuse this
|
|
31
|
+
// resolver behind a proxy which lets callers supply that header.
|
|
32
|
+
clientIp: request => request.headers.get('CF-Connecting-IP') ?? '',
|
|
33
|
+
});
|
|
34
|
+
const response = await createAuthHandler(auth)(request);
|
|
35
|
+
// response === null means another dispatcher owns this path.
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
For first setup omit `mailSender` and `MAIL_FROM` entirely. Provision independent
|
|
39
|
+
random `SESSION_SECRET` and `SETUP_TOKEN` secrets (at least 32 characters; generate
|
|
40
|
+
32 random bytes or more). POST `/auth/bootstrap` with `{token,displayName,returnTo}`
|
|
41
|
+
creates the configured owner and session atomically only on an empty auth store.
|
|
42
|
+
Remove SETUP_TOKEN after bootstrap. Changing it never resets the bootstrap ledger.
|
|
43
|
+
No owner is automatically provisioned when requesting a link or invoking OIDC.
|
|
44
|
+
|
|
45
|
+
All public mutation APIs require POST, the exact configured request origin and
|
|
46
|
+
`Origin` header, and reject cross-site Fetch Metadata. Never derive `origin` from
|
|
47
|
+
request headers. `Host`, when supplied, must match the configured authority; local
|
|
48
|
+
HTTP additionally requires an explicit matching loopback Host. Browser forms and
|
|
49
|
+
JSON are accepted by the handler (8 KiB maximum). Parsed query/form parameters
|
|
50
|
+
with duplicate decoded names or JSON object keys are rejected with `invalid_input` before
|
|
51
|
+
auth processing, including escaped names. POST tokens are accepted only in the
|
|
52
|
+
body; a query token on a POST is rejected as an ambiguous credential source.
|
|
53
|
+
Protect every private API, server function and document with
|
|
54
|
+
`auth.getSession(request)`. Missing/invalid,
|
|
55
|
+
expired, revoked, deactivated and disallowed sessions yield null; host violations
|
|
56
|
+
throw `AuthError` with 403. Never return a private page then check in the browser.
|
|
57
|
+
|
|
58
|
+
## Layers
|
|
59
|
+
|
|
60
|
+
Base's operator auth is the only staff sign-in, in three layers:
|
|
61
|
+
|
|
62
|
+
| Directory | Holds | Interface |
|
|
63
|
+
| --- | --- | --- |
|
|
64
|
+
| `identity/` | Proving who a person is: the email link (`identity/email-link/`), identity providers over OpenID Connect (`identity/oidc/`, see [its README](identity/oidc/README.md)), and the admission that turns a verified principal into a session (`identity/admission.mjs`). | Outputs a **verified principal** `{issuer, subject, provider?, email?, emailVerified?, emailVerifiedAt?, amr?}`; `identity/index.mjs`. |
|
|
65
|
+
| `accounts/` | Who a person is in this app and what they may do: operators, roles, invitations, sessions, domain rules and step-up proofs in the `oa_` store (`accounts/store.mjs`, `accounts/schema/`). | The **accounts adapter interface** (`accounts/adapter.mjs`); Base's implementation over `oa_` is `accounts/oa.mjs`. |
|
|
66
|
+
| `dev/` | Local-only dev sessions, personas and the badge, loaded only from a local-only Worker entry. | `dev/index.mjs`; see "Local dev identity" below. |
|
|
67
|
+
|
|
68
|
+
The files at the root are shared by all three: HTTP and input checks, crypto,
|
|
69
|
+
configuration and branding, the shared page frame (`page.mjs`), the key gate
|
|
70
|
+
(`dev-gate.mjs`, `dev-admission.mjs`, which production loads to *refuse* dev
|
|
71
|
+
credentials), the composition (`service.mjs`), the HTTP adapter (`routes.mjs`)
|
|
72
|
+
and the sign-in page (`signin/`).
|
|
73
|
+
|
|
74
|
+
### Native composition
|
|
75
|
+
|
|
76
|
+
`createOperatorAuth({ accounts, proofs, ... })` routes the complete service
|
|
77
|
+
through the seed's native accounts, cookies, sessions and durable email/step-up
|
|
78
|
+
proof persistence. Both are required; partial injection fails closed. Native
|
|
79
|
+
roles are generic types, and unsupported administration/provisioning explicitly
|
|
80
|
+
refuses. Apply identity/dependency storage only, without OA account tables.
|
|
81
|
+
See the [native adoption contract](docs/native-accounts.md) for exact APIs,
|
|
82
|
+
transaction fences, optional capabilities, callback/trust settings and the
|
|
83
|
+
Slate/HappyToHelp continuation steps.
|
|
84
|
+
|
|
85
|
+
### The accounts adapter interface
|
|
86
|
+
|
|
87
|
+
Identity never decides who may enter; it asks the app's accounts. A seed that
|
|
88
|
+
keeps its own tables (HappyToHelp, Slate) implements these methods over them and
|
|
89
|
+
passes the adapter to `createAdmission` from `identity/index.mjs`:
|
|
90
|
+
|
|
91
|
+
| Method | Contract |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| `findByEmail(email)` | The person whose account holds this **proven** email, or null. A person is `{id, email, status: 'active'\|'invited'\|'inactive', owner: boolean}`. |
|
|
94
|
+
| `findBinding({issuer, subject})` | The person bound to this provider principal, or null. |
|
|
95
|
+
| `bind({issuer, subject, person, associated})` | Binds `(iss, sub)` to the person and returns whether it now names them. Never rebinds a principal bound to someone else; binds only proven emails (or an explicit configured association, `associated: true`); revokes the person's earlier password credentials. |
|
|
96
|
+
| `issueSession({person, principal, method, returnTo, request})` | A completed session `{session, operator, cookie, returnTo}`, or null when the person changed meanwhile. |
|
|
97
|
+
| `revokeSession(sessionId)` | Ends one session (used when a seed's `afterConfirm` refuses a sign-in). |
|
|
98
|
+
| `identityEnabled()` *(optional)* | The app-owned provider sign-in setting; omission means off. |
|
|
99
|
+
| `currentBoundSubject({personId,issuer})` *(optional)* | The current bound subject for this person at this issuer; null for ambiguity. Required for OIDC editor authority. |
|
|
100
|
+
| `bindAndIssueSession(...)` *(optional)* | Atomically bind and issue the session with current person and provider-setting fences; Base uses this path. |
|
|
101
|
+
| `owner()` | The app's owner, or null. |
|
|
102
|
+
| `acceptInvitation(...)` *(optional)* | Accepts a pending invitation through a principal that proves the invited email. |
|
|
103
|
+
| `requiredProvider(email)` *(optional)* | The provider id the email's domain must use, or null. |
|
|
104
|
+
|
|
105
|
+
`accounts/oa.mjs` is Base's own implementation: bindings live in
|
|
106
|
+
`oa_external_identities`, sessions in `oa_sessions` (fenced to the operator's
|
|
107
|
+
version), domain rules in `oa_domain_rules`. `auth.accounts` exposes it and
|
|
108
|
+
`auth.admission` the admission built on it.
|
|
109
|
+
|
|
110
|
+
## HTTP contract
|
|
111
|
+
|
|
112
|
+
| Path | Method | Input / result |
|
|
113
|
+
| --- | --- | --- |
|
|
114
|
+
| `/auth/bootstrap` | POST | token, displayName, returnTo?; 303 with cookie and safe Location |
|
|
115
|
+
| `/auth/magic-link` | POST | email, returnTo?; 202 `{status:'accepted', requiredProvider?}` for admitted/unknown/disallowed address. A native HTML form post (URL-encoded) gets a 303 back to the sign-in page with `notice=sent` instead. |
|
|
116
|
+
| `/auth/confirm?token=…` | GET/HEAD | 200 confirmation form, or 400 expired/invalid; **no writes, no session cookie** |
|
|
117
|
+
| `/auth/confirm` | POST | token; consume once, create session and 303 to stored returnTo. A step-up link completes only in the session that asked for it and sets no new cookie. |
|
|
118
|
+
| `/auth/session` | GET | session and operator, or 401 |
|
|
119
|
+
| `/auth/signout` | POST | `{}`; revoke current session, clear cookie, 204 |
|
|
120
|
+
| `/auth/invite` | POST | email, displayName, role? (member), returnTo?; mail invitation, 202 |
|
|
121
|
+
| `/auth/invite/cancel` | POST | operatorId; cancel pending invitation, return inactive operator, 200 |
|
|
122
|
+
| `/auth/operators` | GET | authorized operator list |
|
|
123
|
+
| `/auth/operator/status` | POST | operatorId, status (`active`/`inactive`) |
|
|
124
|
+
| `/auth/operator/role` | POST | operatorId, role |
|
|
125
|
+
| `/auth/sessions?operatorId=…` | GET | authorized session metadata, no credentials |
|
|
126
|
+
| `/auth/session/revoke` | POST | sessionId; 204 |
|
|
127
|
+
| `/auth/review-session` | POST | returnTo?; explicitly configured loopback review session |
|
|
128
|
+
| `/auth/step-up` | POST | action, returnTo?; email a step-up link to the signed-in operator, 202 (a form post gets 303 with `notice=step_up_sent`) |
|
|
129
|
+
| `/auth/domain-rules` | GET / POST | list rules; POST domain, provider (null lifts); owner only, step-up |
|
|
130
|
+
| `/auth/providers` | GET | the pinned provider links; owner only |
|
|
131
|
+
| `/auth/providers/unlink`, `/auth/providers/relink` | POST | provider; owner only, step-up |
|
|
132
|
+
| `/auth/oidc/<id>/start`, `/auth/oidc/<id>/callback` | POST / GET | each provider's own routes (see [identity/oidc](identity/oidc/README.md)) |
|
|
133
|
+
|
|
134
|
+
Unknown `/auth/*` paths return 404. Unsupported methods return 405. Except for invalid/expired confirmation GET/HEAD documents, known public
|
|
135
|
+
errors are JSON `{error:code}`: invalid input 400, unauthenticated 401, policy/host/
|
|
136
|
+
Origin rejection 403, missing resource 404, conflict/last owner 409, oversized input
|
|
137
|
+
413, unsupported content type 415, rate limit 429, missing mail/database 503.
|
|
138
|
+
Internal errors are redacted as `auth_unavailable`. Methods throw typed AuthError;
|
|
139
|
+
the Fetch adapter translates it. Every response has no-store headers. Successful
|
|
140
|
+
confirmation GET/HEAD responses use `Referrer-Policy: strict-origin` so native
|
|
141
|
+
form POSTs retain their Origin without exposing the token path or query in
|
|
142
|
+
Referer; HTTPS-to-HTTP downgrade requests omit Referer. Other responses retain
|
|
143
|
+
`Referrer-Policy: no-referrer`. Exact Origin/CSRF validation is unchanged.
|
|
144
|
+
Do not log confirmation URLs, input tokens, cookies or provider message bodies.
|
|
145
|
+
|
|
146
|
+
## Confirmation and branding
|
|
147
|
+
|
|
148
|
+
GET/HEAD confirmation uses a self-contained, responsive document. A magic link
|
|
149
|
+
says “Confirm sign-in to Example”; an invitation says “Accept your invitation to
|
|
150
|
+
Example”. Both explain that the link works once and show its remaining lifetime,
|
|
151
|
+
rounded up to minutes (singular for one minute). The sole primary form button is
|
|
152
|
+
“Confirm”. Only its hidden POST field contains the token. Invalid, expired,
|
|
153
|
+
consumed or unauthorized links return a styled 400 document with “Request a new
|
|
154
|
+
link” pointing to the configured sign-in page. HEAD has the same status and
|
|
155
|
+
headers without a body. Preview never writes auth state or sets a cookie.
|
|
156
|
+
|
|
157
|
+
The heading uses 30px type (28px at viewport widths up to 480px), 1.3 line
|
|
158
|
+
height, -.02em tracking and a 24px bottom margin; the brand-to-heading gap is
|
|
159
|
+
32px. Confirmation documents contain one inline stylesheet, and the CSP adds
|
|
160
|
+
only that stylesheet's exact UTF-8 SHA-256 hash.
|
|
161
|
+
|
|
162
|
+
Optional `config.branding` fields are independently defaulted and validated:
|
|
163
|
+
|
|
164
|
+
| Field | Default | Validation |
|
|
165
|
+
| --- | --- | --- |
|
|
166
|
+
| `productName` | `your workspace` | At most 80 UTF-16 code units before trimming, with at least one visible letter, number, punctuation or symbol; rejects C0/C1 controls, U+2028/U+2029 and default-ignorable code points, except contextual script joiners and emoji presentation described below |
|
|
167
|
+
| `accentColor` | `#334155` | Exactly six hexadecimal digits after `#` |
|
|
168
|
+
| `signInPath` | `/signin` | Same-origin absolute path, at most 2048 characters; no scheme, network-path reference, backslash or control characters, including encoded variants |
|
|
169
|
+
|
|
170
|
+
Product names preserve letters and combining marks in every script. ZWNJ
|
|
171
|
+
(U+200C) is allowed after a letter (including its combining marks) when a letter
|
|
172
|
+
or decimal digit follows, directly or after spaces, as in Persian "کتاب ها" and
|
|
173
|
+
"سال۱۴۰۰". ZWJ (U+200D) is allowed between letters and after a virama, including
|
|
174
|
+
at the end of a word, so legacy Malayalam chillu forms are kept. ZWJ also joins
|
|
175
|
+
emoji pictographs, including an emoji presentation selector or skin-tone modifier
|
|
176
|
+
on the preceding emoji. U+FE0F is allowed immediately after an emoji pictograph,
|
|
177
|
+
and after a keycap base (0–9, # or *) only when U+20E3 follows; all other
|
|
178
|
+
variation selectors are rejected. Rejected default-ignorable characters include
|
|
179
|
+
U+200B/U+200E/U+200F, bidi overrides and isolates, U+2060–U+206F, U+FEFF, U+061C,
|
|
180
|
+
U+180E, U+034F, U+115F/U+1160, U+3164 and U+FFA0. The object replacement
|
|
181
|
+
character U+FFFC and lone surrogates (text that is not well formed) are rejected.
|
|
182
|
+
Space-only and invisible-only names are invalid; the braille blank U+2800 does not
|
|
183
|
+
count as a visible symbol.
|
|
184
|
+
|
|
185
|
+
The consumer must serve that sign-in path. Query strings and fragments are
|
|
186
|
+
allowed; malformed encodings in the configured value are rejected. Encoded
|
|
187
|
+
percent data is preserved, and nested encoded separators and controls are still
|
|
188
|
+
rejected. Invalid branding throws
|
|
189
|
+
`AuthError('invalid_configuration', 503)`. Branding is copied and frozen at
|
|
190
|
+
construction. `auth.presentation` exposes only the three normalized branding
|
|
191
|
+
fields; it never exposes configuration secrets. `previewLink` returns only
|
|
192
|
+
`{kind, expiresInMinutes}` for an authorized link, otherwise null. Names are
|
|
193
|
+
HTML-escaped in documents and mail. The accent chooses black or white button
|
|
194
|
+
text for contrast, with a visible keyboard focus ring in light and dark schemes.
|
|
195
|
+
|
|
196
|
+
The module's confirmation HTML responses contain exactly one inline stylesheet. Their
|
|
197
|
+
CSP adds only its exact UTF-8 SHA-256 `style-src` hash to the existing restrictive
|
|
198
|
+
policy. No scripts, external resources, style attributes, CSS URLs or imports
|
|
199
|
+
are included in the document. JSON errors, redirects and other responses retain
|
|
200
|
+
the original CSP. Do not add a broad inline-style allowance to an adopting seed.
|
|
201
|
+
|
|
202
|
+
An application can compose its same-origin brand font head onto this document,
|
|
203
|
+
as Base's foundation/reference do. Preserve the inline style hash, private
|
|
204
|
+
headers and native form behavior; permit only same-origin styles and fonts,
|
|
205
|
+
with no scripts or third-party resources. Set the conformance adapter's
|
|
206
|
+
`confirmationFonts: true` for this composition. The same public auth scenarios
|
|
207
|
+
then require one fixed same-origin stylesheet and primary font preload, the
|
|
208
|
+
unchanged hash and restrictive CSP, and all existing token/session protections.
|
|
209
|
+
The standalone module's default conformance still requires no font resources.
|
|
210
|
+
|
|
211
|
+
## Sessions, tokens and policies
|
|
212
|
+
|
|
213
|
+
Opaque 256-bit credentials are HMAC-SHA256 hashed with domain separation at rest.
|
|
214
|
+
Cookies are host-only, Path=/, HttpOnly, SameSite=Lax; production uses Secure and
|
|
215
|
+
`__Host-operator_session`. Explicit `local:true` allows only literal localhost,
|
|
216
|
+
127.0.0.1 or [::1] and uses `operator_session_local_<port>` (for example
|
|
217
|
+
`operator_session_local_8787`): browsers keep cookies per host, not per port, so
|
|
218
|
+
checkouts side by side on one loopback host each keep their own session. Session expiry defaults to
|
|
219
|
+
7 days (maximum 30), links to 15 minutes (maximum 60); no silent sliding renewal.
|
|
220
|
+
`returnTo` must be a local absolute path; unsafe values become `/app`. It is stored
|
|
221
|
+
with the token, and cannot be replaced on confirmation. Auth methods never return
|
|
222
|
+
raw link tokens. `CompletedSession.cookie` is server-only and must only be emitted
|
|
223
|
+
as Set-Cookie, never serialized into browser data.
|
|
224
|
+
|
|
225
|
+
D1 batches transactionally claim setup, consume links, accept invitations, bind
|
|
226
|
+
external identities and issue sessions. A unique token/session relation prevents
|
|
227
|
+
double consumption. Operator version fences invalidate **all** old links and
|
|
228
|
+
sessions after status/role changes, including after reactivation. Mutation SQL
|
|
229
|
+
rechecks the actor's active session and authority snapshot. Last active owner
|
|
230
|
+
removal/demotion is rejected by SQL triggers, including competing mutations.
|
|
231
|
+
Reactivate never bypasses invitation acceptance. Retrying an invitation with the
|
|
232
|
+
same email/role resends it and revokes older links; an existing active or
|
|
233
|
+
ordinarily inactive operator conflicts. An inactive operator with a durable
|
|
234
|
+
cancellation marker can be reinvited as described below. Unknown addresses never
|
|
235
|
+
become operators through sign-in.
|
|
236
|
+
|
|
237
|
+
`cancelInvitation(request, operatorId)` uses the existing `operators:deactivate`
|
|
238
|
+
policy with the pending operator as target. It changes only an `invited` operator
|
|
239
|
+
to `inactive`, retaining the operator ID, bootstrap ledger and history. The SQL
|
|
240
|
+
write fences the actor's session and authority version and the target's status
|
|
241
|
+
and version. Its existing version trigger invalidates every outstanding link;
|
|
242
|
+
GET/HEAD preview and POST confirmation then fail. A durable cancellation ledger
|
|
243
|
+
and SQL trigger reject ordinary activation with `invitation_required`, even after
|
|
244
|
+
credentials are pruned. A cancelled identity can receive a fresh invitation only
|
|
245
|
+
when the requested role matches its recorded role. Reissue changes that same row
|
|
246
|
+
back to `invited`, updates its display name, and leaves its ID and creation/history
|
|
247
|
+
intact. Its status trigger advances the version; the fenced D1 batch inserts a new
|
|
248
|
+
link and revokes older unconsumed links. The cancellation ledger remains present
|
|
249
|
+
while the replacement is pending. Cancelling that pending invitation again is
|
|
250
|
+
allowed and refreshes the durable marker, so repeated cancel/reissue cycles retain
|
|
251
|
+
the same guard. Existing active and ordinarily deactivated operators still conflict,
|
|
252
|
+
as do role mismatches; a same-role pending invitation can still be resent with
|
|
253
|
+
status 202. A completed invitation cannot be cancelled.
|
|
254
|
+
|
|
255
|
+
The cancellation ledger is removed only inside the successful token-consume batch,
|
|
256
|
+
witnessed by the session inserted from that invitation immediately before the
|
|
257
|
+
operator is activated. Session creation, ledger removal, activation and token
|
|
258
|
+
consumption commit atomically. An ordinary status update cannot supply that
|
|
259
|
+
acceptance evidence. A confirmation racing with cancellation has one winner: a
|
|
260
|
+
completed join makes cancellation conflict; a completed cancellation prevents the
|
|
261
|
+
join and session creation. Use `setOperatorStatus` for explicit active-operator
|
|
262
|
+
removal; its last-owner protection remains in force.
|
|
263
|
+
|
|
264
|
+
The full `accounts/schema/operator-auth.sql` includes the cancellation ledger and triggers
|
|
265
|
+
for a fresh database. In an existing store that lacks these objects, apply
|
|
266
|
+
`accounts/schema/invitation-cancellation.sql` as a **new numbered migration before switching
|
|
267
|
+
to this runtime**: the runtime reads the ledger for reinvitation and consumes it
|
|
268
|
+
on confirmation. Inspect the seed's migration history and assign its next unused
|
|
269
|
+
number; never rewrite an applied migration. Do not apply both files to a fresh
|
|
270
|
+
store. Missing cancellation schema fails closed. The additive migration leaves
|
|
271
|
+
existing rows and credentials unchanged and does not infer historical acceptance
|
|
272
|
+
for existing active or inactive operators; only a witnessed pending cancellation
|
|
273
|
+
writes a ledger row. Keep the ledger and triggers on rollback, and never prune
|
|
274
|
+
`oa_invitation_cancellations` as maintenance.
|
|
275
|
+
|
|
276
|
+
Invitations also bind their issuer's identity and authority version. Preview and
|
|
277
|
+
confirmation require that issuer to remain active and allowed, and re-evaluate
|
|
278
|
+
the configured `operators:invite` policy for the issued role. Confirmation fences
|
|
279
|
+
that issuer/version in the same transaction as invitation acceptance and session
|
|
280
|
+
creation. Changing the issuer's status or role permanently invalidates unused
|
|
281
|
+
invitations, even after restoration; an authorized operator must issue a fresh
|
|
282
|
+
link. Signing out or revoking just the issuing browser session does not invalidate
|
|
283
|
+
an invitation whose issuer still has authority.
|
|
284
|
+
|
|
285
|
+
Durable fixed-window limits pair the client-IP resolver with a normalized email;
|
|
286
|
+
defaults are 30 per IP and 5 per email per 15 minutes. `limitWindowSeconds`,
|
|
287
|
+
`ipLimit` and `emailLimit` configure those defaults. Bootstrap, magic link,
|
|
288
|
+
invitation, confirmation and review use separate scopes. Confirmation limits bind
|
|
289
|
+
the presented opaque token instead of the address. `clientIp` is mandatory;
|
|
290
|
+
unavailable addresses fail closed (503). Header trust belongs to the deployment
|
|
291
|
+
boundary. Do not trust X-Forwarded-For.
|
|
292
|
+
Periodically prune old oa_limits rows and expired tokens/sessions in an
|
|
293
|
+
edition-owned maintenance job; preserve referenced rows or delete referencing
|
|
294
|
+
sessions first. Never delete oa_bootstrap as maintenance.
|
|
295
|
+
|
|
296
|
+
Default applied: **owner/admin/member**. Owner manages all operators; admin invites
|
|
297
|
+
members and manages members; members view/revoke their own sessions. Only owners
|
|
298
|
+
can promote roles. `rolePolicy({actor,action,target,role})` replaces this policy
|
|
299
|
+
for product variations. It receives current persisted operators, never caller
|
|
300
|
+
role claims; keep it deterministic and side-effect-free. Active operator, active
|
|
301
|
+
session, allow-list, version fences and last-owner safeguards cannot be overridden.
|
|
302
|
+
The exported `isActiveOperator` is the shared admission predicate.
|
|
303
|
+
|
|
304
|
+
## Mail, OIDC, password and review seams
|
|
305
|
+
|
|
306
|
+
Magic and invitation mail include both plain text and HTML. Each part has exactly
|
|
307
|
+
one confirmation URL; the HTML part presents it as a button with inline mail-safe
|
|
308
|
+
styling. The body sets Arial; its centered main content has a 480px maximum width
|
|
309
|
+
and 32px by 24px padding. The “Confirm” link opens a page; the copy says “Open this link to review and confirm”.
|
|
310
|
+
Both parts direct readers to press Confirm on that page and explain single use and configured expiry.
|
|
311
|
+
The invitation subject is “You're invited to Example”; no subject includes a
|
|
312
|
+
credential. The product name is escaped in HTML and is plain text in the subject
|
|
313
|
+
and text part. Delivery results still control revocation as described below.
|
|
314
|
+
|
|
315
|
+
Mail consumes actual IF-056-MAIL-v1 `createMailSender(config).send(message)`;
|
|
316
|
+
`deliveryId` is the token record UUID. No parallel transport or implicit outbox
|
|
317
|
+
exists. Missing/failing/uncertain transport revokes the link and reports
|
|
318
|
+
`mail_unavailable`; retries require a new link. Explicit development results are
|
|
319
|
+
accepted only in local mode. Auth never claims mailbox receipt.
|
|
320
|
+
|
|
321
|
+
Identity providers verify the whole protocol and hand the admission a verified
|
|
322
|
+
principal; [identity/oidc/README.md](identity/oidc/README.md) describes them. The
|
|
323
|
+
original server seams remain for existing callers:
|
|
324
|
+
`auth.admitVerifiedIdentity(request, identity, {returnTo})` requires
|
|
325
|
+
`{issuer,subject,email,emailVerified:true}` from an issuer in
|
|
326
|
+
`config.externalIssuers`, and `auth.createIdentityAdmission({issuer,
|
|
327
|
+
subjectEmails})` snapshots an exact issuer's subject associations:
|
|
328
|
+
|
|
329
|
+
```js
|
|
330
|
+
const admit = auth.createIdentityAdmission({
|
|
331
|
+
issuer: 'https://idp.example.com',
|
|
332
|
+
subjectEmails: { 'independently-verified-subject': 'operator@example.com' },
|
|
333
|
+
});
|
|
334
|
+
// Only after full protocol validation; the mapping supplies the account.
|
|
335
|
+
const result = await admit(request, { issuer, subject }, { returnTo });
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Both now admit through the accounts adapter, under the same rules as provider
|
|
339
|
+
sign-in. A binding on `(iss, sub)` is never silently rebound: a bound principal
|
|
340
|
+
always names the same account, and one presenting another existing account's
|
|
341
|
+
email is refused. A proven email binds a person's **first** principal at each
|
|
342
|
+
issuer; another subject at the same issuer needs an explicit association, while
|
|
343
|
+
a different issuer may bind its own principal by a proven email. Binding revokes
|
|
344
|
+
the person's password sessions, and `admitVerifiedPassword` then refuses with
|
|
345
|
+
`password_revoked`. An email counts only when proven in the last 30 days
|
|
346
|
+
(`emailVerifiedAt`, where sent). Identity providers also refuse, in a production
|
|
347
|
+
app (not `local`), `amr` containing `dev` and addresses under `.localhost`,
|
|
348
|
+
`.test` or `.invalid`.
|
|
349
|
+
A pending invitation is accepted when a principal proves the invited email.
|
|
350
|
+
Neither path provisions operators or changes roles. The subject map must come
|
|
351
|
+
from trusted deployment configuration, never HTTP input or token claims.
|
|
352
|
+
|
|
353
|
+
`admitVerifiedPassword(request, operatorId, {returnTo})` supports a seed's existing
|
|
354
|
+
real password verifier. It requires POST/Origin and shares active-operator,
|
|
355
|
+
allow-list, version, domain-rule and session policy; an operator bound to a
|
|
356
|
+
provider has no password admission. The caller must verify and rate-limit the
|
|
357
|
+
password **before** invoking it. It neither creates an operator nor claims setup;
|
|
358
|
+
there is no password checker or password endpoint in this module.
|
|
359
|
+
|
|
360
|
+
For a loopback review session, configure `review:{enabled:true,operatorEmail}` and invoke
|
|
361
|
+
`issueReviewSession`. It requires an already-active operator, POST/Origin, and
|
|
362
|
+
literal loopback URL **and Host**. Non-loopback Host returns 403 regardless of
|
|
363
|
+
flags or forwarded headers; admission of the resulting review cookie checks the
|
|
364
|
+
same boundary every time. Review defaults/maxes at one hour. Disable the flag to
|
|
365
|
+
invalidate review admission immediately. Bind the local review listener to a
|
|
366
|
+
loopback interface; a forged Host is not network isolation. Production configuration
|
|
367
|
+
must never enable review. No fixture/sample operator is created for review.
|
|
368
|
+
|
|
369
|
+
## The sign-in page
|
|
370
|
+
|
|
371
|
+
`createSignInHandler(auth, {setup, links, defaultReturnTo})` serves the one
|
|
372
|
+
sign-in page every Base seed mounts, at `branding.signInPath` (GET and HEAD; null
|
|
373
|
+
for other requests). It is server-rendered plain HTML with no script, so it works
|
|
374
|
+
before hydration and without JavaScript, in the same hashed-style frame as the
|
|
375
|
+
confirmation page (light and dark, the seed's `rer-theme.css` when declared).
|
|
376
|
+
It shows "Continue with…" for each linked, reachable provider, then the email
|
|
377
|
+
link, then optional seed links (the local outbox) and, while the first owner is
|
|
378
|
+
unclaimed, the setup form. A provider that has failed discovery or JWKS for 5
|
|
379
|
+
minutes, or answered 404 or a DNS failure, is hidden, and a browser that still
|
|
380
|
+
posts to it comes back to this page with a notice and the email link instead of
|
|
381
|
+
being redirected away. Its `form-action` allows exactly this origin plus each
|
|
382
|
+
visible provider's authorization origin, since the start route redirects there.
|
|
383
|
+
|
|
384
|
+
Two hooks on `createOperatorAuth({hooks})` keep a seed's own sign-in work in place:
|
|
385
|
+
|
|
386
|
+
- `beforeMagicLink({request, email, operator})` runs before an email link is sent
|
|
387
|
+
to an active operator; returning `false` sends nothing (the public answer is
|
|
388
|
+
unchanged).
|
|
389
|
+
- `afterConfirm({request, operator, session, method, returnTo})` runs after an
|
|
390
|
+
email-link or provider sign-in (`method: 'email' | 'oidc'`). Return
|
|
391
|
+
`{returnTo}` to redirect (Vector's restored-operator review), do other work
|
|
392
|
+
(Pencil's audit mail), or throw an `AuthError` to refuse: the session it was
|
|
393
|
+
about to get is revoked first (Cradle's forgery check).
|
|
394
|
+
|
|
395
|
+
## Domain rules and step-up
|
|
396
|
+
|
|
397
|
+
An owner can require a provider for an email domain (`POST /auth/domain-rules`
|
|
398
|
+
`{domain, provider}`; `provider: null` lifts it). Sign-ins for an address in that
|
|
399
|
+
domain by any other route are refused: the email link is not sent (the public
|
|
400
|
+
answer is the same, and names the domain's provider), another provider is
|
|
401
|
+
refused with `oidc_provider_required`, and the password and review seams refuse
|
|
402
|
+
with `provider_required`. App owners are exempt. Domains match exactly.
|
|
403
|
+
Unlinking a provider lifts the rules naming it.
|
|
404
|
+
|
|
405
|
+
An identity provider can assert any email, so these actions need a fresh
|
|
406
|
+
**app-local email-link proof**: unlinking or changing a provider, changing domain
|
|
407
|
+
rules, transferring ownership (making or unmaking an owner), export, delete and
|
|
408
|
+
minting long-lived credentials (`STEP_UP_ACTIONS`). `POST /auth/step-up
|
|
409
|
+
{action}` emails a link to the signed-in operator's own address; confirming it in
|
|
410
|
+
the same session records the proof, which lasts 10 minutes. An email-link
|
|
411
|
+
sign-in within those 10 minutes is itself a proof. A provider-issued session is
|
|
412
|
+
never one. Base's own routes enforce it; a seed guards its export, delete and
|
|
413
|
+
credential-minting routes with `await auth.requireStepUp(request, action)`,
|
|
414
|
+
which refuses with `step_up_required` (403).
|
|
415
|
+
|
|
416
|
+
The shared administrative actor guard applies that proof to every owner role
|
|
417
|
+
grant or removal, owner invitation (new, reissued or reinstated after cancellation),
|
|
418
|
+
owner invitation cancellation, and owner activation or deactivation. Unchanged
|
|
419
|
+
roles/statuses and non-owner status changes retain their usual authorization.
|
|
420
|
+
The existing version fences and last-active-owner protection still apply after
|
|
421
|
+
the proof succeeds.
|
|
422
|
+
Creating a server-declared owner dev persona also requires the signed-in owner's
|
|
423
|
+
fresh local proof in addition to the checkout key. Its creation repeats the actor
|
|
424
|
+
session/version fence in the database write.
|
|
425
|
+
|
|
426
|
+
## Local dev identity and the key gate
|
|
427
|
+
|
|
428
|
+
`dev-gate.mjs` (exported as `devGate`, production-safe) is the per-checkout key
|
|
429
|
+
gate. `rer setup` writes a random `RER_DEV_KEY` to `.rer/local/dev-key.env`;
|
|
430
|
+
only local runtimes receive it as a binding, and `rer deploy` refuses it, so a
|
|
431
|
+
deployed Worker has no gate and every gated surface answers 404. A tunnel or
|
|
432
|
+
local reverse proxy makes remote requests look local (forwarded requests carry
|
|
433
|
+
a loopback URL and Host, and `CF-Connecting-IP` is whatever the caller sent), so
|
|
434
|
+
the gate is the key, not the network. `devGate({key, origin, local})` returns
|
|
435
|
+
null unless the key is present and the origin is an explicit loopback origin in
|
|
436
|
+
local mode. `admits(request)` accepts the `rer_dev_gate_<port>` cookie (an HMAC of the
|
|
437
|
+
key, so a reset key refuses old cookies) or the key in `x-rer-dev-key` (the rer
|
|
438
|
+
CLI and test harnesses). `exchange(request)` serves `GET /__dev/key` and trades
|
|
439
|
+
the key from the URL fragment, through a same-origin POST, for a host-only
|
|
440
|
+
`HttpOnly; SameSite=Strict; Path=/` cookie. Seeds gate their development outbox
|
|
441
|
+
with it.
|
|
442
|
+
|
|
443
|
+
`dev/` is the dev layer: **server-only and local-only**. Import it only from a
|
|
444
|
+
local-only Worker entry (a Vite-serve or local `wrangler dev` entry), never
|
|
445
|
+
from the deployed main; its `DEV_LAYER_MARKER` lets `rer build` refuse any output
|
|
446
|
+
that bundles it. `withDevIdentity(worker, {bindings, config, personas})` wraps
|
|
447
|
+
the seed's Worker and serves, behind the gate only:
|
|
448
|
+
|
|
449
|
+
| Path | Result |
|
|
450
|
+
| --- | --- |
|
|
451
|
+
| `/auth/dev/enter?as=owner\|signed-out\|PERSONA&returnTo=` | Creates or reuses a real `oa_sessions` row of kind `dev` for the configured owner or persona and 303s to `returnTo` (default `/app`); `signed-out` revokes the current session. |
|
|
452
|
+
| `/__dev/personas` | `[{as,email,role?}]`, used by `rer dev` for `RER_SIGNED_IN`. |
|
|
453
|
+
|
|
454
|
+
`personas()` returns the seed's `devPersonas()`: `[{as, role, displayName?}]`,
|
|
455
|
+
real operators at `<as>@dev.localhost` created only by dev sign-in. Dev
|
|
456
|
+
sessions pass the same version fences, revocation, deactivation and allow-list
|
|
457
|
+
as every other session. The core admits a `dev` session, and any credential
|
|
458
|
+
minted while using one (`oa_dev_credentials`, today an invitation link, whose
|
|
459
|
+
confirmation then yields a `dev` session), only when the dev layer's
|
|
460
|
+
registered check passes for that request; a production Worker never registers
|
|
461
|
+
one. HTML documents gain a dev-only badge for switching identity; the root
|
|
462
|
+
document also gets the fragment exchange script. Both are added under the
|
|
463
|
+
document's own CSP by hash. With `RER_DEV_IDENTITY=off` (`rer dev --login`)
|
|
464
|
+
dev sign-in and personas answer 404 while the gate still opens the outbox. The
|
|
465
|
+
layer answers 503 to every request when `RER_INSTALLATION_ID` is set beside a
|
|
466
|
+
non-loopback `RER_MANAGEMENT_ORIGIN`.
|
|
467
|
+
|
|
468
|
+
`dev/sign-in-as.mjs` is the test harness door: `signInAs({origin, key, as})`
|
|
469
|
+
returns the cookies a test browser or fetch client installs, and
|
|
470
|
+
`readDevKey(root)` reads the checkout's key.
|
|
471
|
+
|
|
472
|
+
Portable archives never carry dev identity: export drops `dev` sessions and
|
|
473
|
+
dev-minted credentials and marks personas suspended, and import refuses an
|
|
474
|
+
archive holding either (`modules/portability/dev-data.mjs`). Seeds that mint
|
|
475
|
+
their own credentials while signed in record them in `oa_dev_credentials` and
|
|
476
|
+
admit them only behind the gate.
|
|
477
|
+
|
|
478
|
+
## Integration and checks
|
|
479
|
+
|
|
480
|
+
Copy this directory as source; import only its server entry from `.server` modules.
|
|
481
|
+
A Start server function must call getSession on each invocation. A Vite SPA must
|
|
482
|
+
use protected HTTP APIs and never bundle this implementation, secrets or D1. The
|
|
483
|
+
consumer owns its native unauthorized document redirect and API 401 behavior.
|
|
484
|
+
Mount `/auth/*` before private namespace guards and before Start; keep private
|
|
485
|
+
unknown API paths behind authentication before returning 404.
|
|
486
|
+
|
|
487
|
+
At the Base repository root, `pnpm run verify:operator-auth` runs the operator-auth,
|
|
488
|
+
identity-provider and sign-in page `.test.mjs` suites; `pnpm run verify:modules` runs the module `.test.ts`
|
|
489
|
+
suites. The default suite includes a static pre-ledger schema and populated-row
|
|
490
|
+
upgrade fixture, exercising current handlers and persistent D1 with a fixed test
|
|
491
|
+
clock; its provenance is documented in `tests/fixtures/pre-ledger/README.md`.
|
|
492
|
+
`tests/prior-data.test.mjs` runs a store written by the auth-1 main's own API
|
|
493
|
+
(`tests/fixtures/auth-1-store/README.md`) through these layers after the two
|
|
494
|
+
additive migrations below.
|
|
495
|
+
The "cancelled before the ledger" fixture row represents a state reachable only
|
|
496
|
+
by a direct database update, not through the cancellation API before its ledger
|
|
497
|
+
exists. The prior-data test executes every statement in the shipped migration
|
|
498
|
+
and checks that the applied count matches the file's statement count.
|
|
499
|
+
The pre-update source upgrade check is separate from those globs. Prepare
|
|
500
|
+
an independently captured pre-update source snapshot containing the complete
|
|
501
|
+
`operator-auth/` directory (including `tests/worker.mjs` and
|
|
502
|
+
`accounts/schema/invitation-cancellation.sql`) and `pre-cancellation.sql` at the snapshot
|
|
503
|
+
root. With the frozen workspace dependencies installed, run:
|
|
504
|
+
|
|
505
|
+
```sh
|
|
506
|
+
OA_BASELINE_SOURCE=/path/to/snapshot node --test modules/operator-auth/tests/reinvitation-upgrade.mjs
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
Runtime tests use the locked Wrangler Miniflare/esbuild dependencies and run the
|
|
510
|
+
bundled implementation in real workerd with D1. They create disposable test data and
|
|
511
|
+
dispose runtimes. Adversarial request tests use a test-only envelope to preserve
|
|
512
|
+
literal Host and Origin through Miniflare's local proxy; the production handler,
|
|
513
|
+
crypto and D1 execute in workerd. These are module-level checks, not proof of an
|
|
514
|
+
adopting seed's emitted browser transport or deployment. Each seed must wire its
|
|
515
|
+
corresponding auth and browser checks into CI. Hosted provider/IdP proof and
|
|
516
|
+
deployment remain separate.
|
|
517
|
+
|
|
518
|
+
## Adopting this version
|
|
519
|
+
|
|
520
|
+
Copy the **complete** `modules/operator-auth/` directory from one reviewed Base
|
|
521
|
+
revision into the seed's operator-auth source location. Keep its layers
|
|
522
|
+
(`identity/`, `accounts/`, `dev/`), runtime files, declarations, schema,
|
|
523
|
+
conformance files and tests from that same revision; `rer check` names any copied
|
|
524
|
+
file that differs from the pinned revision (`SC-05.auth-copy.*`); the runtime imports neighboring helpers within the module. Keep imports
|
|
525
|
+
server-only, reconcile the seed's route integration, and run that seed's real auth
|
|
526
|
+
and browser checks.
|
|
527
|
+
|
|
528
|
+
Wire `config.branding` with the seed's `productName`, `accentColor` and
|
|
529
|
+
`signInPath`; the defaults are `your workspace`, `#334155` and `/signin`. Keep
|
|
530
|
+
`MAIL_FROM` in auth config and provide a real `mailSender` that implements
|
|
531
|
+
`send(message)`. The Base example builds it with `createMailSender` from
|
|
532
|
+
[the mail module](../mail/README.md), using mail config fields `provider`, `mode`, `apiKey` and
|
|
533
|
+
Cloudflare-only `accountId`; its sample maps `provider` to `MAIL_PROVIDER`,
|
|
534
|
+
`apiKey` to `MAIL_API_KEY`, and Cloudflare `accountId` to `MAIL_ACCOUNT_ID`, with
|
|
535
|
+
`mode: 'production'`. `MAIL_FROM` is a separate auth config field. If sending is
|
|
536
|
+
intentionally unavailable for first setup, omit both `mailSender` and `MAIL_FROM`
|
|
537
|
+
until mail is configured.
|
|
538
|
+
|
|
539
|
+
For a fresh auth database, install `accounts/schema/operator-auth.sql` through the seed's
|
|
540
|
+
initial schema or migration process, once. In an existing store that has the
|
|
541
|
+
earlier operator-auth schema but lacks cancellation objects,
|
|
542
|
+
apply `accounts/schema/invitation-cancellation.sql` under the seed's next unused migration
|
|
543
|
+
number **before switching the runtime**. The consume path now reads and removes
|
|
544
|
+
the ledger during invitation acceptance, so the migration is a runtime prerequisite
|
|
545
|
+
for such stores. Never rewrite an applied migration, infer cancellation for old
|
|
546
|
+
inactive rows, or apply both schema files to a fresh store. The complete fresh
|
|
547
|
+
schema already includes the ledger. This update adds no schema objects beyond
|
|
548
|
+
that existing cancellation-capable schema.
|
|
549
|
+
|
|
550
|
+
The updated invite behavior permits same-role reissue of an inactive identity only
|
|
551
|
+
when it carries the durable cancellation marker. Same-role invitations that remain
|
|
552
|
+
pending, including expired pending invitations, retain the existing resend path.
|
|
553
|
+
Cancelled reissue preserves the identity row and history, creates a fresh invitation
|
|
554
|
+
link, invalidates older links and retains the marker until a successful confirmation
|
|
555
|
+
creates a session. Repeated cancellation/reissue is supported. Existing active or
|
|
556
|
+
ordinarily inactive identities and role mismatches still conflict; an ordinary
|
|
557
|
+
status change cannot accept a pending invitation.
|
|
558
|
+
Pre-ledger cancelled identities have no cancellation record and remain refused
|
|
559
|
+
for reinvitation until the owner deliberately removes the identity and reinvites
|
|
560
|
+
the same email; the current status API cannot distinguish them from ordinary
|
|
561
|
+
inactive identities.
|
|
562
|
+
|
|
563
|
+
**Dev sessions.** The fresh schema admits session kind `dev` and includes
|
|
564
|
+
`oa_dev_credentials`. An existing store needs `accounts/schema/dev-sessions.sql` as a
|
|
565
|
+
**new numbered migration under the seed's next unused number, before switching
|
|
566
|
+
to this runtime**: SQLite cannot change a CHECK in place, so it rebuilds
|
|
567
|
+
`oa_sessions` with `dev` added, keeping every row, column, index and
|
|
568
|
+
constraint (no table refers to `oa_sessions`), and creates
|
|
569
|
+
`oa_dev_credentials`. The runtime reads that table when previewing and
|
|
570
|
+
confirming links, so a store without it fails closed. Unlike the cancellation
|
|
571
|
+
migration, this file is also safe after the fresh schema. The reference seed
|
|
572
|
+
applies it as `migrations/core/0007_dev_sessions.sql`;
|
|
573
|
+
`tests/dev-sessions-upgrade.test.mjs` upgrades a populated store built from
|
|
574
|
+
the reference's earlier migrations and proves every stored session keeps its id
|
|
575
|
+
and expiry. Native, review and password sessions retain usable authority after
|
|
576
|
+
the later additive migrations. Legacy provider sessions require a new sign-in:
|
|
577
|
+
upgrades start with provider sign-in off, and enabling it does not manufacture
|
|
578
|
+
missing provider session provenance.
|
|
579
|
+
|
|
580
|
+
**Layers, domain rules and step-up.** An existing store needs
|
|
581
|
+
`accounts/schema/domain-rules-step-up.sql` (`oa_domain_rules`,
|
|
582
|
+
`oa_step_up_challenges`) and, when it offers providers or had the earlier OIDC
|
|
583
|
+
adapter, `identity/oidc/schema/oidc.sql` again (it adds `oa_identity_providers`
|
|
584
|
+
and `oa_identity_provider_health`; every statement is `IF NOT EXISTS`), each as
|
|
585
|
+
a new numbered migration before switching to this runtime. Both are additive and
|
|
586
|
+
safe after the fresh schema; the runtime reads them when previewing links and
|
|
587
|
+
requesting email links. The reference applies them as
|
|
588
|
+
`migrations/core/0008_identity_providers.sql` and
|
|
589
|
+
`0009_domain_rules_step_up.sql`. The earlier single-issuer `oidc/` adapter
|
|
590
|
+
(`createOidcClient`) is replaced by `config.providers`; its existing bindings keep
|
|
591
|
+
signing in once the same issuer is configured as a provider.
|
|
592
|
+
|
|
593
|
+
Seeds copy this module at different revisions, and an older copy lacks the
|
|
594
|
+
cancellation migration and the optional OIDC and conformance files. Check each
|
|
595
|
+
seed's present source and applied migrations before adopting. A seed owns its
|
|
596
|
+
route configuration, CI and release; Base checks do not establish integrated
|
|
597
|
+
adoption or deployment.
|
|
598
|
+
|
|
599
|
+
The application-side dependency, owner-setting and logout protocol is documented in [dependencies](../dependencies/README.md). Existing stores default to provider sign-in off, and provider admission requires the app-owned setting.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { AuthError } from '../errors.mjs';
|
|
2
|
+
|
|
3
|
+
// The accounts adapter interface: everything identity needs from an app's own
|
|
4
|
+
// accounts. Base's oa_ store (./oa.mjs) is one implementation; a seed with its
|
|
5
|
+
// own tables (HappyToHelp, Slate) implements the same methods over them.
|
|
6
|
+
//
|
|
7
|
+
// findByEmail(email) the person whose account holds this proven email, or null
|
|
8
|
+
// findBinding({issuer,subject}) the person bound to this provider principal, or null
|
|
9
|
+
// bind({issuer,subject,person,associated})
|
|
10
|
+
// bind the principal; true when it now names this person.
|
|
11
|
+
// Never rebinds a principal bound to someone else, binds only
|
|
12
|
+
// proven emails or explicit associations, and revokes the
|
|
13
|
+
// person's earlier password credentials.
|
|
14
|
+
// issueSession({person,principal,method,returnTo,request})
|
|
15
|
+
// a CompletedSession ({session,operator,cookie,returnTo}) or null
|
|
16
|
+
// revokeSession(sessionId) end one session
|
|
17
|
+
// owner() the app's owner, or null
|
|
18
|
+
// Optional:
|
|
19
|
+
// bindAndIssueSession(input) atomically bind and issue with current setting/version fences
|
|
20
|
+
// identityEnabled() provider admission setting; missing means off
|
|
21
|
+
// currentBoundSubject(input) current subject for person/issuer; absent denies editor authority
|
|
22
|
+
// acceptInvitation({person,principal,returnTo,request}) accept a pending invitation
|
|
23
|
+
// requiredProvider(email) the provider id an email's domain must use, or null
|
|
24
|
+
//
|
|
25
|
+
// A person is { id, email, status: 'active'|'invited'|'inactive', owner: boolean }.
|
|
26
|
+
const REQUIRED = ['findByEmail', 'findBinding', 'bind', 'issueSession', 'revokeSession', 'owner'];
|
|
27
|
+
const OPTIONAL = ['bindAndIssueSession','acceptInvitation', 'requiredProvider', 'currentBoundSubject', 'identityEnabled'];
|
|
28
|
+
|
|
29
|
+
export function accountsAdapter(adapter) {
|
|
30
|
+
if (!adapter || typeof adapter !== 'object' || REQUIRED.some(name => typeof adapter[name] !== 'function')
|
|
31
|
+
|| OPTIONAL.some(name => adapter[name] !== undefined && typeof adapter[name] !== 'function')) throw new AuthError('invalid_accounts_adapter', 503);
|
|
32
|
+
return adapter;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function person(value) {
|
|
36
|
+
if (!value) return null;
|
|
37
|
+
if (typeof value.id !== 'string' || typeof value.email !== 'string' || !['active', 'invited', 'inactive'].includes(value.status) || typeof value.owner !== 'boolean') {
|
|
38
|
+
throw new AuthError('invalid_accounts_adapter', 503);
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|