@runeditrun/rer 0.0.0-stage → 0.1.1
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 +157 -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 +182 -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 +158 -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 +117 -0
- package/global/launch/facts.mjs +94 -0
- package/global/launch/failure.mjs +5 -0
- package/global/launch/local-defaults.mjs +4 -0
- package/global/launch/prepare.mjs +63 -0
- package/global/launch/render.mjs +44 -0
- package/global/launch/state.mjs +106 -0
- package/global/launch/steps.mjs +90 -0
- package/global/launch/stop.mjs +89 -0
- package/global/launch/worker.mjs +62 -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,137 @@
|
|
|
1
|
+
// Local dev identity. SERVER-ONLY and LOCAL-ONLY: load this layer only from a
|
|
2
|
+
// local-only Worker entry (a `wrangler dev` or Vite-serve entry), never from the
|
|
3
|
+
// deployed main. `rer build` refuses any build output containing the marker.
|
|
4
|
+
import { configure } from '../config.mjs';
|
|
5
|
+
import { AuthStore } from '../accounts/store.mjs';
|
|
6
|
+
import { createOperatorAuth } from '../service.mjs';
|
|
7
|
+
import { AuthError } from '../errors.mjs';
|
|
8
|
+
import { hashToken, opaqueToken } from '../crypto.mjs';
|
|
9
|
+
import { readCookie, sessionCookie, isLoopbackHost } from '../http.mjs';
|
|
10
|
+
import { safeReturnTo } from '../policy.mjs';
|
|
11
|
+
import { isActiveOperator, defaultRolePolicy } from '../accounts/roles.mjs';
|
|
12
|
+
import { devGate, devNotFound } from '../dev-gate.mjs';
|
|
13
|
+
import { registerDevAdmission } from '../dev-admission.mjs';
|
|
14
|
+
import { definePersonas } from './personas.mjs';
|
|
15
|
+
import { decorate } from './badge.mjs';
|
|
16
|
+
|
|
17
|
+
export const DEV_LAYER_MARKER = 'rer-dev-layer:local-only-never-deploy';
|
|
18
|
+
const headers = { 'cache-control': 'no-store', 'referrer-policy': 'no-referrer', 'x-content-type-options': 'nosniff', 'x-robots-tag': 'noindex, nofollow', 'x-rer-dev-layer': DEV_LAYER_MARKER };
|
|
19
|
+
const text = (status, body) => new Response(body, { status, headers: { ...headers, 'content-type': 'text/plain; charset=utf-8' } });
|
|
20
|
+
|
|
21
|
+
// RER Managed sets RER_INSTALLATION_ID with its management origin. Dev identity
|
|
22
|
+
// never runs inside such an installation, even one reached from this computer.
|
|
23
|
+
export function managedInstallation(env) {
|
|
24
|
+
if (!env.RER_INSTALLATION_ID || !env.RER_MANAGEMENT_ORIGIN) return false;
|
|
25
|
+
try { return !isLoopbackHost(new URL(env.RER_MANAGEMENT_ORIGIN).hostname); } catch { return true; }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Wraps a seed's Worker with dev sign-in (`/auth/dev/enter?as=…`), the persona
|
|
30
|
+
* list (`/__dev/personas`) and the dev-only badge. Every surface needs the key
|
|
31
|
+
* gate and answers 404 without it. `config(env)` is the seed's own AuthConfig
|
|
32
|
+
* and `personas()` its devPersonas() declaration.
|
|
33
|
+
*/
|
|
34
|
+
export function withDevIdentity(worker, { bindings = env => env, config: authConfig, personas = () => [] }) {
|
|
35
|
+
const declared = definePersonas(personas());
|
|
36
|
+
return {
|
|
37
|
+
...worker,
|
|
38
|
+
async fetch(request, original, execution) {
|
|
39
|
+
const env = bindings(original);
|
|
40
|
+
if (managedInstallation(env)) {
|
|
41
|
+
// Never admit a dev credential here; with dev identity on, serve nothing.
|
|
42
|
+
registerDevAdmission(null);
|
|
43
|
+
if (env.RER_DEV_IDENTITY !== 'off') return text(503, 'Dev identity refuses to run beside RER_INSTALLATION_ID with a non-loopback RER_MANAGEMENT_ORIGIN. Remove those bindings from local settings or use rer dev --login.');
|
|
44
|
+
return new URL(request.url).pathname === '/auth/dev/enter' || new URL(request.url).pathname === '/__dev/personas' ? devNotFound() : worker.fetch(request, original, execution);
|
|
45
|
+
}
|
|
46
|
+
const gate = devGate({ key: env.RER_DEV_KEY, origin: env.AUTH_ORIGIN, local: env.AUTH_LOCAL === 'true' });
|
|
47
|
+
registerDevAdmission(gate ? r => gate.admits(r) : null);
|
|
48
|
+
const path = new URL(request.url).pathname;
|
|
49
|
+
if (path === '/auth/dev/enter' || path === '/__dev/personas') {
|
|
50
|
+
if (!gate || env.RER_DEV_IDENTITY === 'off' || !await gate.admits(request)) return devNotFound();
|
|
51
|
+
const context = identity(env, authConfig, declared);
|
|
52
|
+
try {
|
|
53
|
+
return path === '/__dev/personas' ? Response.json(context.list(), { headers }) : await enter(request, gate, context);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error instanceof AuthError) return Response.json({ error: error.code }, { status: error.status, headers });
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const response = await worker.fetch(request, original, execution);
|
|
60
|
+
if (!gate || request.method !== 'GET' || path.startsWith('/__dev/')) return response;
|
|
61
|
+
const admitted = await gate.admits(request);
|
|
62
|
+
const badge = admitted && env.RER_DEV_IDENTITY !== 'off' ? await identity(env, authConfig, declared).badge(request) : null;
|
|
63
|
+
return decorate(response, { exchange: path === '/', badge });
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function identity(env, authConfig, declared) {
|
|
69
|
+
const config = configure(authConfig(env)), store = new AuthStore(env.DB);
|
|
70
|
+
const hash = token => hashToken(config.SESSION_SECRET, 'session', token);
|
|
71
|
+
const everyone = () => [{ as: 'owner', email: config.OWNER_EMAIL }, ...declared];
|
|
72
|
+
async function current(request) {
|
|
73
|
+
const token = readCookie(request, config);
|
|
74
|
+
if (!token) return null;
|
|
75
|
+
const session = await store.session(await hash(token), Date.now());
|
|
76
|
+
return session && isActiveOperator(session.operator, config.allowList) ? session : null;
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
config, store, hash, current, everyone,
|
|
80
|
+
async ownerProof(request) {
|
|
81
|
+
// Reuse the same persisted app-local proof guard as the administration
|
|
82
|
+
// routes. This read-only guard never needs a mail sender or an IP budget.
|
|
83
|
+
const auth = createOperatorAuth({ db: env.DB, config: authConfig(env), clientIp: () => null });
|
|
84
|
+
const actor = await auth.requireStepUp(request, 'owner:transfer');
|
|
85
|
+
if (!defaultRolePolicy({ actor: actor.operator, action: 'operators:invite', role: 'owner' })) throw new AuthError('forbidden', 403);
|
|
86
|
+
return { ...actor, hash: await hash(readCookie(request, config)) };
|
|
87
|
+
},
|
|
88
|
+
list: () => [...everyone().map(({ as, email, role }) => ({ as, email, ...(role ? { role } : {}) })), { as: 'signed-out' }],
|
|
89
|
+
async badge(request) { return { session: await current(request), choices: everyone() }; },
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function enter(request, gate, { config, store, hash, current, everyone, ownerProof }) {
|
|
94
|
+
if (request.method !== 'GET') return text(405, 'Method not allowed');
|
|
95
|
+
const url = new URL(request.url), as = url.searchParams.get('as') ?? 'owner';
|
|
96
|
+
const reply = new Headers(headers);
|
|
97
|
+
// A harness presenting the key header also receives the browser cookie.
|
|
98
|
+
if (!(request.headers.get('cookie') ?? '').includes(`${gate.cookieName}=`)) reply.append('set-cookie', await gate.setCookie());
|
|
99
|
+
const existing = await current(request);
|
|
100
|
+
if (as === 'signed-out') {
|
|
101
|
+
const token = readCookie(request, config);
|
|
102
|
+
if (token) await store.run('UPDATE oa_sessions SET revoked_at=COALESCE(revoked_at,?) WHERE token_hash=?', [Date.now(), await hash(token)]);
|
|
103
|
+
reply.append('set-cookie', sessionCookie('', config, 0));
|
|
104
|
+
reply.set('location', '/');
|
|
105
|
+
return new Response(null, { status: 303, headers: reply });
|
|
106
|
+
}
|
|
107
|
+
const persona = everyone().find(p => p.as === as);
|
|
108
|
+
if (!persona) return devNotFound();
|
|
109
|
+
let operator = await store.byEmail(persona.email);
|
|
110
|
+
// Personas are real operators, created only here, in local dev.
|
|
111
|
+
if (!operator && persona.role) {
|
|
112
|
+
if (persona.role === 'owner') {
|
|
113
|
+
const actor = await ownerProof(request), now = Date.now();
|
|
114
|
+
const fence = store.actorFence(actor, now);
|
|
115
|
+
const result = await store.run(`INSERT INTO oa_operators(id,email,display_name,role,status,created_at)
|
|
116
|
+
SELECT ?,?,?,'owner','active',? WHERE ${fence.sql} ON CONFLICT(email) DO NOTHING`,
|
|
117
|
+
[crypto.randomUUID(), persona.email, persona.displayName, now, ...fence.args]);
|
|
118
|
+
if (result.meta.changes !== 1) throw new AuthError('authority_changed', 409);
|
|
119
|
+
} else {
|
|
120
|
+
await store.run(`INSERT INTO oa_operators(id,email,display_name,role,status,created_at) VALUES(?,?,?,?,'active',?) ON CONFLICT(email) DO NOTHING`,
|
|
121
|
+
[crypto.randomUUID(), persona.email, persona.displayName, persona.role, Date.now()]);
|
|
122
|
+
}
|
|
123
|
+
operator = await store.byEmail(persona.email);
|
|
124
|
+
}
|
|
125
|
+
if (!operator) return text(409, 'The owner that rer setup claimed is not in this database. Run rer setup.');
|
|
126
|
+
if (!isActiveOperator(operator, config.allowList)) return text(403, `${as} is not an active operator in this app.`);
|
|
127
|
+
const domain = operator.email.slice(operator.email.lastIndexOf('@') + 1);
|
|
128
|
+
if (operator.role !== 'owner' && await store.domainRule(domain)) return text(403, 'Sign-in refused');
|
|
129
|
+
if (existing?.kind !== 'dev' || existing.operator.id !== operator.id) {
|
|
130
|
+
const token = opaqueToken(), session = { id: crypto.randomUUID(), hash: await hash(token), expiresAt: Date.now() + config.sessionSeconds * 1000 };
|
|
131
|
+
const issued = await store.issue({ operatorId: operator.id, expectedVersion: operator.version, session, now: Date.now(), kind: 'dev' });
|
|
132
|
+
if (!issued) return text(409, 'The operator changed while signing in. Try again.');
|
|
133
|
+
reply.append('set-cookie', sessionCookie(token, config, config.sessionSeconds));
|
|
134
|
+
}
|
|
135
|
+
reply.set('location', safeReturnTo(url.searchParams.get('returnTo')));
|
|
136
|
+
return new Response(null, { status: 303, headers: reply });
|
|
137
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { DEFAULT_ROLES } from '../accounts/roles.mjs';
|
|
2
|
+
|
|
3
|
+
// A seed's devPersonas() returns [{ as, role, displayName? }]. Each persona is
|
|
4
|
+
// a real operator at `<as>@dev.localhost`, created only by local dev sign-in.
|
|
5
|
+
// Export marks them suspended; `.localhost` mail never leaves this computer.
|
|
6
|
+
export const PERSONA_DOMAIN = 'dev.localhost';
|
|
7
|
+
export function definePersonas(list) {
|
|
8
|
+
if (!Array.isArray(list)) throw new TypeError('devPersonas() must return an array.');
|
|
9
|
+
const seen = new Set(['owner', 'signed-out']);
|
|
10
|
+
return Object.freeze(list.map(entry => {
|
|
11
|
+
const { as, role, displayName } = entry ?? {};
|
|
12
|
+
if (typeof as !== 'string' || !/^[a-z][a-z0-9-]{0,39}$/.test(as) || seen.has(as)) throw new TypeError(`Invalid or repeated dev persona name: ${String(as)}`);
|
|
13
|
+
if (!DEFAULT_ROLES.includes(role)) throw new TypeError(`Dev persona ${as} needs a role: ${DEFAULT_ROLES.join(', ')}.`);
|
|
14
|
+
seen.add(as);
|
|
15
|
+
return Object.freeze({ as, role, email: `${as}@${PERSONA_DOMAIN}`, displayName: typeof displayName === 'string' && displayName.trim() ? displayName.trim().slice(0, 80) : `Dev ${as}` });
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export const DEV_KEY_FILE: string;
|
|
2
|
+
export function readDevKey(root: string): Promise<string>;
|
|
3
|
+
export function signInAs(input: { origin: string; key: string; as?: string; returnTo?: string }): Promise<{ cookies: { name: string; value: string }[]; location: string | null; header: string }>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Node test harness door: sign a test browser or fetch client in as a persona
|
|
2
|
+
// on a running local app. The harness presents the checkout's RER_DEV_KEY; the
|
|
3
|
+
// app answers 404 without it. Never import this from Worker code.
|
|
4
|
+
import { readFile } from 'node:fs/promises';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
export const DEV_KEY_FILE = '.rer/local/dev-key.env';
|
|
8
|
+
export async function readDevKey(root) {
|
|
9
|
+
const text = await readFile(join(root, DEV_KEY_FILE), 'utf8');
|
|
10
|
+
const key = /^RER_DEV_KEY=([A-Za-z0-9_-]{43,256})$/m.exec(text)?.[1];
|
|
11
|
+
if (!key) throw new Error(`${DEV_KEY_FILE} holds no RER_DEV_KEY. Run rer setup.`);
|
|
12
|
+
return key;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Returns the cookies to install ({ name, value }[]) and the redirect target. */
|
|
16
|
+
export async function signInAs({ origin, key, as = 'owner', returnTo }) {
|
|
17
|
+
const url = new URL('/auth/dev/enter', origin);
|
|
18
|
+
url.searchParams.set('as', as);
|
|
19
|
+
if (returnTo) url.searchParams.set('returnTo', returnTo);
|
|
20
|
+
const response = await fetch(url, { redirect: 'manual', headers: { 'x-rer-dev-key': key } });
|
|
21
|
+
await response.body?.cancel();
|
|
22
|
+
if (response.status !== 303) throw new Error(`Dev sign-in as ${as} answered HTTP ${response.status}.`);
|
|
23
|
+
const cookies = response.headers.getSetCookie().map(line => line.split(';')[0]).map(pair => ({ name: pair.slice(0, pair.indexOf('=')), value: pair.slice(pair.indexOf('=') + 1) }));
|
|
24
|
+
return { cookies, location: response.headers.get('location'), header: cookies.filter(c => c.value).map(c => `${c.name}=${c.value}`).join('; ') };
|
|
25
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Registers the local key gate's check. Only the local-only dev layer calls it; a non-function clears it. */
|
|
2
|
+
export function registerDevAdmission(check: ((request: Request) => boolean | Promise<boolean>) | null | undefined): void;
|
|
3
|
+
/** True only when a registered check returns exactly true for this request; false with none registered or when it throws. */
|
|
4
|
+
export function devAdmitted(request: Request): Promise<boolean>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// A dev session, and any credential minted while using one, counts only on a
|
|
2
|
+
// request that passes the local key gate. Only the local-only dev layer
|
|
3
|
+
// (./dev/) registers that check. A production Worker never loads the layer, so
|
|
4
|
+
// every dev credential is refused there.
|
|
5
|
+
let admission = null;
|
|
6
|
+
export function registerDevAdmission(check) { admission = typeof check === 'function' ? check : null; }
|
|
7
|
+
export async function devAdmitted(request) {
|
|
8
|
+
if (!admission) return false;
|
|
9
|
+
try { return (await admission(request)) === true; } catch { return false; }
|
|
10
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { constantEqual } from './crypto.mjs';
|
|
2
|
+
import { isLoopbackHost, localCookieName } from './http.mjs';
|
|
3
|
+
|
|
4
|
+
// The local key gate. `rer setup` writes a random RER_DEV_KEY per checkout and
|
|
5
|
+
// the local runtimes receive it as a binding; a deployed Worker never has one
|
|
6
|
+
// (`rer deploy` refuses it), so this gate is inert there. A tunnel or local
|
|
7
|
+
// reverse proxy makes remote requests look local, so the network is not the
|
|
8
|
+
// gate: holding the key is. Browsers trade it once for a host-only cookie; test
|
|
9
|
+
// harnesses and the rer command line present it in a header. The cookie is
|
|
10
|
+
// named for the origin's port (`rer_dev_gate_8787`), so checkouts side by side
|
|
11
|
+
// on one loopback host keep their own.
|
|
12
|
+
export const DEV_KEY_HEADER = 'x-rer-dev-key';
|
|
13
|
+
export const DEV_KEY_PATH = '/__dev/key';
|
|
14
|
+
const encoder = new TextEncoder();
|
|
15
|
+
const validKey = value => typeof value === 'string' && /^[A-Za-z0-9_-]{43,256}$/.test(value);
|
|
16
|
+
|
|
17
|
+
async function digest(text) {
|
|
18
|
+
const bytes = await crypto.subtle.digest('SHA-256', encoder.encode(text));
|
|
19
|
+
return btoa(String.fromCharCode(...new Uint8Array(bytes)));
|
|
20
|
+
}
|
|
21
|
+
async function cookieValue(key) {
|
|
22
|
+
const hmac = await crypto.subtle.importKey('raw', encoder.encode(key), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
23
|
+
const bytes = await crypto.subtle.sign('HMAC', hmac, encoder.encode('rer-dev-gate\0cookie'));
|
|
24
|
+
return Array.from(new Uint8Array(bytes), b => b.toString(16).padStart(2, '0')).join('');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Reads `#rer-dev=KEY` (with optional `as` or `next`), removes it from the
|
|
28
|
+
// address bar first, then trades it through a same-origin POST. The fragment
|
|
29
|
+
// never reaches a server; the key never appears in a query string.
|
|
30
|
+
export const EXCHANGE_SCRIPT = "(()=>{const h=new URLSearchParams(location.hash.slice(1)),k=h.get('rer-dev');if(!k)return;history.replaceState(null,'',location.pathname+location.search);const a=h.get('as'),n=h.get('next')||(a?'/auth/dev/enter?as='+encodeURIComponent(a):location.pathname+location.search),to=n.startsWith('/')&&!n.startsWith('//')&&!n.includes('\\\\')?n:'/';fetch('" + DEV_KEY_PATH + "',{method:'POST',credentials:'same-origin',headers:{'content-type':'application/json'},body:JSON.stringify({key:k})}).then(r=>{if(r.ok)location.replace(to);else document.body.insertAdjacentHTML('beforeend','<p role=\"alert\">This link no longer opens the app. Run rer dev again for a fresh one.</p>')})})()";
|
|
31
|
+
const PAGE_STYLE = 'body{margin:0;font:16px/1.5 system-ui,sans-serif;color:#17212e;background:#f5f7fa}main{max-width:36rem;margin:0 auto;padding:32px 16px}';
|
|
32
|
+
let hashes;
|
|
33
|
+
export async function exchangeHashes() {
|
|
34
|
+
return hashes ??= { script: `'sha256-${await digest(EXCHANGE_SCRIPT)}'`, style: `'sha256-${await digest(PAGE_STYLE)}'` };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const privateHeaders = { 'cache-control': 'no-store', 'referrer-policy': 'no-referrer', 'x-content-type-options': 'nosniff', 'x-robots-tag': 'noindex, nofollow' };
|
|
38
|
+
export function devNotFound() {
|
|
39
|
+
return new Response('Not found', { status: 404, headers: { ...privateHeaders, 'content-type': 'text/plain; charset=utf-8',
|
|
40
|
+
'content-security-policy': "default-src 'none'; script-src 'none'; style-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; object-src 'none'" } });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readGateCookie(request, name) {
|
|
44
|
+
const entries = (request.headers.get('cookie') || '').split(';').map(part => part.trim()).filter(part => part.startsWith(`${name}=`));
|
|
45
|
+
return entries.length === 1 ? entries[0].slice(name.length + 1) : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns the gate for a local runtime holding RER_DEV_KEY, or null. Null means
|
|
50
|
+
* every gated surface answers 404: the binding is missing, or the configured
|
|
51
|
+
* origin is not an explicit local loopback origin.
|
|
52
|
+
*/
|
|
53
|
+
export function devGate({ key, origin, local }) {
|
|
54
|
+
if (local !== true || !validKey(key) || typeof origin !== 'string') return null;
|
|
55
|
+
let url; try { url = new URL(origin); } catch { return null; }
|
|
56
|
+
if (url.origin !== origin || url.username || url.password || !isLoopbackHost(url.hostname)) return null;
|
|
57
|
+
let expected;
|
|
58
|
+
const cookieName = localCookieName('rer_dev_gate', origin);
|
|
59
|
+
const cookie = () => expected ??= cookieValue(key);
|
|
60
|
+
const setCookie = async () => `${cookieName}=${await cookie()}; Path=/; HttpOnly; SameSite=Strict; Max-Age=2592000${url.protocol === 'https:' ? '; Secure' : ''}`;
|
|
61
|
+
return Object.freeze({
|
|
62
|
+
cookieName,
|
|
63
|
+
async admits(request) {
|
|
64
|
+
const presented = request.headers.get(DEV_KEY_HEADER);
|
|
65
|
+
if (presented !== null) return constantEqual(presented, key);
|
|
66
|
+
const value = readGateCookie(request, cookieName);
|
|
67
|
+
return value !== null && constantEqual(value, await cookie());
|
|
68
|
+
},
|
|
69
|
+
setCookie,
|
|
70
|
+
// GET serves the exchange page; POST trades the key for the cookie.
|
|
71
|
+
async exchange(request) {
|
|
72
|
+
if (new URL(request.url).pathname !== DEV_KEY_PATH) return null;
|
|
73
|
+
if (request.method === 'GET' || request.method === 'HEAD') {
|
|
74
|
+
const { script, style } = await exchangeHashes();
|
|
75
|
+
const body = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex, nofollow"><title>Opening the local app</title><style>${PAGE_STYLE}</style></head><body><main><h1>Opening the local app</h1><p>Open the RER_OPEN link that rer prints on this computer.</p></main><script>${EXCHANGE_SCRIPT}</script></body></html>`;
|
|
76
|
+
return new Response(request.method === 'HEAD' ? null : body, { headers: { ...privateHeaders, 'content-type': 'text/html; charset=utf-8',
|
|
77
|
+
'content-security-policy': `default-src 'none'; script-src ${script}; style-src ${style}; connect-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'` } });
|
|
78
|
+
}
|
|
79
|
+
if (request.method !== 'POST' || request.headers.get('sec-fetch-site') === 'cross-site' || !request.headers.get('content-type')?.startsWith('application/json')) return devNotFound();
|
|
80
|
+
const text = await request.text();
|
|
81
|
+
let presented; try { presented = text.length <= 1024 ? JSON.parse(text)?.key : undefined; } catch {}
|
|
82
|
+
if (typeof presented !== 'string' || !constantEqual(presented, key)) return devNotFound();
|
|
83
|
+
return new Response(null, { status: 204, headers: { ...privateHeaders, 'set-cookie': await setCookie() } });
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Adopting operator auth
|
|
2
|
+
|
|
3
|
+
This module is server-only. Copy `modules/operator-auth/` into the product source,
|
|
4
|
+
import its server entry from server-owned files, and apply
|
|
5
|
+
`accounts/schema/operator-auth.sql` as a new numbered migration on the product's primary
|
|
6
|
+
D1 database. The integrator assigns the next migration number. Keep applied
|
|
7
|
+
migrations unchanged, plan an explicit existing-operator import for populated
|
|
8
|
+
systems, and never treat applying the schema as creating an owner or product data.
|
|
9
|
+
When upgrading an existing auth store, apply the separate additive
|
|
10
|
+
`accounts/schema/invitation-cancellation.sql` before enabling pending-invitation cancellation;
|
|
11
|
+
do not replay the complete schema. Its ledger is durable admission state, not
|
|
12
|
+
credential cleanup data. See the [migration and cancellation contract](../README.md).
|
|
13
|
+
|
|
14
|
+
Use the standard Worker secrets `OWNER_EMAIL`, `SESSION_SECRET`, and
|
|
15
|
+
`SETUP_TOKEN`. Generate independent random values of at least 32 bytes for the
|
|
16
|
+
two secrets; retain only `OWNER_EMAIL` after first-owner setup and remove
|
|
17
|
+
`SETUP_TOKEN` after bootstrap. `SESSION_SECRET` must remain stable for sessions.
|
|
18
|
+
They belong in server bindings, never `VITE_*`, rendered HTML, loaders, browser
|
|
19
|
+
storage, logs or responses. Bootstrap creates only the configured owner and its
|
|
20
|
+
session. It runs without mail and does not populate product tables. Invoke the
|
|
21
|
+
one-time `POST /auth/bootstrap` from an owner-controlled setup operation that
|
|
22
|
+
keeps `SETUP_TOKEN` server-side; never add the token to a frontend form or
|
|
23
|
+
client-side request payload.
|
|
24
|
+
|
|
25
|
+
For sign-in and invitations, configure the actual IF-056 sender through
|
|
26
|
+
`MAIL_PROVIDER`, `MAIL_API_KEY`, optional `MAIL_ACCOUNT_ID` for Cloudflare, and
|
|
27
|
+
`MAIL_FROM`. In server code, construct the sender with the real
|
|
28
|
+
`createMailSender({ provider, mode: 'production', apiKey, accountId })` from the
|
|
29
|
+
selected `modules/mail/index.mjs` and pass that object to `createOperatorAuth`.
|
|
30
|
+
Do not add another sender or select a fallback provider. Auth accepts only an
|
|
31
|
+
IF-056 result with `status: 'accepted'` (or the explicit local-only development
|
|
32
|
+
result); `rejected`, `rate_limited`, and `uncertain` revoke that link and return
|
|
33
|
+
`503 mail_unavailable`. An accepted provider submission is not proof that a
|
|
34
|
+
mailbox received the message. Issue a new link after a failed or uncertain
|
|
35
|
+
attempt; the auth core does not retry it.
|
|
36
|
+
|
|
37
|
+
`OWNER_EMAIL` is the first owner. The default roles are `owner`, `admin`, and
|
|
38
|
+
`member`. Configure `allowList` only as an additional admission restriction; it
|
|
39
|
+
does not grant access. Use the exported `rolePolicy` for deterministic product
|
|
40
|
+
policy variations while preserving the module's active-session, allow-list,
|
|
41
|
+
version-fence and last-owner checks.
|
|
42
|
+
|
|
43
|
+
Follow one of these integration recipes:
|
|
44
|
+
|
|
45
|
+
- [TanStack Start](./tanstack-start.md): keep the module and bindings in dynamic
|
|
46
|
+
server imports, check every private server function, and mount the auth HTTP
|
|
47
|
+
handler before protected Hono routes and Start.
|
|
48
|
+
- [Vite SPA](./vite-spa.md): keep auth, D1 and secrets in the Worker API; the SPA
|
|
49
|
+
uses same-origin auth and private APIs and never imports this module.
|
|
50
|
+
|
|
51
|
+
For first-owner, confirmation, role, revocation, rate-limit and mail-result
|
|
52
|
+
coverage, register the shared suite once from the seed's existing test file. See
|
|
53
|
+
[`../conformance/README.md`](../conformance/README.md) and its exact adapter
|
|
54
|
+
types. The adapter must exercise the adopting Worker and real D1, and the private
|
|
55
|
+
request must traverse the product's actual session gate.
|
|
56
|
+
|
|
57
|
+
## Identity providers, the sign-in page and local review
|
|
58
|
+
|
|
59
|
+
Mount `createSignInHandler(auth)` at the seed's sign-in path ahead of the app's
|
|
60
|
+
own routes; it replaces a seed's own sign-in page once the seed has checked its
|
|
61
|
+
`ACCEPTANCE.md`. Offer providers through `config.providers` (each with a preset:
|
|
62
|
+
`auth`, `google`, `microsoft` or `okta`) plus an independent
|
|
63
|
+
`OIDC_TRANSACTION_SECRET`, pass the runtime's `fetch`, apply
|
|
64
|
+
`identity/oidc/schema/oidc.sql`, and keep client secrets and the app's private
|
|
65
|
+
key in Worker bindings. `createAuthHandler(auth)` then also serves each
|
|
66
|
+
provider's `/auth/oidc/<id>/start` and `/auth/oidc/<id>/callback`; preserve all
|
|
67
|
+
`Set-Cookie` headers individually. See [identity/oidc](../identity/oidc/README.md)
|
|
68
|
+
for presets, pinning, email trust and reachability, and the module README for
|
|
69
|
+
domain rules, step-up and the `beforeMagicLink`/`afterConfirm` hooks. Microsoft
|
|
70
|
+
and Okta identify people only through `subjectEmails`, configured from trusted
|
|
71
|
+
server configuration, never callback input. The provider-key-free conformance
|
|
72
|
+
case checks only a first binding into an unbound active owner; the fake-provider
|
|
73
|
+
tests cover the protocol without live provider credentials.
|
|
74
|
+
|
|
75
|
+
`review` is a local-only mechanism. Keep `review.enabled` false in production;
|
|
76
|
+
bind a local review server to a loopback interface; use literal `localhost`,
|
|
77
|
+
`127.0.0.1`, or `[::1]` with a matching Host and exact Origin on POST. The core
|
|
78
|
+
does not trust forwarded headers. A matching Host is not network isolation.
|