@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,28 @@
|
|
|
1
|
+
import { deny } from '../errors.mjs';
|
|
2
|
+
import { normalizeEmail } from '../policy.mjs';
|
|
3
|
+
|
|
4
|
+
// How long an email proof counts: the owner's decision, 10 October 2026.
|
|
5
|
+
export const EMAIL_PROOF_DAYS = 30;
|
|
6
|
+
const PROOF_MS = EMAIL_PROOF_DAYS * 86400000;
|
|
7
|
+
const RESERVED = /\.(?:localhost|test|invalid)$/;
|
|
8
|
+
|
|
9
|
+
// The principal's email, only when it was proven in the last 30 days. A
|
|
10
|
+
// provider that sends email_verified_at dates the proof; one that does not
|
|
11
|
+
// (Google, Microsoft, Okta) proves it at this sign-in.
|
|
12
|
+
export function trustedEmail(principal, now) {
|
|
13
|
+
if (principal.emailVerified !== true || typeof principal.email !== 'string') return null;
|
|
14
|
+
if (principal.emailVerifiedAt !== undefined) {
|
|
15
|
+
if (!Number.isSafeInteger(principal.emailVerifiedAt) || principal.emailVerifiedAt > now + 300000 || now - principal.emailVerifiedAt > PROOF_MS) return null;
|
|
16
|
+
}
|
|
17
|
+
return normalizeEmail(principal.email);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// A production app refuses dev identities and addresses that only local or
|
|
21
|
+
// test systems can hold, whatever the provider asserts.
|
|
22
|
+
export function refuseInProduction(principal, config) {
|
|
23
|
+
if (config.local === true) return;
|
|
24
|
+
if (Array.isArray(principal.amr) && principal.amr.includes('dev')) deny('dev_identity_refused');
|
|
25
|
+
for (const value of [principal.email, principal.associatedEmail]) {
|
|
26
|
+
if (typeof value === 'string' && RESERVED.test(value.trim().toLowerCase())) deny('reserved_email');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
export type Role = 'owner' | 'admin' | 'member';
|
|
2
|
+
export type SessionKind = 'native' | 'review' | 'oidc' | 'password' | 'dev';
|
|
3
|
+
export interface Operator<R extends string = Role> { id: string; email: string; displayName: string; role: R; status: 'invited' | 'active' | 'inactive'; version: number; createdAt: number }
|
|
4
|
+
export interface Session<R extends string = Role> { issuer?: string; subject?: string; sid?: string; id: string; kind: SessionKind; operatorId: string; operator: Operator<R>; createdAt: number; expiresAt: number }
|
|
5
|
+
export interface CompletedSession<R extends string = Role> { session: Pick<Session<R>, 'id' | 'kind' | 'createdAt' | 'expiresAt'>; operator: Operator<R>; cookie: string; returnTo: string }
|
|
6
|
+
export interface MailMessage { deliveryId: string; from: string; to: string[]; subject: string; text: string; html: string; headers: Record<string, string> }
|
|
7
|
+
export interface MailSender { send(message: MailMessage): Promise<{ status: 'accepted' | 'rejected' | 'rate_limited' | 'uncertain' | 'development'; provider: string; providerMessageId: string | null; code?: string }> }
|
|
8
|
+
export interface AuthBranding { productName?: string; accentColor?: string; signInPath?: string; /** Same-origin path of the seed's served rer-theme.css. */ theme?: string }
|
|
9
|
+
export interface AuthPresentation { readonly productName: string; readonly accentColor: string; readonly signInPath: string; readonly theme?: string }
|
|
10
|
+
export interface LinkPreview { kind: 'magic' | 'invite' | 'step_up'; expiresInMinutes: number }
|
|
11
|
+
/** A step-up confirmation proves the session's operator again; it issues no new session. */
|
|
12
|
+
export interface StepUpConfirmed { stepUp: StepUpAction; returnTo: string; cookie?: undefined }
|
|
13
|
+
export type StepUpAction = 'providers:unlink' | 'providers:relink' | 'domains:change' | 'owner:transfer' | 'export' | 'delete' | 'credentials:mint';
|
|
14
|
+
export const STEP_UP_ACTIONS: readonly StepUpAction[];
|
|
15
|
+
export type ProviderPreset = 'auth' | 'google' | 'microsoft' | 'okta' | 'legacy-rs256';
|
|
16
|
+
/** One identity provider. Secrets and the private key are server bindings, never source text. */
|
|
17
|
+
export interface IdentityProviderConfig {
|
|
18
|
+
/** Route and pin id: /auth/oidc/<id>/start and /auth/oidc/<id>/callback. */
|
|
19
|
+
id: string;
|
|
20
|
+
preset: ProviderPreset;
|
|
21
|
+
/** Auth and Okta: the exact HTTPS issuer. Google's is fixed; Microsoft's comes from tenant. */
|
|
22
|
+
issuer?: string;
|
|
23
|
+
tenant?: string;
|
|
24
|
+
/** Explicit same-origin callback, including an incumbent path outside /auth/oidc/. */
|
|
25
|
+
redirectUri?: string;
|
|
26
|
+
/** Required only for legacy-rs256: exact HTTPS endpoints trusted for verified email. */
|
|
27
|
+
endpointPins?: { authorization: string; token: string; jwks: string };
|
|
28
|
+
clientId: string;
|
|
29
|
+
label?: string;
|
|
30
|
+
/** Default: private_key_jwt (no shared secret). Auth accepts only private_key_jwt. */
|
|
31
|
+
tokenAuthMethod?: 'private_key_jwt' | 'client_secret_basic' | 'client_secret_post';
|
|
32
|
+
clientSecret?: string;
|
|
33
|
+
/** The app's private JWK (ES256 or RS256) with kid; the provider holds its public half. */
|
|
34
|
+
privateKey?: JsonWebKey & { kid: string };
|
|
35
|
+
/** Explicit issuer-scoped sub to existing operator email associations; the only way Microsoft and Okta identify a person. */
|
|
36
|
+
subjectEmails?: Record<string, string>;
|
|
37
|
+
additionalAudiences?: string[];
|
|
38
|
+
endpointOrigins?: string[];
|
|
39
|
+
/** Auth only: the lowest oidc-provider interface version accepted (at least the module's minimum). */
|
|
40
|
+
minimumVersion?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface AuthConfig {
|
|
43
|
+
dependencies?: boolean;
|
|
44
|
+
/** Name: 1-80 characters; accent: #rrggbb; signInPath: same-origin absolute path. */
|
|
45
|
+
branding?: AuthBranding;
|
|
46
|
+
origin: string; OWNER_EMAIL: string; SESSION_SECRET: string; SETUP_TOKEN?: string; MAIL_FROM?: string;
|
|
47
|
+
/** Local HTTP is allowed only for literal localhost, 127.0.0.1 or [::1] with matching Host. */
|
|
48
|
+
local?: boolean; allowList?: string[]; sessionSeconds?: number; linkSeconds?: number;
|
|
49
|
+
review?: { enabled: boolean; operatorEmail: string }; reviewSeconds?: number;
|
|
50
|
+
limitWindowSeconds?: number; ipLimit?: number; emailLimit?: number;
|
|
51
|
+
/** Issuers the original admitVerifiedIdentity/createIdentityAdmission seams accept. */
|
|
52
|
+
externalIssuers?: string[];
|
|
53
|
+
/** Linked identity providers, offered as "Continue with…" on the sign-in page. */
|
|
54
|
+
providers?: IdentityProviderConfig[];
|
|
55
|
+
/** Independent secret of at least 32 characters for provider transactions; required with providers. */
|
|
56
|
+
OIDC_TRANSACTION_SECRET?: string;
|
|
57
|
+
identityTimeoutMs?: number; identityStartLimit?: number;
|
|
58
|
+
}
|
|
59
|
+
export type Action = 'operators:list' | 'operators:invite' | 'operators:deactivate' | 'operators:reactivate' | 'operators:role' | 'sessions:list' | 'sessions:revoke' | 'providers:manage' | 'domains:manage';
|
|
60
|
+
export interface PolicyInput { actor: Operator; action: Action; target?: Operator; role?: Role }
|
|
61
|
+
export type RolePolicy = (input: PolicyInput) => boolean | Promise<boolean>;
|
|
62
|
+
/** Minimal structural D1 binding contract, compatible with Workers D1Database. */
|
|
63
|
+
export interface Database {
|
|
64
|
+
prepare(sql: string): Statement;
|
|
65
|
+
batch(statements: Statement[]): Promise<Result[]>;
|
|
66
|
+
}
|
|
67
|
+
export interface Statement {
|
|
68
|
+
bind(...values: unknown[]): Statement;
|
|
69
|
+
first<T = Record<string, unknown>>(): Promise<T | null>;
|
|
70
|
+
all<T = Record<string, unknown>>(): Promise<Result<T>>;
|
|
71
|
+
run<T = Record<string, unknown>>(): Promise<Result<T>>;
|
|
72
|
+
}
|
|
73
|
+
export interface Result<T = Record<string, unknown>> { results: T[]; meta: { changes: number } }
|
|
74
|
+
/**
|
|
75
|
+
* Cryptographically verified principal, the identity layer's output. Its email
|
|
76
|
+
* counts only when emailVerified is true and any emailVerifiedAt (ms) is within
|
|
77
|
+
* 30 days. associatedEmail comes only from trusted server configuration.
|
|
78
|
+
*/
|
|
79
|
+
export interface VerifiedPrincipal { issuer: string; subject: string; provider?: string; email?: string; emailVerified?: boolean; emailVerifiedAt?: number; amr?: string[] }
|
|
80
|
+
export interface Principal extends VerifiedPrincipal { associatedEmail?: string; sid?: string; datedEmailRequired?: boolean; providerGeneration?: number; providerPin?: { id: string; issuer: string; clientId: string; linkedAt: number } }
|
|
81
|
+
export interface VerifiedIdentity extends VerifiedPrincipal { email: string; emailVerified: true }
|
|
82
|
+
/** Deployment-owned association, never populated from token claims or HTTP input. */
|
|
83
|
+
export interface IdentityAdmissionConfig { issuer: string; subjectEmails?: Record<string, string> }
|
|
84
|
+
export type IdentityAdmission<R extends string = Role> = (request: Request, identity: VerifiedPrincipal, input?: { returnTo?: string }) => Promise<CompletedSession<R>>;
|
|
85
|
+
export interface AuthHooks<R extends string = Role> {
|
|
86
|
+
/** Before an email link is sent to an active operator. Return false to send nothing; the public answer is unchanged. */
|
|
87
|
+
beforeMagicLink?(input: { request: Request; email: string; operator: Operator<R> }): boolean | void | Promise<boolean | void>;
|
|
88
|
+
/** After an email-link or provider sign-in completes. Return a returnTo to redirect; throw to refuse (the new session is revoked). */
|
|
89
|
+
afterConfirm?(input: { request: Request; operator: Operator<R>; session: CompletedSession<R>['session']; method: 'email' | 'oidc'; returnTo: string }): void | { returnTo?: string } | Promise<void | { returnTo?: string }>;
|
|
90
|
+
}
|
|
91
|
+
export interface AuthOptions<R extends string = Role> {
|
|
92
|
+
db: Database; config: AuthConfig; mailSender?: MailSender; clientIp: (request: Request) => string | Promise<string>; rolePolicy?: RolePolicy; clock?: () => number;
|
|
93
|
+
hooks?: AuthHooks<R>;
|
|
94
|
+
/** The runtime's fetch for provider discovery, JWKS and token calls (it must honour redirect: 'manual' and AbortSignal). */
|
|
95
|
+
fetch?: typeof globalThis.fetch;
|
|
96
|
+
/** The Worker's waitUntil, so a hidden provider is re-checked after the page answers. */
|
|
97
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
98
|
+
}
|
|
99
|
+
/** A person as the identity layer sees them through an accounts adapter. */
|
|
100
|
+
export interface AccountPerson { id: string; email: string; status: 'active' | 'invited' | 'inactive'; owner: boolean; [extra: string]: unknown }
|
|
101
|
+
/**
|
|
102
|
+
* The accounts adapter interface: what identity needs from an app's own
|
|
103
|
+
* accounts. Base's oa_ store implements it; a seed with its own tables may too.
|
|
104
|
+
*/
|
|
105
|
+
export interface AccountsAdapter<R extends string = Role> {
|
|
106
|
+
findByEmail(email: string): Promise<AccountPerson | null>;
|
|
107
|
+
findBinding(principal: { issuer: string; subject: string }): Promise<AccountPerson | null>;
|
|
108
|
+
/** Never rebinds a principal bound to someone else; binds only proven emails or explicit associations; revokes earlier passwords. */
|
|
109
|
+
bind(input: { issuer: string; subject: string; person: AccountPerson; associated: boolean }): Promise<boolean>;
|
|
110
|
+
issueSession(input: { person: AccountPerson; principal: Principal; method: 'oidc'; returnTo?: string; request: Request }): Promise<CompletedSession<R> | null>;
|
|
111
|
+
bindAndIssueSession?(input: { person: AccountPerson; principal: Principal; method: 'oidc'; returnTo?: string; request: Request }): Promise<CompletedSession<R> | null>;
|
|
112
|
+
revokeSession(sessionId: string): Promise<void>;
|
|
113
|
+
owner(): Promise<AccountPerson | null>;
|
|
114
|
+
currentBoundSubject?(input: {personId:string;issuer:string}):Promise<string|null>;
|
|
115
|
+
identityEnabled?():Promise<boolean>;
|
|
116
|
+
acceptInvitation?(input: { person: AccountPerson; principal: Principal; returnTo?: string; request: Request }): Promise<CompletedSession<R> | null>;
|
|
117
|
+
requiredProvider?(email: string): Promise<string | null>;
|
|
118
|
+
}
|
|
119
|
+
export interface Admission<R extends string = Role> {
|
|
120
|
+
admit(request: Request, principal: Principal, input?: { returnTo?: string }): Promise<CompletedSession<R>>;
|
|
121
|
+
readonly accounts: AccountsAdapter<R>;
|
|
122
|
+
}
|
|
123
|
+
export interface ProviderEntry { id: string; label: string; preset: ProviderPreset; startPath: string; visible: boolean; linked: boolean; pendingRelink: boolean }
|
|
124
|
+
export interface ProviderLink {
|
|
125
|
+
id: string; preset: ProviderPreset; status: 'linked' | 'unlinked'; issuer: string; clientId: string; jwks: string; authorization: string; token: string;
|
|
126
|
+
algorithms: Array<'ES256' | 'RS256'>; tokenAuthMethod: string; responseIssuer: boolean; interfaceVersion: string | null; linkedAt: number;
|
|
127
|
+
pendingIssuer: string | null; pendingClientId: string | null; pendingPreset: string | null; pendingAt: number | null;
|
|
128
|
+
}
|
|
129
|
+
export interface IdentityProviders {
|
|
130
|
+
readonly ids: readonly string[];
|
|
131
|
+
readonly callbackPaths?: Readonly<Record<string, string>>;
|
|
132
|
+
begin(request: Request, id: string, input?: { returnTo?: string }): Promise<Response>;
|
|
133
|
+
callback(request: Request, id: string): Promise<Response>;
|
|
134
|
+
list(): Promise<ProviderEntry[]>;
|
|
135
|
+
relink(id: string): Promise<ProviderLink | null>;
|
|
136
|
+
unlink(id: string): Promise<void>;
|
|
137
|
+
links(): Promise<ProviderLink[]>;
|
|
138
|
+
}
|
|
139
|
+
export interface DomainRule { domain: string; providerId: string; createdAt: number }
|
|
140
|
+
export interface OperatorAuth<R extends string = Role> {
|
|
141
|
+
mintCredential(request:Request,input?:{expiresInSeconds?:number}):Promise<{id:string;token:string;expiresAt:number}>;
|
|
142
|
+
authenticateCredential(request:Request):Promise<{id:string;personId:string;fence:number;kind:SessionKind}|null>;
|
|
143
|
+
authorizeDependencySetup(request: Request, input: { token?: string }): Promise<boolean>;
|
|
144
|
+
completeLocalSetup(request:Request):Promise<{complete:boolean}>;
|
|
145
|
+
identitySettings(request:Request):Promise<{idpSignIn:boolean}>;
|
|
146
|
+
setIdentitySettings(request:Request,input:{idpSignIn:boolean}):Promise<{idpSignIn:boolean}>;
|
|
147
|
+
requireEditorOwner(request:Request):Promise<Session<R>>;
|
|
148
|
+
credentialFence(request:Request):Promise<{personId:string;fence:number;kind:SessionKind}>;
|
|
149
|
+
checkCredentialFence(credential:{personId:string;fence?:number;kind?:SessionKind},request?:Request):Promise<boolean>;
|
|
150
|
+
/** Frozen public branding only; contains no credentials or auth configuration. */
|
|
151
|
+
readonly presentation: AuthPresentation;
|
|
152
|
+
/** Linked identity providers; null when none is configured. */
|
|
153
|
+
readonly providers: IdentityProviders | null;
|
|
154
|
+
readonly admission: Admission<R>;
|
|
155
|
+
readonly accounts: AccountsAdapter<R>;
|
|
156
|
+
bootstrap(request: Request, input: { token: string; displayName: string; returnTo?: string }): Promise<CompletedSession<R>>;
|
|
157
|
+
/** requiredProvider names the provider the address's domain must use, whoever asks. */
|
|
158
|
+
requestMagicLink(request: Request, input: { email: string; returnTo?: string }): Promise<{ status: 'accepted'; requiredProvider?: string }>;
|
|
159
|
+
previewLink(request: Request, token: string | null): Promise<LinkPreview | null>;
|
|
160
|
+
confirmLink(request: Request, input: { token: string }): Promise<CompletedSession<R> | StepUpConfirmed>;
|
|
161
|
+
getSession(request: Request): Promise<Session<R> | null>;
|
|
162
|
+
signOut(request: Request): Promise<{ cookie: string }>;
|
|
163
|
+
invite(request: Request, input: { email: string; displayName: string; role?: Role; returnTo?: string }): Promise<{ status: 'accepted' }>;
|
|
164
|
+
listOperators(request: Request): Promise<Operator<R>[]>;
|
|
165
|
+
/** Cancel a pending invitation using operators:deactivate policy; accepted invitations conflict. */
|
|
166
|
+
cancelInvitation(request: Request, operatorId: string): Promise<Operator<R>>;
|
|
167
|
+
setOperatorStatus(request: Request, operatorId: string, status: 'active' | 'inactive'): Promise<Operator<R>>;
|
|
168
|
+
setRole(request: Request, operatorId: string, role: Role): Promise<Operator<R>>;
|
|
169
|
+
listSessions(request: Request, operatorId?: string): Promise<Array<{ id: string; kind: SessionKind; createdAt: number; expiresAt: number; active: boolean }>>;
|
|
170
|
+
revokeSession(request: Request, sessionId: string): Promise<void>;
|
|
171
|
+
issueReviewSession(request: Request, input?: { returnTo?: string }): Promise<CompletedSession<R>>;
|
|
172
|
+
/** Server-only: caller has verified the full OIDC protocol. Never bind untrusted JSON to this method. */
|
|
173
|
+
admitVerifiedIdentity(request: Request, identity: VerifiedIdentity, input?: { returnTo?: string }): Promise<CompletedSession<R>>;
|
|
174
|
+
/** Server-only configuration: snapshots exact subject associations; caller must verify the full OIDC protocol. */
|
|
175
|
+
createIdentityAdmission(config: IdentityAdmissionConfig): IdentityAdmission<R>;
|
|
176
|
+
/** Server-only: caller has verified a real password against this existing operator. Rate-limit that verifier independently. */
|
|
177
|
+
admitVerifiedPassword(request: Request, operatorId: string, input?: { returnTo?: string }): Promise<CompletedSession<R>>;
|
|
178
|
+
/** Email a step-up link to the signed-in operator's own address. */
|
|
179
|
+
requestStepUp(request: Request, input: { action: StepUpAction; returnTo?: string }): Promise<{ status: 'accepted' }>;
|
|
180
|
+
/** Refuse unless this session holds a fresh (10-minute) app-local email-link proof. Provider sessions never do by themselves. */
|
|
181
|
+
requireStepUp(request: Request, action: StepUpAction): Promise<Session<R>>;
|
|
182
|
+
listDomainRules(request: Request): Promise<DomainRule[]>;
|
|
183
|
+
/** provider null lifts the rule. Needs a fresh step-up proof. */
|
|
184
|
+
setDomainRule(request: Request, input: { domain: string; provider: string | null }): Promise<DomainRule[]>;
|
|
185
|
+
listProviders(request: Request): Promise<ProviderLink[]>;
|
|
186
|
+
unlinkProvider(request: Request, input: { provider: string }): Promise<ProviderLink[]>;
|
|
187
|
+
relinkProvider(request: Request, input: { provider: string }): Promise<ProviderLink[]>;
|
|
188
|
+
}
|
|
189
|
+
export type { NativeAccountsAdapter, NativeAuthOptions, NativePerson, NativeOperatorAuth } from './accounts/native.mjs';
|
|
190
|
+
export type { NativeProofPersistence } from './identity/email-link/index.mjs';
|
|
191
|
+
export function createOperatorAuth<R extends string>(options: import('./accounts/native.mjs').NativeAuthOptions<R>): import('./accounts/native.mjs').NativeOperatorAuth<R>;
|
|
192
|
+
export function createOperatorAuth(options: AuthOptions): OperatorAuth;
|
|
193
|
+
export function createAuthHandler<R extends string>(auth: OperatorAuth<R>): (request: Request) => Promise<Response | null>;
|
|
194
|
+
export interface SignInPageOptions {
|
|
195
|
+
/** While the first owner is unclaimed: the setup form and its instructions. */
|
|
196
|
+
setup?: (request: Request) => null | false | undefined | { instructions?: string } | Promise<null | false | undefined | { instructions?: string }>;
|
|
197
|
+
/** Extra same-origin links under the form (for example the local development outbox). */
|
|
198
|
+
links?: (request: Request) => Array<{ href: string; label: string }> | Promise<Array<{ href: string; label: string }>>;
|
|
199
|
+
defaultReturnTo?: string;
|
|
200
|
+
}
|
|
201
|
+
/** The shared server-rendered sign-in page at branding.signInPath (GET and HEAD); null for anything else. */
|
|
202
|
+
export function createSignInHandler<R extends string>(auth: OperatorAuth<R>, options?: SignInPageOptions): (request: Request) => Promise<Response | null>;
|
|
203
|
+
export class AuthError extends Error { constructor(code: string, status?: number); code: string; status: number }
|
|
204
|
+
export const DEFAULT_ROLES: readonly Role[];
|
|
205
|
+
export function defaultRolePolicy(input: PolicyInput): boolean;
|
|
206
|
+
export function normalizeEmail(value: unknown): string;
|
|
207
|
+
export function safeReturnTo(value: unknown, fallback?: string): string;
|
|
208
|
+
export function isActiveOperator(operator: Operator | null | undefined, allowList?: string[]): boolean;
|
|
209
|
+
export function requireLoopback(request: Request): void;
|
|
210
|
+
export function isLoopbackHost(hostname: string): boolean;
|
|
211
|
+
export function securityHeaders(): Record<string, string>;
|
|
212
|
+
export interface DevGateInput { key: string | undefined; origin: string; local: boolean }
|
|
213
|
+
export interface DevGate {
|
|
214
|
+
/** `rer_dev_gate_<port>`: one gate cookie per local origin. */
|
|
215
|
+
readonly cookieName: string;
|
|
216
|
+
admits(request: Request): Promise<boolean>;
|
|
217
|
+
setCookie(): Promise<string>;
|
|
218
|
+
/** Handles GET/POST /__dev/key; null for every other path. */
|
|
219
|
+
exchange(request: Request): Promise<Response | null>;
|
|
220
|
+
}
|
|
221
|
+
export const DEV_KEY_HEADER: 'x-rer-dev-key';
|
|
222
|
+
export function devGate(input: DevGateInput): DevGate | null;
|
|
223
|
+
export function devNotFound(): Response;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Server-only entry. Never import this module into browser components/loaders.
|
|
2
|
+
// Layers: identity/ proves who a person is, accounts/ holds who they are in this
|
|
3
|
+
// app and what they may do, dev/ is local only. See README.md.
|
|
4
|
+
export { createOperatorAuth, STEP_UP_ACTIONS } from './service.mjs';
|
|
5
|
+
export { createAuthHandler } from './routes.mjs';
|
|
6
|
+
export { createSignInHandler } from './signin/index.mjs';
|
|
7
|
+
export { AuthError } from './errors.mjs';
|
|
8
|
+
export { normalizeEmail, safeReturnTo } from './policy.mjs';
|
|
9
|
+
export { DEFAULT_ROLES, defaultRolePolicy, isActiveOperator } from './accounts/roles.mjs';
|
|
10
|
+
export { requireLoopback, isLoopbackHost, securityHeaders } from './http.mjs';
|
|
11
|
+
export { devGate, devNotFound, DEV_KEY_HEADER } from './dev-gate.mjs';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { AuthError } from './errors.mjs';
|
|
2
|
+
|
|
3
|
+
// URLSearchParams decodes field names before comparison, so token and %74oken
|
|
4
|
+
// cannot be interpreted as different credentials by different HTTP layers.
|
|
5
|
+
export function readParameters(parameters) {
|
|
6
|
+
const fields = Object.create(null);
|
|
7
|
+
for (const [name, value] of parameters) {
|
|
8
|
+
if (Object.hasOwn(fields, name)) throw new AuthError('invalid_input');
|
|
9
|
+
fields[name] = value;
|
|
10
|
+
}
|
|
11
|
+
return fields;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function readJson(text) {
|
|
15
|
+
const value = JSON.parse(text);
|
|
16
|
+
// JSON.parse discards duplicate members. Scan the already validated JSON's
|
|
17
|
+
// strings and containers, comparing decoded keys in each object before using
|
|
18
|
+
// its parsed value. Strings inside values never become object keys.
|
|
19
|
+
const objects = [];
|
|
20
|
+
for (const token of text.matchAll(/"(?:\\[\s\S]|[^"\\])*"|[{}\[\]]/g)) {
|
|
21
|
+
const part = token[0];
|
|
22
|
+
if (part === '{') objects.push(new Set());
|
|
23
|
+
else if (part === '[') objects.push(null);
|
|
24
|
+
else if (part === '}' || part === ']') objects.pop();
|
|
25
|
+
else if (/^\s*:/.test(text.slice(token.index + part.length))) {
|
|
26
|
+
const name = JSON.parse(part);
|
|
27
|
+
const keys = objects.at(-1);
|
|
28
|
+
if (keys.has(name)) throw new AuthError('invalid_input');
|
|
29
|
+
keys.add(name);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function readInput(request) {
|
|
36
|
+
const contentType = request.headers.get('content-type')?.split(';')[0];
|
|
37
|
+
if (!['application/json', 'application/x-www-form-urlencoded'].includes(contentType)) throw new AuthError('unsupported_media_type', 415);
|
|
38
|
+
const reader = request.body?.getReader();
|
|
39
|
+
if (!reader) throw new AuthError('invalid_input');
|
|
40
|
+
const chunks = []; let length = 0;
|
|
41
|
+
while (true) {
|
|
42
|
+
const next = await reader.read(); if (next.done) break;
|
|
43
|
+
length += next.value.byteLength;
|
|
44
|
+
if (length > 8192) { await reader.cancel(); throw new AuthError('input_too_large', 413); }
|
|
45
|
+
chunks.push(next.value);
|
|
46
|
+
}
|
|
47
|
+
const bytes = new Uint8Array(length); let offset = 0;
|
|
48
|
+
for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; }
|
|
49
|
+
try {
|
|
50
|
+
const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
51
|
+
const parsed = contentType === 'application/json' ? readJson(text) : readParameters(new URLSearchParams(text));
|
|
52
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new AuthError('invalid_input');
|
|
53
|
+
return parsed;
|
|
54
|
+
} catch { throw new AuthError('invalid_input'); }
|
|
55
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { hashToken, constantEqual } from './crypto.mjs';
|
|
2
|
+
|
|
3
|
+
// The rer command line signs in to its own loopback development server on every
|
|
4
|
+
// setup and dev start. It proves it holds the private SESSION_SECRET with this
|
|
5
|
+
// header so those requests do not spend the owner's sign-in budget. Only a local
|
|
6
|
+
// (loopback) configuration honours it; production ignores the header entirely.
|
|
7
|
+
// The onboarding adapter derives the same value: HMAC-SHA256(SESSION_SECRET,
|
|
8
|
+
// "local-cli\0sign-in") as lowercase hex.
|
|
9
|
+
export const LOCAL_CLI_HEADER = 'x-rer-local-cli';
|
|
10
|
+
|
|
11
|
+
export async function isLocalCli(request, config) {
|
|
12
|
+
if (!config.local) return false;
|
|
13
|
+
const presented = request.headers.get(LOCAL_CLI_HEADER);
|
|
14
|
+
if (!presented) return false;
|
|
15
|
+
return constantEqual(presented, await hashToken(config.SESSION_SECRET, 'local-cli', 'sign-in'));
|
|
16
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { accentInk } from './branding.mjs';
|
|
2
|
+
import { escapeHtml, securityHeaders } from './http.mjs';
|
|
3
|
+
|
|
4
|
+
// The shared frame of every server-rendered auth page (confirmation, sign-in):
|
|
5
|
+
// no script, styles allowed only by their own hash, the seed's theme when it
|
|
6
|
+
// declares one, light and dark.
|
|
7
|
+
// Added only when the seed declares a theme: its linked rer-theme.css supplies
|
|
8
|
+
// the --rer-* tokens; --rer-logo toggles and draws the mark beside the name.
|
|
9
|
+
// Each token falls back to a readable neutral if the file cannot load.
|
|
10
|
+
function themed(accentColor) {
|
|
11
|
+
return `.rer-themed{color-scheme:light;background:var(--rer-page,#f3f5f8);color:var(--rer-text,#172033)}.rer-themed body{font-family:var(--rer-font-body,inherit)}.rer-themed main{background:var(--rer-surface,#fff);border-color:transparent;border-radius:var(--rer-radius,20px);box-shadow:0 1px 2px #0000000d,0 14px 36px #00000014}.rer-themed h1{font-family:var(--rer-font-heading,var(--rer-font-body,inherit))}.rer-themed .brand{display:flex;align-items:center;gap:12px;font-size:16px;color:var(--rer-text,#172033);font-family:var(--rer-font-heading,var(--rer-font-body,inherit))}.rer-themed .mark{display:none}.rer-themed .brand::before{content:var(--rer-logo,none);display:block;flex:none;width:32px;height:32px;overflow:hidden;text-indent:200%;white-space:nowrap;background:var(--rer-logo) center/contain no-repeat}.rer-themed p{color:var(--rer-muted,#475569)}.rer-themed button,.rer-themed .action{border-color:transparent;border-radius:var(--rer-radius,10px);background:var(--rer-accent,${accentColor});color:var(--rer-accent-text,${accentInk(accentColor)})}.rer-themed button:focus-visible,.rer-themed .action:focus-visible{outline-color:var(--rer-focus,#172033)}.rer-themed button.secondary{background:transparent;color:var(--rer-text,#172033);border-color:var(--rer-muted,#64748b)}.rer-themed .secondary-link a{color:var(--rer-link,var(--rer-text,#172033))}.rer-themed .secondary-link a:focus-visible{outline-color:var(--rer-focus,#172033)}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function stylesheet(accentColor) {
|
|
15
|
+
return `:root{color-scheme:light;font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#172033;background:#f3f5f8}*{box-sizing:border-box}body{margin:0;min-height:100vh;padding:32px 20px;display:grid;place-items:center}main{width:100%;max-width:480px;background:#fff;border:1px solid #dce2ea;border-radius:20px;padding:36px;box-shadow:0 12px 40px #1720330a;overflow-wrap:anywhere}.brand{font-size:14px;font-weight:650;line-height:1.5;margin:0 0 32px;color:#475569}.mark{display:block;width:32px;height:6px;border-radius:3px;border:1px solid currentColor;background:${accentColor};margin-bottom:16px}h1,h2{text-wrap:balance}h1{font-size:30px;line-height:1.3;letter-spacing:-.02em;margin:0 0 24px;font-weight:700}p{font-size:16px;line-height:1.6;margin:0 0 24px;color:#475569;text-wrap:pretty}form{margin:0}button,.action{display:block;width:100%;border:1px solid #64748b;border-radius:10px;background:${accentColor};color:${accentInk(accentColor)};padding:13px 18px;font:inherit;font-weight:650;text-align:center;text-decoration:none;cursor:pointer;line-height:1.5}button:hover,.action:hover{box-shadow:inset 0 0 0 2px currentColor}button:focus-visible,.action:focus-visible{outline:3px solid #172033;outline-offset:4px}@media(max-width:480px){body{padding:24px 16px}main{padding:28px 24px}h1{font-size:28px}}@media(prefers-color-scheme:dark){:root{color-scheme:dark;background:#111827;color:#f8fafc}main{background:#1e293b;border-color:#475569;box-shadow:none}.brand,p{color:#cbd5e1}button:focus-visible,.action:focus-visible{outline-color:#f8fafc}}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Form controls and notices used by the sign-in page; part of the same hashed sheet.
|
|
19
|
+
const FORMS = `label{display:block;font-weight:600;margin:0 0 8px}input{display:block;width:100%;margin:0 0 16px;padding:12px 14px;border:1px solid #64748b;border-radius:10px;font:inherit;line-height:1.5;color:inherit;background:transparent}input:focus-visible{outline:3px solid #172033;outline-offset:2px}.stack{display:grid;gap:12px;margin:0 0 24px}button.secondary{background:transparent;color:inherit}.divider{display:flex;align-items:center;gap:12px;margin:0 0 24px;color:#475569;font-size:14px}.divider::before,.divider::after{content:"";flex:1;border-top:1px solid #dce2ea}.notice{border-left:4px solid #64748b;padding:8px 12px;margin:0 0 24px}h2{font-size:20px;line-height:1.4;margin:32px 0 16px}.secondary-link{margin:16px 0 0;text-align:center;font-size:15px}.secondary-link a{color:#334155;font-weight:600;text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:3px}.secondary-link a:hover{text-decoration-thickness:2px}.secondary-link a:focus-visible{outline:3px solid #172033;outline-offset:2px;border-radius:2px}@media(prefers-color-scheme:dark){input:focus-visible,.secondary-link a:focus-visible{outline-color:#f8fafc}.divider{color:#cbd5e1}.divider::before,.divider::after{border-color:#475569}.secondary-link a{color:#cbd5e1}}`;
|
|
20
|
+
|
|
21
|
+
export async function pageResponse({ branding, title, heading, content, status = 200, method = 'GET', referrer, formTargets = [] }) {
|
|
22
|
+
const css = stylesheet(branding.accentColor) + FORMS + (branding.theme ? themed(branding.accentColor) : '');
|
|
23
|
+
const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(css)));
|
|
24
|
+
const hash = btoa(String.fromCharCode(...digest));
|
|
25
|
+
const product = escapeHtml(branding.productName);
|
|
26
|
+
const html = `<!doctype html><html lang="en"${branding.theme ? ' class="rer-themed"' : ''}><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${title ?? heading}</title>${branding.theme ? `<link rel="stylesheet" href="${escapeHtml(branding.theme)}">` : ''}<style>${css}</style></head><body><main><p class="brand"><span class="mark" aria-hidden="true"></span>${product}</p><h1>${heading}</h1>${content}</main></body></html>`;
|
|
27
|
+
const headers = { ...securityHeaders(), 'Content-Type': 'text/html; charset=utf-8' };
|
|
28
|
+
// A form may post to this origin only, plus any origin its own redirect must reach (a provider's authorization endpoint).
|
|
29
|
+
if (formTargets.length) headers['Content-Security-Policy'] = headers['Content-Security-Policy'].replace("form-action 'self'", `form-action 'self' ${formTargets.join(' ')}`);
|
|
30
|
+
headers['Content-Security-Policy'] += branding.theme ? `; style-src 'self' 'sha256-${hash}'; font-src 'self'; img-src 'self'` : `; style-src 'sha256-${hash}'`;
|
|
31
|
+
// Native form POSTs need their Origin; never disclose the path or query.
|
|
32
|
+
if (referrer) headers['Referrer-Policy'] = referrer;
|
|
33
|
+
return new Response(method === 'HEAD' ? null : html, { status, headers });
|
|
34
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AuthError } from './errors.mjs';
|
|
2
|
+
// Value rules shared by every layer.
|
|
3
|
+
export function normalizeEmail(value) {
|
|
4
|
+
if (typeof value !== 'string') throw new AuthError('invalid_email');
|
|
5
|
+
const email = value.trim().toLowerCase();
|
|
6
|
+
if (email.length > 254 || !/^[^\s@<>\x00-\x1f]+@[^\s@<>\x00-\x1f]+\.[^\s@<>\x00-\x1f]+$/.test(email)) throw new AuthError('invalid_email');
|
|
7
|
+
return email;
|
|
8
|
+
}
|
|
9
|
+
export function emailDomain(email) { return email.slice(email.lastIndexOf('@') + 1); }
|
|
10
|
+
export function safeReturnTo(value, fallback = '/app') {
|
|
11
|
+
const safeFallback = fallback === '/app' ? '/app' : safeReturnTo(fallback);
|
|
12
|
+
if (typeof value !== 'string' || value.length > 2048 || !value.startsWith('/') || value.startsWith('//') || /[\\\x00-\x20\x7f]/.test(value)) return safeFallback;
|
|
13
|
+
// Decode repeatedly to reject encoded slash/backslash/control redirect tricks.
|
|
14
|
+
let decoded = value;
|
|
15
|
+
for (let i = 0; i < 4; i++) {
|
|
16
|
+
try { const next = decodeURIComponent(decoded); if (next === decoded) break; decoded = next; } catch { return safeFallback; }
|
|
17
|
+
if (decoded.startsWith('//') || /[\\\x00-\x1f\x7f]/.test(decoded)) return safeFallback;
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { identitySettingsPage } from './accounts/settings-page.mjs';
|
|
2
|
+
import { confirmationResponse } from './identity/email-link/confirmation.mjs';
|
|
3
|
+
import { createOidcHandler } from './identity/oidc/index.mjs';
|
|
4
|
+
import { AuthError } from './errors.mjs';
|
|
5
|
+
import { securityHeaders, requireLoopback } from './http.mjs';
|
|
6
|
+
import { safeReturnTo } from './policy.mjs';
|
|
7
|
+
import { readInput, readParameters } from './input.mjs';
|
|
8
|
+
function json(value, status = 200) { return Response.json(value, { status, headers: securityHeaders() }); }
|
|
9
|
+
function seeOther(location, cookie) { return new Response(null, { status: 303, headers: { ...securityHeaders(), ...(cookie ? { 'Set-Cookie': cookie } : {}), Location: location } }); }
|
|
10
|
+
// A native HTML form (the sign-in page before any script) gets a page back; an
|
|
11
|
+
// API caller gets JSON.
|
|
12
|
+
const formPost = request => (request.headers.get('content-type') ?? '').split(';')[0].trim().toLowerCase() === 'application/x-www-form-urlencoded';
|
|
13
|
+
function notice(auth, code, extra = {}) {
|
|
14
|
+
const url = new URL(auth.presentation.signInPath, 'https://signin.invalid');
|
|
15
|
+
url.searchParams.set('notice', code);
|
|
16
|
+
for (const [key, value] of Object.entries(extra)) if (value) url.searchParams.set(key, value);
|
|
17
|
+
return url.pathname + url.search;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Fetch-compatible optional HTTP adapter. Returns null for paths it does not own. */
|
|
21
|
+
export function createAuthHandler(auth) {
|
|
22
|
+
const oidc = auth.providers ? createOidcHandler(auth.providers) : null;
|
|
23
|
+
return async function handleAuth(request) {
|
|
24
|
+
const url = new URL(request.url);
|
|
25
|
+
const path = url.pathname;
|
|
26
|
+
let form = false;
|
|
27
|
+
try {
|
|
28
|
+
const providerResponse = await oidc?.(request);
|
|
29
|
+
if (providerResponse) return providerResponse;
|
|
30
|
+
if (!path.startsWith('/auth/')) return null;
|
|
31
|
+
if (path.startsWith('/auth/oidc/')) return json({ error: 'not_found' }, 404);
|
|
32
|
+
if (path === '/auth/review-session') requireLoopback(request);
|
|
33
|
+
if (path === '/auth/confirm' && ['GET', 'HEAD'].includes(request.method)) {
|
|
34
|
+
const token = readParameters(url.searchParams).token ?? null;
|
|
35
|
+
const preview = await auth.previewLink(request, token);
|
|
36
|
+
return await confirmationResponse({ branding: auth.presentation, preview, token, method: request.method });
|
|
37
|
+
}
|
|
38
|
+
if (path === '/auth/session' && request.method === 'GET') {
|
|
39
|
+
const session = await auth.getSession(request);
|
|
40
|
+
return session ? json(session) : json({ error: 'unauthenticated' }, 401);
|
|
41
|
+
}
|
|
42
|
+
if (path === '/auth/operators' && request.method === 'GET') return json(await auth.listOperators(request));
|
|
43
|
+
if (path === '/auth/sessions' && request.method === 'GET') return json(await auth.listSessions(request, readParameters(url.searchParams).operatorId));
|
|
44
|
+
if (path === '/auth/domain-rules' && request.method === 'GET') return json(await auth.listDomainRules(request));
|
|
45
|
+
if (path === '/auth/settings/identity' && request.method === 'GET') {const value=await auth.identitySettings(request);return /text\/html/.test(request.headers.get('accept')??'')?identitySettingsPage(value):json(value);}
|
|
46
|
+
if (path === '/auth/providers' && request.method === 'GET') return json(await auth.listProviders(request));
|
|
47
|
+
const posts = new Set(['/auth/bootstrap', '/auth/magic-link', '/auth/confirm', '/auth/signout', '/auth/invite', '/auth/invite/cancel', '/auth/review-session',
|
|
48
|
+
'/auth/operator/status', '/auth/operator/role', '/auth/session/revoke', '/auth/step-up', '/auth/domain-rules', '/auth/providers/unlink', '/auth/providers/relink', '/auth/settings/identity', '/auth/setup-complete']);
|
|
49
|
+
if (!posts.has(path)) return json({ error: 'not_found' }, 404);
|
|
50
|
+
if (request.method !== 'POST') return json({ error: 'method_not_allowed' }, 405);
|
|
51
|
+
const query = readParameters(url.searchParams);
|
|
52
|
+
// POST credentials belong only in the body. Refuse a second credential
|
|
53
|
+
// source even if a proxy or caller would choose one of the two values.
|
|
54
|
+
if (Object.hasOwn(query, 'token')) throw new AuthError('invalid_input');
|
|
55
|
+
form = formPost(request) && ['/auth/magic-link', '/auth/step-up'].includes(path);
|
|
56
|
+
const input = await readInput(request);
|
|
57
|
+
switch (path) {
|
|
58
|
+
case '/auth/settings/identity': {const value=await auth.setIdentitySettings(request,formPost(request)?{idpSignIn:input.idpSignIn==='true'}:input);return formPost(request)?seeOther('/auth/settings/identity'):json(value);}
|
|
59
|
+
case '/auth/setup-complete': return json(await auth.completeLocalSetup(request));
|
|
60
|
+
case '/auth/bootstrap': { const value = await auth.bootstrap(request, input); return seeOther(value.returnTo, value.cookie); }
|
|
61
|
+
case '/auth/magic-link': {
|
|
62
|
+
const result = await auth.requestMagicLink(request, input);
|
|
63
|
+
return form ? seeOther(notice(auth, 'sent', { returnTo: safeReturnTo(input.returnTo) === '/app' ? undefined : safeReturnTo(input.returnTo), provider: result.requiredProvider })) : json(result, 202);
|
|
64
|
+
}
|
|
65
|
+
case '/auth/confirm': { const value = await auth.confirmLink(request, input); return seeOther(value.returnTo, value.cookie); }
|
|
66
|
+
case '/auth/review-session': { const value = await auth.issueReviewSession(request, input); return seeOther(value.returnTo, value.cookie); }
|
|
67
|
+
case '/auth/step-up': {
|
|
68
|
+
const result = await auth.requestStepUp(request, input);
|
|
69
|
+
return form ? seeOther(notice(auth, 'step_up_sent')) : json(result, 202);
|
|
70
|
+
}
|
|
71
|
+
case '/auth/invite': return json(await auth.invite(request, input), 202);
|
|
72
|
+
case '/auth/invite/cancel': return json(await auth.cancelInvitation(request, input.operatorId));
|
|
73
|
+
case '/auth/signout': {
|
|
74
|
+
const result = await auth.signOut(request);
|
|
75
|
+
return new Response(null, { status: 204, headers: { ...securityHeaders(), 'Set-Cookie': result.cookie } });
|
|
76
|
+
}
|
|
77
|
+
case '/auth/operator/status': return json(await auth.setOperatorStatus(request, input.operatorId, input.status));
|
|
78
|
+
case '/auth/operator/role': return json(await auth.setRole(request, input.operatorId, input.role));
|
|
79
|
+
case '/auth/session/revoke': await auth.revokeSession(request, input.sessionId); return new Response(null, { status: 204, headers: securityHeaders() });
|
|
80
|
+
case '/auth/domain-rules': return json(await auth.setDomainRule(request, input));
|
|
81
|
+
case '/auth/providers/unlink': return json(await auth.unlinkProvider(request, input));
|
|
82
|
+
case '/auth/providers/relink': return json(await auth.relinkProvider(request, input));
|
|
83
|
+
}
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (form) return seeOther(notice(auth, error instanceof AuthError ? error.code : 'auth_unavailable'));
|
|
86
|
+
if (error instanceof AuthError) return json({ error: error.code }, error.status);
|
|
87
|
+
// Do not put SQL, email addresses, setup tokens or provider exceptions on the wire.
|
|
88
|
+
return json({ error: 'auth_unavailable' }, 503);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|