@antseed/cli 0.1.179 → 0.1.180
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/README.md +2 -0
- package/dist/buyer-identities/store.d.ts +39 -2
- package/dist/buyer-identities/store.d.ts.map +1 -1
- package/dist/buyer-identities/store.js +99 -10
- package/dist/buyer-identities/store.js.map +1 -1
- package/dist/cli/commands/buyer/identity.d.ts +3 -1
- package/dist/cli/commands/buyer/identity.d.ts.map +1 -1
- package/dist/cli/commands/buyer/identity.js +55 -8
- package/dist/cli/commands/buyer/identity.js.map +1 -1
- package/dist/cli/commands/buyer/identity.test.d.ts +2 -0
- package/dist/cli/commands/buyer/identity.test.d.ts.map +1 -0
- package/dist/cli/commands/buyer/identity.test.js +168 -0
- package/dist/cli/commands/buyer/identity.test.js.map +1 -0
- package/dist/cli/commands/buyer/set-authorized-wallet.d.ts +10 -0
- package/dist/cli/commands/buyer/set-authorized-wallet.d.ts.map +1 -1
- package/dist/cli/commands/buyer/set-authorized-wallet.js +26 -4
- package/dist/cli/commands/buyer/set-authorized-wallet.js.map +1 -1
- package/dist/cli/commands/buyer/set-authorized-wallet.test.js +37 -2
- package/dist/cli/commands/buyer/set-authorized-wallet.test.js.map +1 -1
- package/dist/cli/commands/buyer/start.d.ts +15 -0
- package/dist/cli/commands/buyer/start.d.ts.map +1 -1
- package/dist/cli/commands/buyer/start.js +158 -48
- package/dist/cli/commands/buyer/start.js.map +1 -1
- package/dist/cli/commands/buyer/start.test.js +16 -1
- package/dist/cli/commands/buyer/start.test.js.map +1 -1
- package/dist/cli/commands/chat.d.ts +3 -0
- package/dist/cli/commands/chat.d.ts.map +1 -0
- package/dist/cli/commands/chat.js +189 -0
- package/dist/cli/commands/chat.js.map +1 -0
- package/dist/cli/commands/gateway/activity.d.ts +15 -0
- package/dist/cli/commands/gateway/activity.d.ts.map +1 -0
- package/dist/cli/commands/gateway/activity.js +233 -0
- package/dist/cli/commands/gateway/activity.js.map +1 -0
- package/dist/cli/commands/gateway/admin-token.d.ts +3 -0
- package/dist/cli/commands/gateway/admin-token.d.ts.map +1 -0
- package/dist/cli/commands/gateway/admin-token.js +90 -0
- package/dist/cli/commands/gateway/admin-token.js.map +1 -0
- package/dist/cli/commands/gateway/console-link.d.ts +20 -0
- package/dist/cli/commands/gateway/console-link.d.ts.map +1 -0
- package/dist/cli/commands/gateway/console-link.js +92 -0
- package/dist/cli/commands/gateway/console-link.js.map +1 -0
- package/dist/cli/commands/gateway/gateway-commands.test.d.ts +2 -0
- package/dist/cli/commands/gateway/gateway-commands.test.d.ts.map +1 -0
- package/dist/cli/commands/gateway/gateway-commands.test.js +230 -0
- package/dist/cli/commands/gateway/gateway-commands.test.js.map +1 -0
- package/dist/cli/commands/gateway/gateway-console-commands.test.d.ts +2 -0
- package/dist/cli/commands/gateway/gateway-console-commands.test.d.ts.map +1 -0
- package/dist/cli/commands/gateway/gateway-console-commands.test.js +373 -0
- package/dist/cli/commands/gateway/gateway-console-commands.test.js.map +1 -0
- package/dist/cli/commands/gateway/index.d.ts +8 -0
- package/dist/cli/commands/gateway/index.d.ts.map +1 -1
- package/dist/cli/commands/gateway/index.js +93 -14
- package/dist/cli/commands/gateway/index.js.map +1 -1
- package/dist/cli/commands/gateway/key.d.ts +4 -0
- package/dist/cli/commands/gateway/key.d.ts.map +1 -1
- package/dist/cli/commands/gateway/key.js +297 -182
- package/dist/cli/commands/gateway/key.js.map +1 -1
- package/dist/cli/commands/gateway/member.d.ts +3 -0
- package/dist/cli/commands/gateway/member.d.ts.map +1 -0
- package/dist/cli/commands/gateway/member.js +302 -0
- package/dist/cli/commands/gateway/member.js.map +1 -0
- package/dist/cli/commands/gateway/migrate.d.ts +18 -0
- package/dist/cli/commands/gateway/migrate.d.ts.map +1 -0
- package/dist/cli/commands/gateway/migrate.js +158 -0
- package/dist/cli/commands/gateway/migrate.js.map +1 -0
- package/dist/cli/commands/gateway/operator.d.ts +11 -0
- package/dist/cli/commands/gateway/operator.d.ts.map +1 -0
- package/dist/cli/commands/gateway/operator.js +199 -0
- package/dist/cli/commands/gateway/operator.js.map +1 -0
- package/dist/cli/commands/gateway/operator.test.d.ts +2 -0
- package/dist/cli/commands/gateway/operator.test.d.ts.map +1 -0
- package/dist/cli/commands/gateway/operator.test.js +151 -0
- package/dist/cli/commands/gateway/operator.test.js.map +1 -0
- package/dist/cli/commands/gateway/peer-list.d.ts +3 -0
- package/dist/cli/commands/gateway/peer-list.d.ts.map +1 -0
- package/dist/cli/commands/gateway/peer-list.js +107 -0
- package/dist/cli/commands/gateway/peer-list.js.map +1 -0
- package/dist/cli/commands/gateway/policy-options.d.ts +23 -0
- package/dist/cli/commands/gateway/policy-options.d.ts.map +1 -0
- package/dist/cli/commands/gateway/policy-options.js +156 -0
- package/dist/cli/commands/gateway/policy-options.js.map +1 -0
- package/dist/cli/commands/gateway/preset.d.ts +3 -0
- package/dist/cli/commands/gateway/preset.d.ts.map +1 -0
- package/dist/cli/commands/gateway/preset.js +181 -0
- package/dist/cli/commands/gateway/preset.js.map +1 -0
- package/dist/cli/commands/gateway/routing.d.ts +3 -0
- package/dist/cli/commands/gateway/routing.d.ts.map +1 -0
- package/dist/cli/commands/gateway/routing.js +110 -0
- package/dist/cli/commands/gateway/routing.js.map +1 -0
- package/dist/cli/commands/gateway/settings.d.ts +4 -0
- package/dist/cli/commands/gateway/settings.d.ts.map +1 -0
- package/dist/cli/commands/gateway/settings.js +314 -0
- package/dist/cli/commands/gateway/settings.js.map +1 -0
- package/dist/cli/commands/gateway/shared.d.ts +55 -6
- package/dist/cli/commands/gateway/shared.d.ts.map +1 -1
- package/dist/cli/commands/gateway/shared.js +206 -26
- package/dist/cli/commands/gateway/shared.js.map +1 -1
- package/dist/cli/commands/gateway/workspace.d.ts +3 -0
- package/dist/cli/commands/gateway/workspace.d.ts.map +1 -0
- package/dist/cli/commands/gateway/workspace.js +250 -0
- package/dist/cli/commands/gateway/workspace.js.map +1 -0
- package/dist/cli/commands/tunnel/start.d.ts.map +1 -1
- package/dist/cli/commands/tunnel/start.js +23 -5
- package/dist/cli/commands/tunnel/start.js.map +1 -1
- package/dist/cli/index.js +12 -44
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/program.d.ts +4 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +49 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/program.test.d.ts +2 -0
- package/dist/cli/program.test.d.ts.map +1 -0
- package/dist/cli/program.test.js +38 -0
- package/dist/cli/program.test.js.map +1 -0
- package/dist/cli/prompt-hidden.d.ts +3 -0
- package/dist/cli/prompt-hidden.d.ts.map +1 -0
- package/dist/cli/prompt-hidden.js +38 -0
- package/dist/cli/prompt-hidden.js.map +1 -0
- package/dist/cli/wallet-passphrase.d.ts +48 -0
- package/dist/cli/wallet-passphrase.d.ts.map +1 -0
- package/dist/cli/wallet-passphrase.js +119 -0
- package/dist/cli/wallet-passphrase.js.map +1 -0
- package/dist/cli/wallet-passphrase.test.d.ts +2 -0
- package/dist/cli/wallet-passphrase.test.d.ts.map +1 -0
- package/dist/cli/wallet-passphrase.test.js +79 -0
- package/dist/cli/wallet-passphrase.test.js.map +1 -0
- package/dist/config/effective.d.ts +12 -0
- package/dist/config/effective.d.ts.map +1 -1
- package/dist/config/effective.js +39 -6
- package/dist/config/effective.js.map +1 -1
- package/dist/config/effective.test.js +9 -1
- package/dist/config/effective.test.js.map +1 -1
- package/dist/config/loader.d.ts +6 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +12 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/config/loader.test.js +7 -1
- package/dist/config/loader.test.js.map +1 -1
- package/dist/config/types.d.ts +14 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/validation.d.ts.map +1 -1
- package/dist/config/validation.js +19 -0
- package/dist/config/validation.js.map +1 -1
- package/dist/gateway/accounting.d.ts +33 -3
- package/dist/gateway/accounting.d.ts.map +1 -1
- package/dist/gateway/accounting.js +86 -7
- package/dist/gateway/accounting.js.map +1 -1
- package/dist/gateway/admission-benchmark.test.d.ts +2 -0
- package/dist/gateway/admission-benchmark.test.d.ts.map +1 -0
- package/dist/gateway/admission-benchmark.test.js +75 -0
- package/dist/gateway/admission-benchmark.test.js.map +1 -0
- package/dist/gateway/auth/auth.test.d.ts +4 -0
- package/dist/gateway/auth/auth.test.d.ts.map +1 -0
- package/dist/gateway/auth/auth.test.js +337 -0
- package/dist/gateway/auth/auth.test.js.map +1 -0
- package/dist/gateway/auth/credentials.test.d.ts +2 -0
- package/dist/gateway/auth/credentials.test.d.ts.map +1 -0
- package/dist/gateway/auth/credentials.test.js +132 -0
- package/dist/gateway/auth/credentials.test.js.map +1 -0
- package/dist/gateway/auth/db.d.ts +156 -0
- package/dist/gateway/auth/db.d.ts.map +1 -0
- package/dist/gateway/auth/db.js +390 -0
- package/dist/gateway/auth/db.js.map +1 -0
- package/dist/gateway/auth/hardening.test.d.ts +2 -0
- package/dist/gateway/auth/hardening.test.d.ts.map +1 -0
- package/dist/gateway/auth/hardening.test.js +155 -0
- package/dist/gateway/auth/hardening.test.js.map +1 -0
- package/dist/gateway/auth/http.d.ts +53 -0
- package/dist/gateway/auth/http.d.ts.map +1 -0
- package/dist/gateway/auth/http.js +165 -0
- package/dist/gateway/auth/http.js.map +1 -0
- package/dist/gateway/auth/index.d.ts +26 -0
- package/dist/gateway/auth/index.d.ts.map +1 -0
- package/dist/gateway/auth/index.js +1014 -0
- package/dist/gateway/auth/index.js.map +1 -0
- package/dist/gateway/auth/oidc.d.ts +57 -0
- package/dist/gateway/auth/oidc.d.ts.map +1 -0
- package/dist/gateway/auth/oidc.js +159 -0
- package/dist/gateway/auth/oidc.js.map +1 -0
- package/dist/gateway/auth/oidc.test.d.ts +2 -0
- package/dist/gateway/auth/oidc.test.d.ts.map +1 -0
- package/dist/gateway/auth/oidc.test.js +223 -0
- package/dist/gateway/auth/oidc.test.js.map +1 -0
- package/dist/gateway/auth/reauth.test.d.ts +2 -0
- package/dist/gateway/auth/reauth.test.d.ts.map +1 -0
- package/dist/gateway/auth/reauth.test.js +108 -0
- package/dist/gateway/auth/reauth.test.js.map +1 -0
- package/dist/gateway/auth/store-integration.test.d.ts +2 -0
- package/dist/gateway/auth/store-integration.test.d.ts.map +1 -0
- package/dist/gateway/auth/store-integration.test.js +100 -0
- package/dist/gateway/auth/store-integration.test.js.map +1 -0
- package/dist/gateway/auth/test-helpers.d.ts +167 -0
- package/dist/gateway/auth/test-helpers.d.ts.map +1 -0
- package/dist/gateway/auth/test-helpers.js +368 -0
- package/dist/gateway/auth/test-helpers.js.map +1 -0
- package/dist/gateway/auth/types.d.ts +145 -0
- package/dist/gateway/auth/types.d.ts.map +1 -0
- package/dist/gateway/auth/types.js +2 -0
- package/dist/gateway/auth/types.js.map +1 -0
- package/dist/gateway/auth/wallet.d.ts +15 -0
- package/dist/gateway/auth/wallet.d.ts.map +1 -0
- package/dist/gateway/auth/wallet.js +43 -0
- package/dist/gateway/auth/wallet.js.map +1 -0
- package/dist/gateway/bundle.d.ts +127 -0
- package/dist/gateway/bundle.d.ts.map +1 -0
- package/dist/gateway/bundle.js +604 -0
- package/dist/gateway/bundle.js.map +1 -0
- package/dist/gateway/bundle.test.d.ts +2 -0
- package/dist/gateway/bundle.test.d.ts.map +1 -0
- package/dist/gateway/bundle.test.js +277 -0
- package/dist/gateway/bundle.test.js.map +1 -0
- package/dist/gateway/buyer-control.d.ts +33 -0
- package/dist/gateway/buyer-control.d.ts.map +1 -0
- package/dist/gateway/buyer-control.js +93 -0
- package/dist/gateway/buyer-control.js.map +1 -0
- package/dist/gateway/buyer-policy-probe.d.ts +41 -0
- package/dist/gateway/buyer-policy-probe.d.ts.map +1 -0
- package/dist/gateway/buyer-policy-probe.js +99 -0
- package/dist/gateway/buyer-policy-probe.js.map +1 -0
- package/dist/gateway/buyer-policy-probe.test.d.ts +2 -0
- package/dist/gateway/buyer-policy-probe.test.d.ts.map +1 -0
- package/dist/gateway/buyer-policy-probe.test.js +128 -0
- package/dist/gateway/buyer-policy-probe.test.js.map +1 -0
- package/dist/gateway/buyer-process.d.ts +30 -0
- package/dist/gateway/buyer-process.d.ts.map +1 -0
- package/dist/gateway/buyer-process.js +101 -0
- package/dist/gateway/buyer-process.js.map +1 -0
- package/dist/gateway/buyer-process.test.d.ts +2 -0
- package/dist/gateway/buyer-process.test.d.ts.map +1 -0
- package/dist/gateway/buyer-process.test.js +112 -0
- package/dist/gateway/buyer-process.test.js.map +1 -0
- package/dist/gateway/chat/routes.d.ts +45 -0
- package/dist/gateway/chat/routes.d.ts.map +1 -0
- package/dist/gateway/chat/routes.js +439 -0
- package/dist/gateway/chat/routes.js.map +1 -0
- package/dist/gateway/chat/routes.test.d.ts +2 -0
- package/dist/gateway/chat/routes.test.d.ts.map +1 -0
- package/dist/gateway/chat/routes.test.js +304 -0
- package/dist/gateway/chat/routes.test.js.map +1 -0
- package/dist/gateway/chat/tools.d.ts +35 -0
- package/dist/gateway/chat/tools.d.ts.map +1 -0
- package/dist/gateway/chat/tools.js +347 -0
- package/dist/gateway/chat/tools.js.map +1 -0
- package/dist/gateway/chat/tools.test.d.ts +2 -0
- package/dist/gateway/chat/tools.test.d.ts.map +1 -0
- package/dist/gateway/chat/tools.test.js +136 -0
- package/dist/gateway/chat/tools.test.js.map +1 -0
- package/dist/gateway/console-api/access.d.ts +52 -0
- package/dist/gateway/console-api/access.d.ts.map +1 -0
- package/dist/gateway/console-api/access.js +171 -0
- package/dist/gateway/console-api/access.js.map +1 -0
- package/dist/gateway/console-api/deps.d.ts +52 -0
- package/dist/gateway/console-api/deps.d.ts.map +1 -0
- package/dist/gateway/console-api/deps.js +2 -0
- package/dist/gateway/console-api/deps.js.map +1 -0
- package/dist/gateway/console-api/handlers/admin-tokens.d.ts +8 -0
- package/dist/gateway/console-api/handlers/admin-tokens.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/admin-tokens.js +48 -0
- package/dist/gateway/console-api/handlers/admin-tokens.js.map +1 -0
- package/dist/gateway/console-api/handlers/audit.d.ts +8 -0
- package/dist/gateway/console-api/handlers/audit.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/audit.js +19 -0
- package/dist/gateway/console-api/handlers/audit.js.map +1 -0
- package/dist/gateway/console-api/handlers/audit.test.d.ts +2 -0
- package/dist/gateway/console-api/handlers/audit.test.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/audit.test.js +241 -0
- package/dist/gateway/console-api/handlers/audit.test.js.map +1 -0
- package/dist/gateway/console-api/handlers/data-settings.test.d.ts +2 -0
- package/dist/gateway/console-api/handlers/data-settings.test.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/data-settings.test.js +264 -0
- package/dist/gateway/console-api/handlers/data-settings.test.js.map +1 -0
- package/dist/gateway/console-api/handlers/index.d.ts +4 -0
- package/dist/gateway/console-api/handlers/index.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/index.js +24 -0
- package/dist/gateway/console-api/handlers/index.js.map +1 -0
- package/dist/gateway/console-api/handlers/keys.d.ts +13 -0
- package/dist/gateway/console-api/handlers/keys.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/keys.js +128 -0
- package/dist/gateway/console-api/handlers/keys.js.map +1 -0
- package/dist/gateway/console-api/handlers/keys.test.d.ts +2 -0
- package/dist/gateway/console-api/handlers/keys.test.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/keys.test.js +166 -0
- package/dist/gateway/console-api/handlers/keys.test.js.map +1 -0
- package/dist/gateway/console-api/handlers/network-buyer.d.ts +22 -0
- package/dist/gateway/console-api/handlers/network-buyer.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/network-buyer.js +39 -0
- package/dist/gateway/console-api/handlers/network-buyer.js.map +1 -0
- package/dist/gateway/console-api/handlers/network-mapping.d.ts +33 -0
- package/dist/gateway/console-api/handlers/network-mapping.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/network-mapping.js +219 -0
- package/dist/gateway/console-api/handlers/network-mapping.js.map +1 -0
- package/dist/gateway/console-api/handlers/network-test-helpers.d.ts +35 -0
- package/dist/gateway/console-api/handlers/network-test-helpers.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/network-test-helpers.js +121 -0
- package/dist/gateway/console-api/handlers/network-test-helpers.js.map +1 -0
- package/dist/gateway/console-api/handlers/network.d.ts +15 -0
- package/dist/gateway/console-api/handlers/network.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/network.js +57 -0
- package/dist/gateway/console-api/handlers/network.js.map +1 -0
- package/dist/gateway/console-api/handlers/network.test.d.ts +2 -0
- package/dist/gateway/console-api/handlers/network.test.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/network.test.js +383 -0
- package/dist/gateway/console-api/handlers/network.test.js.map +1 -0
- package/dist/gateway/console-api/handlers/org.d.ts +10 -0
- package/dist/gateway/console-api/handlers/org.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/org.js +150 -0
- package/dist/gateway/console-api/handlers/org.js.map +1 -0
- package/dist/gateway/console-api/handlers/peer-lists.d.ts +5 -0
- package/dist/gateway/console-api/handlers/peer-lists.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/peer-lists.js +43 -0
- package/dist/gateway/console-api/handlers/peer-lists.js.map +1 -0
- package/dist/gateway/console-api/handlers/presets.d.ts +9 -0
- package/dist/gateway/console-api/handlers/presets.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/presets.js +79 -0
- package/dist/gateway/console-api/handlers/presets.js.map +1 -0
- package/dist/gateway/console-api/handlers/routing.d.ts +5 -0
- package/dist/gateway/console-api/handlers/routing.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/routing.js +20 -0
- package/dist/gateway/console-api/handlers/routing.js.map +1 -0
- package/dist/gateway/console-api/handlers/settings.d.ts +10 -0
- package/dist/gateway/console-api/handlers/settings.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/settings.js +38 -0
- package/dist/gateway/console-api/handlers/settings.js.map +1 -0
- package/dist/gateway/console-api/handlers/status.d.ts +9 -0
- package/dist/gateway/console-api/handlers/status.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/status.js +47 -0
- package/dist/gateway/console-api/handlers/status.js.map +1 -0
- package/dist/gateway/console-api/handlers/status.test.d.ts +2 -0
- package/dist/gateway/console-api/handlers/status.test.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/status.test.js +82 -0
- package/dist/gateway/console-api/handlers/status.test.js.map +1 -0
- package/dist/gateway/console-api/handlers/usage.d.ts +10 -0
- package/dist/gateway/console-api/handlers/usage.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/usage.js +68 -0
- package/dist/gateway/console-api/handlers/usage.js.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-card-link.d.ts +29 -0
- package/dist/gateway/console-api/handlers/wallet-card-link.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-card-link.js +46 -0
- package/dist/gateway/console-api/handlers/wallet-card-link.js.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-chain.d.ts +26 -0
- package/dist/gateway/console-api/handlers/wallet-chain.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-chain.js +80 -0
- package/dist/gateway/console-api/handlers/wallet-chain.js.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-mapping.d.ts +45 -0
- package/dist/gateway/console-api/handlers/wallet-mapping.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-mapping.js +202 -0
- package/dist/gateway/console-api/handlers/wallet-mapping.js.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-operator.d.ts +41 -0
- package/dist/gateway/console-api/handlers/wallet-operator.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-operator.js +198 -0
- package/dist/gateway/console-api/handlers/wallet-operator.js.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-operator.test.d.ts +2 -0
- package/dist/gateway/console-api/handlers/wallet-operator.test.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/wallet-operator.test.js +195 -0
- package/dist/gateway/console-api/handlers/wallet-operator.test.js.map +1 -0
- package/dist/gateway/console-api/handlers/wallet.d.ts +61 -0
- package/dist/gateway/console-api/handlers/wallet.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/wallet.js +221 -0
- package/dist/gateway/console-api/handlers/wallet.js.map +1 -0
- package/dist/gateway/console-api/handlers/wallet.test.d.ts +2 -0
- package/dist/gateway/console-api/handlers/wallet.test.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/wallet.test.js +581 -0
- package/dist/gateway/console-api/handlers/wallet.test.js.map +1 -0
- package/dist/gateway/console-api/handlers/workspaces.d.ts +12 -0
- package/dist/gateway/console-api/handlers/workspaces.d.ts.map +1 -0
- package/dist/gateway/console-api/handlers/workspaces.js +96 -0
- package/dist/gateway/console-api/handlers/workspaces.js.map +1 -0
- package/dist/gateway/console-api/operator-types.d.ts +68 -0
- package/dist/gateway/console-api/operator-types.d.ts.map +1 -0
- package/dist/gateway/console-api/operator-types.js +21 -0
- package/dist/gateway/console-api/operator-types.js.map +1 -0
- package/dist/gateway/console-api/router.d.ts +97 -0
- package/dist/gateway/console-api/router.d.ts.map +1 -0
- package/dist/gateway/console-api/router.js +69 -0
- package/dist/gateway/console-api/router.js.map +1 -0
- package/dist/gateway/console-api/routes.test.d.ts +2 -0
- package/dist/gateway/console-api/routes.test.d.ts.map +1 -0
- package/dist/gateway/console-api/routes.test.js +331 -0
- package/dist/gateway/console-api/routes.test.js.map +1 -0
- package/dist/gateway/console-api/serialize.d.ts +33 -0
- package/dist/gateway/console-api/serialize.d.ts.map +1 -0
- package/dist/gateway/console-api/serialize.js +179 -0
- package/dist/gateway/console-api/serialize.js.map +1 -0
- package/dist/gateway/console-api/server.d.ts +38 -0
- package/dist/gateway/console-api/server.d.ts.map +1 -0
- package/dist/gateway/console-api/server.js +308 -0
- package/dist/gateway/console-api/server.js.map +1 -0
- package/dist/gateway/console-api/server.test.d.ts +2 -0
- package/dist/gateway/console-api/server.test.d.ts.map +1 -0
- package/dist/gateway/console-api/server.test.js +149 -0
- package/dist/gateway/console-api/server.test.js.map +1 -0
- package/dist/gateway/console-api/test-support.d.ts +59 -0
- package/dist/gateway/console-api/test-support.d.ts.map +1 -0
- package/dist/gateway/console-api/test-support.js +120 -0
- package/dist/gateway/console-api/test-support.js.map +1 -0
- package/dist/gateway/console-api/types.d.ts +779 -0
- package/dist/gateway/console-api/types.d.ts.map +1 -0
- package/dist/gateway/console-api/types.js +6 -0
- package/dist/gateway/console-api/types.js.map +1 -0
- package/dist/gateway/console-location.d.ts +21 -0
- package/dist/gateway/console-location.d.ts.map +1 -0
- package/dist/gateway/console-location.js +41 -0
- package/dist/gateway/console-location.js.map +1 -0
- package/dist/gateway/console-recovery.d.ts +34 -0
- package/dist/gateway/console-recovery.d.ts.map +1 -0
- package/dist/gateway/console-recovery.js +94 -0
- package/dist/gateway/console-recovery.js.map +1 -0
- package/dist/gateway/console-recovery.test.d.ts +2 -0
- package/dist/gateway/console-recovery.test.d.ts.map +1 -0
- package/dist/gateway/console-recovery.test.js +106 -0
- package/dist/gateway/console-recovery.test.js.map +1 -0
- package/dist/gateway/console.d.ts +18 -0
- package/dist/gateway/console.d.ts.map +1 -0
- package/dist/gateway/console.js +101 -0
- package/dist/gateway/console.js.map +1 -0
- package/dist/gateway/console.test.d.ts +2 -0
- package/dist/gateway/console.test.d.ts.map +1 -0
- package/dist/gateway/console.test.js +165 -0
- package/dist/gateway/console.test.js.map +1 -0
- package/dist/gateway/errors.d.ts +3 -0
- package/dist/gateway/errors.d.ts.map +1 -0
- package/dist/gateway/errors.js +5 -0
- package/dist/gateway/errors.js.map +1 -0
- package/dist/gateway/exposure.d.ts +33 -0
- package/dist/gateway/exposure.d.ts.map +1 -0
- package/dist/gateway/exposure.js +146 -0
- package/dist/gateway/exposure.js.map +1 -0
- package/dist/gateway/exposure.test.d.ts +2 -0
- package/dist/gateway/exposure.test.d.ts.map +1 -0
- package/dist/gateway/exposure.test.js +93 -0
- package/dist/gateway/exposure.test.js.map +1 -0
- package/dist/gateway/gateway-policy.test.d.ts +2 -0
- package/dist/gateway/gateway-policy.test.d.ts.map +1 -0
- package/dist/gateway/gateway-policy.test.js +404 -0
- package/dist/gateway/gateway-policy.test.js.map +1 -0
- package/dist/gateway/gateway.test.js +47 -25
- package/dist/gateway/gateway.test.js.map +1 -1
- package/dist/gateway/keys.d.ts +2 -0
- package/dist/gateway/keys.d.ts.map +1 -1
- package/dist/gateway/keys.js +2 -0
- package/dist/gateway/keys.js.map +1 -1
- package/dist/gateway/limits.d.ts +32 -8
- package/dist/gateway/limits.d.ts.map +1 -1
- package/dist/gateway/limits.js +27 -11
- package/dist/gateway/limits.js.map +1 -1
- package/dist/gateway/observability.d.ts +51 -0
- package/dist/gateway/observability.d.ts.map +1 -0
- package/dist/gateway/observability.js +136 -0
- package/dist/gateway/observability.js.map +1 -0
- package/dist/gateway/policy-resolver.d.ts +73 -0
- package/dist/gateway/policy-resolver.d.ts.map +1 -0
- package/dist/gateway/policy-resolver.js +142 -0
- package/dist/gateway/policy-resolver.js.map +1 -0
- package/dist/gateway/policy-resolver.test.d.ts +2 -0
- package/dist/gateway/policy-resolver.test.d.ts.map +1 -0
- package/dist/gateway/policy-resolver.test.js +273 -0
- package/dist/gateway/policy-resolver.test.js.map +1 -0
- package/dist/gateway/request-errors.test.d.ts +2 -0
- package/dist/gateway/request-errors.test.d.ts.map +1 -0
- package/dist/gateway/request-errors.test.js +108 -0
- package/dist/gateway/request-errors.test.js.map +1 -0
- package/dist/gateway/request-shaping.d.ts +25 -0
- package/dist/gateway/request-shaping.d.ts.map +1 -0
- package/dist/gateway/request-shaping.js +66 -0
- package/dist/gateway/request-shaping.js.map +1 -0
- package/dist/gateway/response-usage.d.ts +35 -0
- package/dist/gateway/response-usage.d.ts.map +1 -0
- package/dist/gateway/response-usage.js +167 -0
- package/dist/gateway/response-usage.js.map +1 -0
- package/dist/gateway/response-usage.test.d.ts +2 -0
- package/dist/gateway/response-usage.test.d.ts.map +1 -0
- package/dist/gateway/response-usage.test.js +78 -0
- package/dist/gateway/response-usage.test.js.map +1 -0
- package/dist/gateway/runtime.d.ts +25 -0
- package/dist/gateway/runtime.d.ts.map +1 -1
- package/dist/gateway/runtime.js +39 -19
- package/dist/gateway/runtime.js.map +1 -1
- package/dist/gateway/server.d.ts +68 -2
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +532 -116
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/services/audit.d.ts +16 -0
- package/dist/gateway/services/audit.d.ts.map +1 -0
- package/dist/gateway/services/audit.js +18 -0
- package/dist/gateway/services/audit.js.map +1 -0
- package/dist/gateway/services/context.d.ts +66 -0
- package/dist/gateway/services/context.d.ts.map +1 -0
- package/dist/gateway/services/context.js +52 -0
- package/dist/gateway/services/context.js.map +1 -0
- package/dist/gateway/services/keys.d.ts +78 -0
- package/dist/gateway/services/keys.d.ts.map +1 -0
- package/dist/gateway/services/keys.js +232 -0
- package/dist/gateway/services/keys.js.map +1 -0
- package/dist/gateway/services/members.d.ts +72 -0
- package/dist/gateway/services/members.d.ts.map +1 -0
- package/dist/gateway/services/members.js +161 -0
- package/dist/gateway/services/members.js.map +1 -0
- package/dist/gateway/services/network.d.ts +36 -0
- package/dist/gateway/services/network.d.ts.map +1 -0
- package/dist/gateway/services/network.js +147 -0
- package/dist/gateway/services/network.js.map +1 -0
- package/dist/gateway/services/operator.d.ts +98 -0
- package/dist/gateway/services/operator.d.ts.map +1 -0
- package/dist/gateway/services/operator.js +170 -0
- package/dist/gateway/services/operator.js.map +1 -0
- package/dist/gateway/services/operator.test.d.ts +2 -0
- package/dist/gateway/services/operator.test.d.ts.map +1 -0
- package/dist/gateway/services/operator.test.js +184 -0
- package/dist/gateway/services/operator.test.js.map +1 -0
- package/dist/gateway/services/peer-lists.d.ts +20 -0
- package/dist/gateway/services/peer-lists.d.ts.map +1 -0
- package/dist/gateway/services/peer-lists.js +134 -0
- package/dist/gateway/services/peer-lists.js.map +1 -0
- package/dist/gateway/services/presets.d.ts +34 -0
- package/dist/gateway/services/presets.d.ts.map +1 -0
- package/dist/gateway/services/presets.js +134 -0
- package/dist/gateway/services/presets.js.map +1 -0
- package/dist/gateway/services/routing.d.ts +13 -0
- package/dist/gateway/services/routing.d.ts.map +1 -0
- package/dist/gateway/services/routing.js +18 -0
- package/dist/gateway/services/routing.js.map +1 -0
- package/dist/gateway/services/services.test.d.ts +2 -0
- package/dist/gateway/services/services.test.d.ts.map +1 -0
- package/dist/gateway/services/services.test.js +476 -0
- package/dist/gateway/services/services.test.js.map +1 -0
- package/dist/gateway/services/settings.d.ts +68 -0
- package/dist/gateway/services/settings.d.ts.map +1 -0
- package/dist/gateway/services/settings.js +580 -0
- package/dist/gateway/services/settings.js.map +1 -0
- package/dist/gateway/services/tokens.d.ts +29 -0
- package/dist/gateway/services/tokens.d.ts.map +1 -0
- package/dist/gateway/services/tokens.js +48 -0
- package/dist/gateway/services/tokens.js.map +1 -0
- package/dist/gateway/services/usage.d.ts +55 -0
- package/dist/gateway/services/usage.d.ts.map +1 -0
- package/dist/gateway/services/usage.js +225 -0
- package/dist/gateway/services/usage.js.map +1 -0
- package/dist/gateway/services/wallet-address-env.test.d.ts +2 -0
- package/dist/gateway/services/wallet-address-env.test.d.ts.map +1 -0
- package/dist/gateway/services/wallet-address-env.test.js +119 -0
- package/dist/gateway/services/wallet-address-env.test.js.map +1 -0
- package/dist/gateway/services/wallet-address.d.ts +40 -0
- package/dist/gateway/services/wallet-address.d.ts.map +1 -0
- package/dist/gateway/services/wallet-address.js +116 -0
- package/dist/gateway/services/wallet-address.js.map +1 -0
- package/dist/gateway/services/wallet-address.test.d.ts +2 -0
- package/dist/gateway/services/wallet-address.test.d.ts.map +1 -0
- package/dist/gateway/services/wallet-address.test.js +84 -0
- package/dist/gateway/services/wallet-address.test.js.map +1 -0
- package/dist/gateway/services/workspaces.d.ts +75 -0
- package/dist/gateway/services/workspaces.d.ts.map +1 -0
- package/dist/gateway/services/workspaces.js +228 -0
- package/dist/gateway/services/workspaces.js.map +1 -0
- package/dist/gateway/spend-durability.test.d.ts +2 -0
- package/dist/gateway/spend-durability.test.d.ts.map +1 -0
- package/dist/gateway/spend-durability.test.js +330 -0
- package/dist/gateway/spend-durability.test.js.map +1 -0
- package/dist/gateway/spend-feed.d.ts +11 -1
- package/dist/gateway/spend-feed.d.ts.map +1 -1
- package/dist/gateway/spend-feed.js +17 -4
- package/dist/gateway/spend-feed.js.map +1 -1
- package/dist/gateway/spend-feed.test.d.ts +2 -0
- package/dist/gateway/spend-feed.test.d.ts.map +1 -0
- package/dist/gateway/spend-feed.test.js +34 -0
- package/dist/gateway/spend-feed.test.js.map +1 -0
- package/dist/gateway/store-data.test.d.ts +2 -0
- package/dist/gateway/store-data.test.d.ts.map +1 -0
- package/dist/gateway/store-data.test.js +346 -0
- package/dist/gateway/store-data.test.js.map +1 -0
- package/dist/gateway/store-migrations.d.ts +6 -0
- package/dist/gateway/store-migrations.d.ts.map +1 -0
- package/dist/gateway/store-migrations.js +301 -0
- package/dist/gateway/store-migrations.js.map +1 -0
- package/dist/gateway/store.d.ts +595 -10
- package/dist/gateway/store.d.ts.map +1 -1
- package/dist/gateway/store.js +1242 -120
- package/dist/gateway/store.js.map +1 -1
- package/dist/gateway/store.test.d.ts +2 -0
- package/dist/gateway/store.test.d.ts.map +1 -0
- package/dist/gateway/store.test.js +185 -0
- package/dist/gateway/store.test.js.map +1 -0
- package/dist/proxy/buyer-chain-reader.d.ts +68 -0
- package/dist/proxy/buyer-chain-reader.d.ts.map +1 -0
- package/dist/proxy/buyer-chain-reader.js +152 -0
- package/dist/proxy/buyer-chain-reader.js.map +1 -0
- package/dist/proxy/buyer-chain-reader.test.d.ts +2 -0
- package/dist/proxy/buyer-chain-reader.test.d.ts.map +1 -0
- package/dist/proxy/buyer-chain-reader.test.js +102 -0
- package/dist/proxy/buyer-chain-reader.test.js.map +1 -0
- package/dist/proxy/buyer-proxy.d.ts +169 -3
- package/dist/proxy/buyer-proxy.d.ts.map +1 -1
- package/dist/proxy/buyer-proxy.js +873 -117
- package/dist/proxy/buyer-proxy.js.map +1 -1
- package/dist/proxy/buyer-proxy.test.js +31 -0
- package/dist/proxy/buyer-proxy.test.js.map +1 -1
- package/dist/proxy/chain-read-cache.d.ts +47 -0
- package/dist/proxy/chain-read-cache.d.ts.map +1 -0
- package/dist/proxy/chain-read-cache.js +110 -0
- package/dist/proxy/chain-read-cache.js.map +1 -0
- package/dist/proxy/chain-read-cache.test.d.ts +2 -0
- package/dist/proxy/chain-read-cache.test.d.ts.map +1 -0
- package/dist/proxy/chain-read-cache.test.js +75 -0
- package/dist/proxy/chain-read-cache.test.js.map +1 -0
- package/dist/proxy/chain-rpc-test-support.d.ts +31 -0
- package/dist/proxy/chain-rpc-test-support.d.ts.map +1 -0
- package/dist/proxy/chain-rpc-test-support.js +136 -0
- package/dist/proxy/chain-rpc-test-support.js.map +1 -0
- package/dist/proxy/chain-rpc.d.ts +121 -0
- package/dist/proxy/chain-rpc.d.ts.map +1 -0
- package/dist/proxy/chain-rpc.js +420 -0
- package/dist/proxy/chain-rpc.js.map +1 -0
- package/dist/proxy/chain-rpc.test.d.ts +2 -0
- package/dist/proxy/chain-rpc.test.d.ts.map +1 -0
- package/dist/proxy/chain-rpc.test.js +180 -0
- package/dist/proxy/chain-rpc.test.js.map +1 -0
- package/dist/proxy/deposit-watch-hub.d.ts +47 -0
- package/dist/proxy/deposit-watch-hub.d.ts.map +1 -0
- package/dist/proxy/deposit-watch-hub.js +111 -0
- package/dist/proxy/deposit-watch-hub.js.map +1 -0
- package/dist/proxy/deposit-watch-hub.test.d.ts +2 -0
- package/dist/proxy/deposit-watch-hub.test.d.ts.map +1 -0
- package/dist/proxy/deposit-watch-hub.test.js +133 -0
- package/dist/proxy/deposit-watch-hub.test.js.map +1 -0
- package/dist/proxy/deposit-watcher.d.ts +44 -4
- package/dist/proxy/deposit-watcher.d.ts.map +1 -1
- package/dist/proxy/deposit-watcher.js +76 -11
- package/dist/proxy/deposit-watcher.js.map +1 -1
- package/dist/proxy/deposit-watcher.test.js +54 -1
- package/dist/proxy/deposit-watcher.test.js.map +1 -1
- package/dist/proxy/gateway-policy.test.d.ts +2 -0
- package/dist/proxy/gateway-policy.test.d.ts.map +1 -0
- package/dist/proxy/gateway-policy.test.js +576 -0
- package/dist/proxy/gateway-policy.test.js.map +1 -0
- package/dist/proxy/network-models.d.ts +2 -0
- package/dist/proxy/network-models.d.ts.map +1 -1
- package/dist/proxy/network-models.js +3 -1
- package/dist/proxy/network-models.js.map +1 -1
- package/dist/proxy/route-policy.d.ts +97 -0
- package/dist/proxy/route-policy.d.ts.map +1 -0
- package/dist/proxy/route-policy.js +230 -0
- package/dist/proxy/route-policy.js.map +1 -0
- package/dist/proxy/route-policy.test.d.ts +2 -0
- package/dist/proxy/route-policy.test.d.ts.map +1 -0
- package/dist/proxy/route-policy.test.js +120 -0
- package/dist/proxy/route-policy.test.js.map +1 -0
- package/dist/proxy/spend-attribution.d.ts +41 -10
- package/dist/proxy/spend-attribution.d.ts.map +1 -1
- package/dist/proxy/spend-attribution.js +133 -37
- package/dist/proxy/spend-attribution.js.map +1 -1
- package/dist/proxy/tee-control.d.ts +3 -1
- package/dist/proxy/tee-control.d.ts.map +1 -1
- package/dist/proxy/tee-control.js +5 -3
- package/dist/proxy/tee-control.js.map +1 -1
- package/dist/proxy/telemetry.d.ts +2 -1
- package/dist/proxy/telemetry.d.ts.map +1 -1
- package/dist/proxy/telemetry.js +10 -5
- package/dist/proxy/telemetry.js.map +1 -1
- package/dist/routing-policy/policy.d.ts +149 -0
- package/dist/routing-policy/policy.d.ts.map +1 -0
- package/dist/routing-policy/policy.js +450 -0
- package/dist/routing-policy/policy.js.map +1 -0
- package/dist/routing-policy/policy.test.d.ts +2 -0
- package/dist/routing-policy/policy.test.d.ts.map +1 -0
- package/dist/routing-policy/policy.test.js +134 -0
- package/dist/routing-policy/policy.test.js.map +1 -0
- package/package.json +15 -7
|
@@ -0,0 +1,1014 @@
|
|
|
1
|
+
import { generateAuthenticationOptions, generateRegistrationOptions, verifyAuthenticationResponse, verifyRegistrationResponse, } from '@simplewebauthn/server';
|
|
2
|
+
import { errorMessage } from '../errors.js';
|
|
3
|
+
import { ADMIN_TOKEN_PREFIX, parseBearerToken } from '../keys.js';
|
|
4
|
+
import { BUDGET_PERIODS } from '../limits.js';
|
|
5
|
+
import { usdcToDecimalString } from '../money.js';
|
|
6
|
+
import { ConsoleError, respond, } from '../console-api/router.js';
|
|
7
|
+
import { AuthDb, FRESH_SIGN_IN_MS, OIDC_STATE_TTL_MS, SESSION_ABSOLUTE_MS, WALLET_NONCE_TTL_MS, sessionSignIn, sha256Hex, } from './db.js';
|
|
8
|
+
import { appendSetCookie, clientIp, headerValue, isIpHostname, OIDC_STATE_COOKIE, RateLimiter, rateLimited, readCookie, redirect, requestOrigin, serializeCookie, SESSION_COOKIE, } from './http.js';
|
|
9
|
+
import { allowedDomainsFromEnv, cloudflareAccessConfigFromEnv, CloudflareAccessVerifier, oidcConfigFromEnv, OidcClient, safeEqual, } from './oidc.js';
|
|
10
|
+
import { buildSignInMessage, newWalletNonce, nonceFromMessage, normalizeAddress, recoverSigner } from './wallet.js';
|
|
11
|
+
const DEFAULT_GATEWAY_PORT = 8379;
|
|
12
|
+
const SETUP_OWNER_META = 'setup_owner_id';
|
|
13
|
+
const RP_NAME = 'Antseed gateway';
|
|
14
|
+
const OIDC_COOKIE_PATH = '/console/api/auth/oidc';
|
|
15
|
+
export function createConsoleAuth(deps, options = {}) {
|
|
16
|
+
const { store } = deps;
|
|
17
|
+
const env = options.env ?? process.env;
|
|
18
|
+
const db = new AuthDb(store.database, deps.now);
|
|
19
|
+
const publicUrl = deps.publicUrl ? deps.publicUrl.replace(/\/+$/, '') : null;
|
|
20
|
+
let gatewayPort = options.gatewayPort ?? deps.gatewayPort ?? DEFAULT_GATEWAY_PORT;
|
|
21
|
+
const oidcConfig = oidcConfigFromEnv(env, publicUrl);
|
|
22
|
+
const oidc = oidcConfig && publicUrl
|
|
23
|
+
? new OidcClient(oidcConfig, `${publicUrl}/console/api/auth/oidc/callback`, options.fetch ?? fetch, deps.now)
|
|
24
|
+
: null;
|
|
25
|
+
if (!oidcConfig && env.ANTSEED_OIDC_ISSUER && (!publicUrl || !publicUrl.startsWith('https://'))) {
|
|
26
|
+
deps.log('OIDC sign-in is disabled: it needs an https public URL for its redirect URI.');
|
|
27
|
+
}
|
|
28
|
+
const cfConfig = cloudflareAccessConfigFromEnv(env);
|
|
29
|
+
const cloudflare = cfConfig ? new CloudflareAccessVerifier(cfConfig, deps.now, options.cloudflareAccessKeys) : null;
|
|
30
|
+
const allowedDomains = allowedDomainsFromEnv(env);
|
|
31
|
+
const trustCloudflare = deps.trustCloudflareHeaders === true;
|
|
32
|
+
const ipOf = (req) => clientIp(req, { trustCloudflare });
|
|
33
|
+
const ipLimiter = new RateLimiter([{ limit: 30, ms: 60_000 }, { limit: 300, ms: 3_600_000 }], deps.now);
|
|
34
|
+
const accountLimiter = new RateLimiter([{ limit: 10, ms: 60_000 }, { limit: 100, ms: 3_600_000 }], deps.now);
|
|
35
|
+
function limit(req, account) {
|
|
36
|
+
const ipWait = ipLimiter.hit(`ip:${ipOf(req)}`);
|
|
37
|
+
const accountWait = account ? accountLimiter.hit(account) : null;
|
|
38
|
+
const wait = Math.max(ipWait ?? 0, accountWait ?? 0);
|
|
39
|
+
return wait > 0 ? rateLimited(wait) : null;
|
|
40
|
+
}
|
|
41
|
+
function recordAudit(req, actor, action, details = {}) {
|
|
42
|
+
if (!store.recordAudit)
|
|
43
|
+
return;
|
|
44
|
+
try {
|
|
45
|
+
store.recordAudit({ actor, action, details, ip: req.socket ? ipOf(req) : null });
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
deps.log(`Console: AUDIT WRITE FAILED for ${action}: ${errorMessage(error)}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function memberActor(memberId) {
|
|
52
|
+
return { kind: 'member', id: memberId, label: store.getMember(memberId)?.label ?? null };
|
|
53
|
+
}
|
|
54
|
+
/** Records a refused sign-in (never the secret that was tried) and passes the error on. */
|
|
55
|
+
function signInFailures(method, handler) {
|
|
56
|
+
return async (request) => {
|
|
57
|
+
try {
|
|
58
|
+
return await handler(request);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (error instanceof ConsoleError && error.status >= 400 && error.status < 500 && error.status !== 429) {
|
|
62
|
+
recordAudit(request.raw, { kind: 'system', id: null }, 'auth.sign_in_failed', { method, code: error.code });
|
|
63
|
+
}
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// ── Setup state ─────────────────────────────────────────────────────────
|
|
69
|
+
/** An owner created by a setup link who never registered a credential may re-claim with a new link. */
|
|
70
|
+
function pendingSetupOwner() {
|
|
71
|
+
const id = db.getMeta(SETUP_OWNER_META);
|
|
72
|
+
if (!id)
|
|
73
|
+
return null;
|
|
74
|
+
if (db.countCredentials(id) > 0) {
|
|
75
|
+
// Claimed for good: removing credentials later must not reopen setup.
|
|
76
|
+
db.setMeta(SETUP_OWNER_META, '');
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const member = store.getMember(id);
|
|
80
|
+
return member && member.status !== 'disabled' ? member : null;
|
|
81
|
+
}
|
|
82
|
+
function setupClaimable() {
|
|
83
|
+
return !store.isSetupComplete() || pendingSetupOwner() !== null;
|
|
84
|
+
}
|
|
85
|
+
// ── Principals and sessions ─────────────────────────────────────────────
|
|
86
|
+
function memberPrincipal(member, sessionId) {
|
|
87
|
+
if (member.status !== 'active')
|
|
88
|
+
return null;
|
|
89
|
+
return { kind: 'member', memberId: member.id, orgRole: member.orgRole, workspaceRoles: store.memberWorkspaceRoles(member.id), sessionId };
|
|
90
|
+
}
|
|
91
|
+
function keyUsable(key) {
|
|
92
|
+
return key !== null && key.status === 'active' && (key.expiresAt === null || key.expiresAt > deps.now());
|
|
93
|
+
}
|
|
94
|
+
function startSession(req, res, principal, method, credentialId = null) {
|
|
95
|
+
// Rotate: a login always replaces whatever session this browser had,
|
|
96
|
+
// and a new session is a fresh sign-in (see FRESH_SIGN_IN_MS).
|
|
97
|
+
const previous = readCookie(req, SESSION_COOKIE);
|
|
98
|
+
if (previous)
|
|
99
|
+
db.deleteSessionBySecret(previous);
|
|
100
|
+
db.sweep();
|
|
101
|
+
const secret = db.createSession(principal, { userAgent: headerValue(req, 'user-agent'), ip: ipOf(req) }, credentialId);
|
|
102
|
+
if (principal.kind === 'member') {
|
|
103
|
+
recordAudit(req, memberActor(principal.memberId), 'auth.sign_in', { method });
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
recordAudit(req, { kind: 'key', id: principal.keyId, label: store.getKey(principal.keyId)?.label ?? null }, 'auth.sign_in', { method: 'api-key' });
|
|
107
|
+
}
|
|
108
|
+
appendSetCookie(res, serializeCookie(req, SESSION_COOKIE, secret, { maxAgeSeconds: SESSION_ABSOLUTE_MS / 1000, path: '/console', sameSite: 'Strict' }));
|
|
109
|
+
return sha256Hex(secret);
|
|
110
|
+
}
|
|
111
|
+
async function startMemberSession(request, member, method, credentialId) {
|
|
112
|
+
if (member.status !== 'active')
|
|
113
|
+
throw new ConsoleError(403, 'member_disabled', 'This member cannot sign in.');
|
|
114
|
+
const sessionId = startSession(request.raw, request.res, { kind: 'member', memberId: member.id }, method, credentialId);
|
|
115
|
+
return me(memberPrincipal(member, sessionId));
|
|
116
|
+
}
|
|
117
|
+
async function authenticate(req) {
|
|
118
|
+
const authorization = headerValue(req, 'authorization') ?? undefined;
|
|
119
|
+
if (authorization?.startsWith('Bearer ')) {
|
|
120
|
+
// Bearer requests skip the CSRF header check, so they never fall back to the session cookie.
|
|
121
|
+
const bearer = parseBearerToken(authorization);
|
|
122
|
+
if (!bearer?.startsWith(ADMIN_TOKEN_PREFIX))
|
|
123
|
+
return null;
|
|
124
|
+
const token = store.findAdminTokenBySecret(bearer);
|
|
125
|
+
if (!token || token.revokedAt !== null)
|
|
126
|
+
return null;
|
|
127
|
+
if (typeof token.expiresAt === 'number' && token.expiresAt <= deps.now())
|
|
128
|
+
return null;
|
|
129
|
+
// A token is only as good as its creator's role (null: created from the CLI).
|
|
130
|
+
if (token.createdBy) {
|
|
131
|
+
const creator = store.getMember(token.createdBy);
|
|
132
|
+
if (!creator || creator.status !== 'active' || (creator.orgRole !== 'owner' && creator.orgRole !== 'admin'))
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
store.touchAdminToken?.(token.id);
|
|
136
|
+
return { kind: 'token', tokenId: token.id, scope: token.scope };
|
|
137
|
+
}
|
|
138
|
+
const cookie = readCookie(req, SESSION_COOKIE);
|
|
139
|
+
if (cookie) {
|
|
140
|
+
const session = db.findSession(cookie);
|
|
141
|
+
if (session?.kind === 'member' && session.member_id) {
|
|
142
|
+
const member = store.getMember(session.member_id);
|
|
143
|
+
const principal = member ? memberPrincipal(member, session.id) : null;
|
|
144
|
+
if (principal)
|
|
145
|
+
return principal;
|
|
146
|
+
db.deleteSession(session.id);
|
|
147
|
+
}
|
|
148
|
+
else if (session?.kind === 'key' && session.key_id) {
|
|
149
|
+
if (keyUsable(store.getKey(session.key_id)))
|
|
150
|
+
return { kind: 'key', keyId: session.key_id, sessionId: session.id };
|
|
151
|
+
db.deleteSession(session.id);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const assertion = headerValue(req, 'cf-access-jwt-assertion');
|
|
155
|
+
if (cloudflare && assertion) {
|
|
156
|
+
let identity;
|
|
157
|
+
try {
|
|
158
|
+
identity = await cloudflare.verify(assertion);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
const resolved = resolveExternalMember(identity, null, false);
|
|
164
|
+
if (!('member' in resolved))
|
|
165
|
+
return null;
|
|
166
|
+
// The setup owner arriving through Access has claimed the console: an
|
|
167
|
+
// outstanding setup link must not be able to claim it again.
|
|
168
|
+
if (db.getMeta(SETUP_OWNER_META) === resolved.member.id && db.countCredentials(resolved.member.id) === 0) {
|
|
169
|
+
db.setMeta(SETUP_OWNER_META, '');
|
|
170
|
+
deps.log('Console: the owner signed in through Cloudflare Access; setup is complete.');
|
|
171
|
+
}
|
|
172
|
+
return memberPrincipal(resolved.member, `cf-access:${sha256Hex(identity.subject).slice(0, 16)}`);
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Maps a verified external identity to a member: a bound OIDC credential,
|
|
178
|
+
* then the enrollment the flow started with, then an invited member with
|
|
179
|
+
* that email, then domain auto-join. Cloudflare Access (no credential
|
|
180
|
+
* binding) also accepts an active member by email, since Access already
|
|
181
|
+
* gates who reaches the gateway.
|
|
182
|
+
*/
|
|
183
|
+
function resolveExternalMember(identity, enrollmentMemberId, bindCredential, req = null) {
|
|
184
|
+
if (bindCredential) {
|
|
185
|
+
const credential = db.findOidc(identity.issuer, identity.subject);
|
|
186
|
+
if (credential) {
|
|
187
|
+
// Linking or enrolling an account another member already signs in with must not switch to that member.
|
|
188
|
+
if (enrollmentMemberId && credential.member_id !== enrollmentMemberId)
|
|
189
|
+
return { error: 'credential_exists' };
|
|
190
|
+
const member = store.getMember(credential.member_id);
|
|
191
|
+
if (!member)
|
|
192
|
+
return { error: 'not_invited' };
|
|
193
|
+
if (member.status !== 'active')
|
|
194
|
+
return { error: 'disabled' };
|
|
195
|
+
db.touchCredential(credential.id);
|
|
196
|
+
return { member };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const bind = (member) => {
|
|
200
|
+
if (bindCredential) {
|
|
201
|
+
addCredential(req, member, { kind: 'oidc', label: `${oidcConfig?.label ?? 'Single sign-on'} (${identity.email})`, issuer: identity.issuer, subject: identity.subject }, enrollmentMemberId ? 'enrollment' : 'sign-in');
|
|
202
|
+
}
|
|
203
|
+
return { member };
|
|
204
|
+
};
|
|
205
|
+
if (enrollmentMemberId) {
|
|
206
|
+
const member = store.getMember(enrollmentMemberId);
|
|
207
|
+
if (!member)
|
|
208
|
+
return { error: 'invalid_enrollment' };
|
|
209
|
+
if (member.status === 'disabled')
|
|
210
|
+
return { error: 'disabled' };
|
|
211
|
+
if (member.status === 'invited') {
|
|
212
|
+
const activated = activateWithInvite(member.id, req);
|
|
213
|
+
return activated ? bind(activated) : { error: 'not_invited' };
|
|
214
|
+
}
|
|
215
|
+
return bind(member);
|
|
216
|
+
}
|
|
217
|
+
const byEmail = store.findMemberByEmail(identity.email);
|
|
218
|
+
if (byEmail) {
|
|
219
|
+
if (byEmail.status === 'disabled')
|
|
220
|
+
return { error: 'disabled' };
|
|
221
|
+
if (byEmail.status === 'invited') {
|
|
222
|
+
// Only while their invite is live: an expired or cancelled invite
|
|
223
|
+
// must not turn into access because the email still matches.
|
|
224
|
+
const activated = activateWithInvite(byEmail.id, req);
|
|
225
|
+
return activated ? bind(activated) : { error: 'not_invited' };
|
|
226
|
+
}
|
|
227
|
+
return bindCredential ? { error: 'not_linked' } : { member: byEmail };
|
|
228
|
+
}
|
|
229
|
+
const domain = identity.email.split('@').pop() ?? '';
|
|
230
|
+
if (allowedDomains.length > 0 && (allowedDomains.includes(domain) || (identity.hostedDomain !== null && allowedDomains.includes(identity.hostedDomain)))) {
|
|
231
|
+
const member = autoJoin(identity.email);
|
|
232
|
+
if (!member)
|
|
233
|
+
return { error: 'not_invited' };
|
|
234
|
+
deps.log(`Console: ${identity.email} joined via allowed domain ${domain}.`);
|
|
235
|
+
return bind(member);
|
|
236
|
+
}
|
|
237
|
+
return { error: 'not_invited' };
|
|
238
|
+
}
|
|
239
|
+
/** Activates an invited member by consuming their newest live (unexpired, unused) invite; null when there is none. */
|
|
240
|
+
function activateWithInvite(memberId, req) {
|
|
241
|
+
const now = deps.now();
|
|
242
|
+
const hash = store.liveInviteHash(memberId, now);
|
|
243
|
+
const member = hash ? store.consumeInvite(hash, now) : null;
|
|
244
|
+
if (member && req)
|
|
245
|
+
recordAudit(req, { kind: 'member', id: member.id, label: member.label }, 'auth.invite_accept');
|
|
246
|
+
return member;
|
|
247
|
+
}
|
|
248
|
+
/** New active member in the Default workspace as role member. */
|
|
249
|
+
function autoJoin(email) {
|
|
250
|
+
const defaultWorkspace = store.listWorkspaces?.().find((workspace) => workspace.isDefault) ?? null;
|
|
251
|
+
const workspaces = defaultWorkspace ? [{ workspaceId: defaultWorkspace.id, role: 'member' }] : [];
|
|
252
|
+
if (store.createMember)
|
|
253
|
+
return store.createMember({ label: email, email, orgRole: 'member', status: 'active', workspaces });
|
|
254
|
+
if (store.createInvite) {
|
|
255
|
+
// Same records an accepted invite leaves behind, which also keeps an audit trail of the join.
|
|
256
|
+
const { token } = store.createInvite({ label: email, email, orgRole: 'member', workspaces, expiresAt: deps.now() + 60_000, createdBy: null });
|
|
257
|
+
return store.consumeInvite(sha256Hex(token), deps.now());
|
|
258
|
+
}
|
|
259
|
+
deps.log('Console sign-in: domain auto-join is configured but the store cannot create members.');
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
// ── Presentation ────────────────────────────────────────────────────────
|
|
263
|
+
function credentialsFor(memberId) {
|
|
264
|
+
return db.listCredentials(memberId).map((row) => ({
|
|
265
|
+
id: row.id,
|
|
266
|
+
kind: row.kind,
|
|
267
|
+
label: row.label,
|
|
268
|
+
createdAt: row.created_at,
|
|
269
|
+
lastUsedAt: row.last_used_at,
|
|
270
|
+
}));
|
|
271
|
+
}
|
|
272
|
+
function presentMember(member) {
|
|
273
|
+
const credentials = credentialsFor(member.id);
|
|
274
|
+
if (options.presentMember)
|
|
275
|
+
return options.presentMember(member, credentials);
|
|
276
|
+
return {
|
|
277
|
+
id: member.id,
|
|
278
|
+
label: member.label,
|
|
279
|
+
email: member.email,
|
|
280
|
+
orgRole: member.orgRole,
|
|
281
|
+
status: member.status,
|
|
282
|
+
credentials,
|
|
283
|
+
limits: toSpendLimits(member.limits),
|
|
284
|
+
routingPolicy: (member.routingPolicy ?? null),
|
|
285
|
+
maxKeys: member.maxKeys ?? null,
|
|
286
|
+
createdAt: member.createdAt,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function presentKey(key) {
|
|
290
|
+
if (options.presentKey)
|
|
291
|
+
return options.presentKey(key);
|
|
292
|
+
const now = new Date(deps.now());
|
|
293
|
+
const monthStart = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1);
|
|
294
|
+
const total = store.usageStats?.(key.id, 0);
|
|
295
|
+
const month = store.usageStats?.(key.id, monthStart);
|
|
296
|
+
return {
|
|
297
|
+
id: key.id,
|
|
298
|
+
label: key.label,
|
|
299
|
+
hint: key.hint,
|
|
300
|
+
workspaceId: key.workspaceId ?? store.workspaceForKey?.(key.id)?.id ?? 'default',
|
|
301
|
+
ownerMemberId: key.ownerMemberId ?? null,
|
|
302
|
+
buyerIdentity: key.buyerIdentity,
|
|
303
|
+
status: key.status,
|
|
304
|
+
limits: toSpendLimits(key.limits),
|
|
305
|
+
routingPolicy: (key.routingPolicy ?? null),
|
|
306
|
+
topupEnabled: key.topupEnabled,
|
|
307
|
+
expiresAt: key.expiresAt,
|
|
308
|
+
createdAt: key.createdAt,
|
|
309
|
+
lastUsedAt: key.lastUsedAt,
|
|
310
|
+
usage: {
|
|
311
|
+
requests: total?.requests ?? 0,
|
|
312
|
+
spent: usdcToDecimalString(total?.spentUsdc ?? 0),
|
|
313
|
+
spentThisMonth: usdcToDecimalString(month?.spentUsdc ?? 0),
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function memberWorkspaces(member, roles) {
|
|
318
|
+
const all = store.listWorkspaces?.() ?? null;
|
|
319
|
+
const summary = (workspace) => ({ id: workspace.id, name: workspace.name, isDefault: workspace.isDefault });
|
|
320
|
+
if (all && (member.orgRole === 'owner' || member.orgRole === 'admin')) {
|
|
321
|
+
return all.map((workspace) => ({ workspace: summary(workspace), role: roles.get(workspace.id) ?? 'admin' }));
|
|
322
|
+
}
|
|
323
|
+
const result = [];
|
|
324
|
+
for (const [workspaceId, role] of roles) {
|
|
325
|
+
const workspace = store.getWorkspace?.(workspaceId) ?? all?.find((candidate) => candidate.id === workspaceId) ?? null;
|
|
326
|
+
if (workspace)
|
|
327
|
+
result.push({ workspace: summary(workspace), role });
|
|
328
|
+
}
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
331
|
+
async function me(principal) {
|
|
332
|
+
if (principal.kind === 'member') {
|
|
333
|
+
const member = store.getMember(principal.memberId);
|
|
334
|
+
if (!member)
|
|
335
|
+
throw new ConsoleError(401, 'unauthenticated', 'Sign in again.');
|
|
336
|
+
return { kind: 'member', me: { member: presentMember(member), workspaces: memberWorkspaces(member, store.memberWorkspaceRoles(member.id)) } };
|
|
337
|
+
}
|
|
338
|
+
if (principal.kind === 'key') {
|
|
339
|
+
const key = store.getKey(principal.keyId);
|
|
340
|
+
if (!key)
|
|
341
|
+
throw new ConsoleError(401, 'unauthenticated', 'Sign in again.');
|
|
342
|
+
return { kind: 'key', me: { key: presentKey(key) } };
|
|
343
|
+
}
|
|
344
|
+
throw new ConsoleError(400, 'not_supported', 'Management tokens have no console profile.');
|
|
345
|
+
}
|
|
346
|
+
// ── Helpers for routes ──────────────────────────────────────────────────
|
|
347
|
+
function rpFor(req) {
|
|
348
|
+
const rp = requestOrigin(req, publicUrl);
|
|
349
|
+
if (!rp)
|
|
350
|
+
throw new ConsoleError(400, 'public_url_required', 'Set the gateway public URL to sign in from another host.');
|
|
351
|
+
return rp;
|
|
352
|
+
}
|
|
353
|
+
function passkeyAvailable(req) {
|
|
354
|
+
const rp = passkeyOrigin(req);
|
|
355
|
+
if (!rp || isIpHostname(rp.hostname))
|
|
356
|
+
return false;
|
|
357
|
+
// WebAuthn needs a secure context: https, or localhost.
|
|
358
|
+
return rp.origin.startsWith('https://') || rp.hostname === 'localhost' || rp.hostname.endsWith('.localhost');
|
|
359
|
+
}
|
|
360
|
+
/** The relying party passkeys are judged against: the request's origin, or without a request the public URL's. */
|
|
361
|
+
function passkeyOrigin(req) {
|
|
362
|
+
if (req)
|
|
363
|
+
return requestOrigin(req, publicUrl);
|
|
364
|
+
return publicUrl ? requestOrigin({}, publicUrl) : null;
|
|
365
|
+
}
|
|
366
|
+
/** The relying party for a passkey ceremony; refused when passkeys cannot work on this origin. */
|
|
367
|
+
function passkeyRp(req) {
|
|
368
|
+
if (!passkeyAvailable(req))
|
|
369
|
+
throw new ConsoleError(400, 'passkey_unavailable', 'Passkeys need https or localhost.');
|
|
370
|
+
return rpFor(req);
|
|
371
|
+
}
|
|
372
|
+
function memberPasskeys(memberId) {
|
|
373
|
+
return db.listCredentials(memberId).filter((row) => row.kind === 'passkey' && row.webauthn_id);
|
|
374
|
+
}
|
|
375
|
+
/** The member signed in to this request's session (or Cloudflare Access), if any. */
|
|
376
|
+
async function signedInMember(request) {
|
|
377
|
+
const principal = request.principal ?? await authenticate(request.raw);
|
|
378
|
+
const member = principal?.kind === 'member' ? store.getMember(principal.memberId) : null;
|
|
379
|
+
return { principal, member };
|
|
380
|
+
}
|
|
381
|
+
/** Every new sign-in method goes through here: it is audited, and the setup owner's first one closes setup for good. */
|
|
382
|
+
function addCredential(req, member, credential, via) {
|
|
383
|
+
const row = db.addCredential(member.id, credential);
|
|
384
|
+
if (db.getMeta(SETUP_OWNER_META) === member.id)
|
|
385
|
+
db.setMeta(SETUP_OWNER_META, '');
|
|
386
|
+
const details = { credentialId: row.id, kind: row.kind, label: row.label, via, ...(row.wallet_address ? { address: row.wallet_address } : {}) };
|
|
387
|
+
if (req)
|
|
388
|
+
recordAudit(req, { kind: 'member', id: member.id, label: member.label }, 'auth.credential_add', details);
|
|
389
|
+
deps.log(`Console: ${row.kind} sign-in method added for member ${member.id} (${via}).`);
|
|
390
|
+
return row;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Registers a sign-in method through an enrollment and uses the enrollment
|
|
394
|
+
* up. An invited member's invite is consumed in the same transaction, so a
|
|
395
|
+
* sign-up that fails before this point leaves the invite link usable.
|
|
396
|
+
*/
|
|
397
|
+
function enrollCredential(req, enrollment, credential) {
|
|
398
|
+
return db.transaction(() => {
|
|
399
|
+
const memberId = db.consumeEnrollment(enrollment);
|
|
400
|
+
let member = memberId ? store.getMember(memberId) : null;
|
|
401
|
+
if (!member)
|
|
402
|
+
throw new ConsoleError(400, 'invalid_enrollment', 'This sign-up link has expired. Ask for a new one.');
|
|
403
|
+
if (member.status === 'invited') {
|
|
404
|
+
member = activateWithInvite(member.id, req);
|
|
405
|
+
if (!member)
|
|
406
|
+
throw new ConsoleError(400, 'invite_expired', 'This invite link has expired. Ask for a new one.');
|
|
407
|
+
}
|
|
408
|
+
return { member, credential: addCredential(req, member, credential, 'enrollment') };
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Adding or removing a sign-in method from an existing session needs a
|
|
413
|
+
* recent sign-in (FRESH_SIGN_IN_MS), so a stolen session cookie can't plant
|
|
414
|
+
* its own credential or lock the member out. Sessions confirm through `/auth/reauth/*`; Cloudflare Access
|
|
415
|
+
* principals have no session row, so their Access token must be fresh.
|
|
416
|
+
*/
|
|
417
|
+
async function requireFreshSignIn(request, principal) {
|
|
418
|
+
if (principal?.kind !== 'member')
|
|
419
|
+
throw new ConsoleError(401, 'unauthenticated', 'Sign in first.');
|
|
420
|
+
const now = deps.now();
|
|
421
|
+
if (principal.sessionId.startsWith('cf-access:')) {
|
|
422
|
+
const assertion = headerValue(request.raw, 'cf-access-jwt-assertion');
|
|
423
|
+
let issuedAt = null;
|
|
424
|
+
if (cloudflare && assertion) {
|
|
425
|
+
try {
|
|
426
|
+
issuedAt = (await cloudflare.verify(assertion)).issuedAt;
|
|
427
|
+
}
|
|
428
|
+
catch {
|
|
429
|
+
issuedAt = null;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (issuedAt === null || now - issuedAt > FRESH_SIGN_IN_MS) {
|
|
433
|
+
throw new ConsoleError(403, 'access_reauth_required', 'Your Cloudflare Access sign-in is more than 5 minutes old. Sign out of Cloudflare Access, sign in again, then retry.');
|
|
434
|
+
}
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
const signIn = sessionSignIn(store.database, principal.sessionId);
|
|
438
|
+
if (!signIn || now - signIn.authenticatedAt > FRESH_SIGN_IN_MS) {
|
|
439
|
+
throw new ConsoleError(403, 'reauth_required', 'Confirm it\'s you (passkey or wallet) before changing your sign-in methods, then retry.');
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
/** The member registering a credential: from an enrollment ticket, or the signed-in member adding another one (after a fresh sign-in). */
|
|
443
|
+
async function enrollingMember(request, enrollment) {
|
|
444
|
+
if (enrollment) {
|
|
445
|
+
const memberId = db.peekEnrollment(enrollment);
|
|
446
|
+
const member = memberId ? store.getMember(memberId) : null;
|
|
447
|
+
if (!member)
|
|
448
|
+
throw new ConsoleError(400, 'invalid_enrollment', 'This sign-up link has expired. Ask for a new one.');
|
|
449
|
+
return { member, viaSession: false };
|
|
450
|
+
}
|
|
451
|
+
const { principal, member } = await signedInMember(request);
|
|
452
|
+
if (!member)
|
|
453
|
+
throw new ConsoleError(401, 'unauthenticated', 'Sign in or use an invite link first.');
|
|
454
|
+
await requireFreshSignIn(request, principal);
|
|
455
|
+
return { member, viaSession: true };
|
|
456
|
+
}
|
|
457
|
+
function enrollmentFor(member) {
|
|
458
|
+
return { enrollment: db.createEnrollment(member.id), label: member.label, orgRole: member.orgRole };
|
|
459
|
+
}
|
|
460
|
+
function loginRedirectTo(res, error) {
|
|
461
|
+
return redirect(res, `/console/login?error=${encodeURIComponent(error)}`);
|
|
462
|
+
}
|
|
463
|
+
function clearCookie(req, res, name, path, sameSite) {
|
|
464
|
+
appendSetCookie(res, serializeCookie(req, name, '', { maxAgeSeconds: 0, path, sameSite }));
|
|
465
|
+
}
|
|
466
|
+
async function verifyPasskey(raw, response, challenge, stored) {
|
|
467
|
+
const rp = rpFor(raw);
|
|
468
|
+
let verification;
|
|
469
|
+
try {
|
|
470
|
+
verification = await verifyAuthenticationResponse({
|
|
471
|
+
response,
|
|
472
|
+
expectedChallenge: challenge,
|
|
473
|
+
expectedOrigin: rp.origin,
|
|
474
|
+
expectedRPID: rp.hostname,
|
|
475
|
+
credential: {
|
|
476
|
+
id: stored.webauthn_id,
|
|
477
|
+
publicKey: new Uint8Array(stored.public_key),
|
|
478
|
+
counter: stored.counter ?? 0,
|
|
479
|
+
transports: parseTransports(stored.transports),
|
|
480
|
+
},
|
|
481
|
+
requireUserVerification: true,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
throw new ConsoleError(401, 'passkey_invalid', `Passkey sign-in failed: ${errorMessage(error)}`);
|
|
486
|
+
}
|
|
487
|
+
if (!verification.verified)
|
|
488
|
+
throw new ConsoleError(401, 'passkey_invalid', 'Passkey sign-in could not be verified.');
|
|
489
|
+
return verification;
|
|
490
|
+
}
|
|
491
|
+
function walletNonce(raw, value) {
|
|
492
|
+
const address = normalizeAddress(value);
|
|
493
|
+
if (!address)
|
|
494
|
+
throw new ConsoleError(400, 'invalid_address', 'Enter a valid wallet address.');
|
|
495
|
+
const rp = rpFor(raw);
|
|
496
|
+
const nonce = newWalletNonce();
|
|
497
|
+
const issuedAt = deps.now();
|
|
498
|
+
const expiresAt = issuedAt + WALLET_NONCE_TTL_MS;
|
|
499
|
+
const message = buildSignInMessage({ domain: rp.host, address, uri: `${rp.origin}/console`, nonce, issuedAt, expiresAt });
|
|
500
|
+
db.saveWalletNonce(nonce, address, message, expiresAt);
|
|
501
|
+
return { message };
|
|
502
|
+
}
|
|
503
|
+
/** Consumes the message's nonce and returns the (lower-case) signer, which must be the address the nonce was issued to. */
|
|
504
|
+
function verifyWalletSignature(message, signature, signer = recoverSigner(message, signature)) {
|
|
505
|
+
const nonce = nonceFromMessage(message);
|
|
506
|
+
const saved = nonce ? db.takeWalletNonce(nonce) : null;
|
|
507
|
+
if (!saved || !safeEqual(saved.message, message))
|
|
508
|
+
throw new ConsoleError(400, 'invalid_nonce', 'This sign-in message expired. Try again.');
|
|
509
|
+
if (!signer || signer !== saved.address)
|
|
510
|
+
throw new ConsoleError(401, 'bad_signature', 'The signature does not match this wallet.');
|
|
511
|
+
return signer;
|
|
512
|
+
}
|
|
513
|
+
/** The member whose session is being re-authenticated; only real session rows can be refreshed. */
|
|
514
|
+
function reauthMemberId(request) {
|
|
515
|
+
const principal = request.principal;
|
|
516
|
+
if (principal?.kind !== 'member')
|
|
517
|
+
throw new ConsoleError(403, 'forbidden', 'Only members can confirm a sign-in.');
|
|
518
|
+
if (principal.sessionId.startsWith('cf-access:')) {
|
|
519
|
+
throw new ConsoleError(409, 'reauth_unavailable', 'This session comes from Cloudflare Access; sign in with a passkey or wallet to confirm.');
|
|
520
|
+
}
|
|
521
|
+
return principal.memberId;
|
|
522
|
+
}
|
|
523
|
+
function wrongMember(request, method, message) {
|
|
524
|
+
const principal = request.principal;
|
|
525
|
+
recordAudit(request.raw, memberActor(principal.memberId), 'auth.reauth_failed', { method, code: 'reauth_wrong_member' });
|
|
526
|
+
return new ConsoleError(403, 'reauth_wrong_member', message);
|
|
527
|
+
}
|
|
528
|
+
async function finishReauth(request, method, credentialId) {
|
|
529
|
+
const principal = request.principal;
|
|
530
|
+
if (!db.markAuthenticated(principal.sessionId, credentialId))
|
|
531
|
+
throw new ConsoleError(401, 'unauthenticated', 'Sign in again.');
|
|
532
|
+
recordAudit(request.raw, memberActor(principal.memberId), 'auth.reauth', { method });
|
|
533
|
+
return me(principal);
|
|
534
|
+
}
|
|
535
|
+
// ── Routes ──────────────────────────────────────────────────────────────
|
|
536
|
+
const PUBLIC = { public: true };
|
|
537
|
+
/** Re-authentication routes: only for the signed-in member's own session. */
|
|
538
|
+
const MEMBER_ONLY = { allow: ['member'] };
|
|
539
|
+
function registerRoutes(router) {
|
|
540
|
+
router.add('GET', '/auth/config', async ({ raw }) => authConfig(raw), PUBLIC);
|
|
541
|
+
registerClaimRoutes(router);
|
|
542
|
+
registerPasskeyRoutes(router);
|
|
543
|
+
registerWalletRoutes(router);
|
|
544
|
+
registerOidcRoutes(router);
|
|
545
|
+
registerReauthRoutes(router);
|
|
546
|
+
registerSessionRoutes(router);
|
|
547
|
+
}
|
|
548
|
+
/** Setup and invite links: each hands out an enrollment for registering a first sign-in method. */
|
|
549
|
+
function registerClaimRoutes(router) {
|
|
550
|
+
router.add('POST', '/auth/setup', signInFailures('setup', async ({ raw, body }) => {
|
|
551
|
+
const input = objectBody(body);
|
|
552
|
+
const token = stringField(input, 'token');
|
|
553
|
+
// Per token, so guessing elsewhere can't lock the owner's link out.
|
|
554
|
+
const limited = limit(raw, `setup:${sha256Hex(token)}`);
|
|
555
|
+
if (limited)
|
|
556
|
+
return limited;
|
|
557
|
+
if (!setupClaimable())
|
|
558
|
+
throw new ConsoleError(409, 'setup_complete', 'This console has already been claimed.');
|
|
559
|
+
if (!db.consumeSetupToken(token))
|
|
560
|
+
throw new ConsoleError(400, 'invalid_token', 'This setup link is invalid, used or expired. Ask the gateway operator for a new one.');
|
|
561
|
+
const owner = pendingSetupOwner() ?? store.createOwner({
|
|
562
|
+
label: optionalString(input, 'label') ?? 'Owner',
|
|
563
|
+
email: optionalString(input, 'email')?.toLowerCase() ?? null,
|
|
564
|
+
});
|
|
565
|
+
db.setMeta(SETUP_OWNER_META, owner.id);
|
|
566
|
+
deps.log('Console: setup link used; the owner is registering a sign-in method.');
|
|
567
|
+
recordAudit(raw, { kind: 'member', id: owner.id, label: owner.label }, 'auth.setup_claim');
|
|
568
|
+
return enrollmentFor(owner);
|
|
569
|
+
}), PUBLIC);
|
|
570
|
+
router.add('POST', '/auth/invite', signInFailures('invite', async ({ raw, body }) => {
|
|
571
|
+
const token = stringField(objectBody(body), 'token');
|
|
572
|
+
const tokenHash = sha256Hex(token);
|
|
573
|
+
const limited = limit(raw, `invite:${tokenHash}`);
|
|
574
|
+
if (limited)
|
|
575
|
+
return limited;
|
|
576
|
+
// The invite stays open until the first sign-in method is registered
|
|
577
|
+
// (enrollCredential), so the link can be opened again if sign-up fails.
|
|
578
|
+
const found = store.peekInvite(tokenHash, deps.now());
|
|
579
|
+
if (!found) {
|
|
580
|
+
const state = store.inviteState(tokenHash, deps.now());
|
|
581
|
+
if (state === 'used')
|
|
582
|
+
throw new ConsoleError(400, 'invite_used', 'This invite link was already used.');
|
|
583
|
+
if (state === 'expired')
|
|
584
|
+
throw new ConsoleError(400, 'invite_expired', 'This invite link has expired.');
|
|
585
|
+
throw new ConsoleError(400, 'invalid_token', 'This invite link is invalid.');
|
|
586
|
+
}
|
|
587
|
+
return enrollmentFor(found.member);
|
|
588
|
+
}), PUBLIC);
|
|
589
|
+
}
|
|
590
|
+
function registerPasskeyRoutes(router) {
|
|
591
|
+
router.add('POST', '/auth/passkey/register/options', async (request) => {
|
|
592
|
+
const limited = limit(request.raw);
|
|
593
|
+
if (limited)
|
|
594
|
+
return limited;
|
|
595
|
+
const { member } = await enrollingMember(request, optionalString(objectBody(request.body), 'enrollment'));
|
|
596
|
+
const rp = passkeyRp(request.raw);
|
|
597
|
+
const creation = await generateRegistrationOptions({
|
|
598
|
+
rpName: RP_NAME,
|
|
599
|
+
rpID: rp.hostname,
|
|
600
|
+
userName: member.email ?? member.label,
|
|
601
|
+
userDisplayName: member.label,
|
|
602
|
+
userID: new TextEncoder().encode(member.id),
|
|
603
|
+
attestationType: 'none',
|
|
604
|
+
excludeCredentials: memberPasskeys(member.id).map((row) => ({ id: row.webauthn_id, transports: parseTransports(row.transports) })),
|
|
605
|
+
authenticatorSelection: { residentKey: 'required', requireResidentKey: true, userVerification: 'required' },
|
|
606
|
+
});
|
|
607
|
+
db.saveChallenge(creation.challenge, 'register', member.id);
|
|
608
|
+
return creation;
|
|
609
|
+
}, PUBLIC);
|
|
610
|
+
router.add('POST', '/auth/passkey/register/verify', async (request) => {
|
|
611
|
+
const limited = limit(request.raw);
|
|
612
|
+
if (limited)
|
|
613
|
+
return limited;
|
|
614
|
+
const input = objectBody(request.body);
|
|
615
|
+
const enrollment = optionalString(input, 'enrollment');
|
|
616
|
+
const { member, viaSession } = await enrollingMember(request, enrollment);
|
|
617
|
+
const response = input.response;
|
|
618
|
+
const challenge = challengeOf(response);
|
|
619
|
+
const saved = challenge ? db.takeChallenge(challenge, 'register') : null;
|
|
620
|
+
if (!response || !challenge || !saved || saved.memberId !== member.id) {
|
|
621
|
+
throw new ConsoleError(400, 'invalid_challenge', 'This passkey request expired. Try again.');
|
|
622
|
+
}
|
|
623
|
+
const rp = rpFor(request.raw);
|
|
624
|
+
let verification;
|
|
625
|
+
try {
|
|
626
|
+
verification = await verifyRegistrationResponse({
|
|
627
|
+
response,
|
|
628
|
+
expectedChallenge: challenge,
|
|
629
|
+
expectedOrigin: rp.origin,
|
|
630
|
+
expectedRPID: rp.hostname,
|
|
631
|
+
requireUserVerification: true,
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
catch (error) {
|
|
635
|
+
throw new ConsoleError(400, 'passkey_invalid', `Passkey registration failed: ${errorMessage(error)}`);
|
|
636
|
+
}
|
|
637
|
+
if (!verification.verified)
|
|
638
|
+
throw new ConsoleError(400, 'passkey_invalid', 'Passkey registration could not be verified.');
|
|
639
|
+
const { credential } = verification.registrationInfo;
|
|
640
|
+
if (db.findPasskey(credential.id))
|
|
641
|
+
throw new ConsoleError(409, 'credential_exists', 'This passkey is already registered.');
|
|
642
|
+
const passkey = {
|
|
643
|
+
kind: 'passkey',
|
|
644
|
+
label: optionalString(input, 'label')?.slice(0, 80) ?? 'Passkey',
|
|
645
|
+
webauthnId: credential.id,
|
|
646
|
+
publicKey: credential.publicKey,
|
|
647
|
+
counter: credential.counter,
|
|
648
|
+
transports: credential.transports ?? [],
|
|
649
|
+
};
|
|
650
|
+
if (viaSession) {
|
|
651
|
+
addCredential(request.raw, member, passkey, 'session');
|
|
652
|
+
return me((request.principal ?? await authenticate(request.raw)));
|
|
653
|
+
}
|
|
654
|
+
const enrolled = enrollCredential(request.raw, enrollment, passkey);
|
|
655
|
+
return startMemberSession(request, enrolled.member, 'passkey', enrolled.credential.id);
|
|
656
|
+
}, PUBLIC);
|
|
657
|
+
router.add('POST', '/auth/passkey/login/options', async ({ raw }) => {
|
|
658
|
+
const limited = limit(raw);
|
|
659
|
+
if (limited)
|
|
660
|
+
return limited;
|
|
661
|
+
const rp = passkeyRp(raw);
|
|
662
|
+
const request = await generateAuthenticationOptions({ rpID: rp.hostname, userVerification: 'required' });
|
|
663
|
+
db.saveChallenge(request.challenge, 'login', null);
|
|
664
|
+
return request;
|
|
665
|
+
}, PUBLIC);
|
|
666
|
+
router.add('POST', '/auth/passkey/login/verify', signInFailures('passkey', async (request) => {
|
|
667
|
+
const input = objectBody(request.body);
|
|
668
|
+
const response = input.response;
|
|
669
|
+
const credentialId = typeof response?.id === 'string' ? response.id : '';
|
|
670
|
+
const limited = limit(request.raw, `passkey:${credentialId}`);
|
|
671
|
+
if (limited)
|
|
672
|
+
return limited;
|
|
673
|
+
const challenge = challengeOf(response);
|
|
674
|
+
if (!response || !challenge || !db.takeChallenge(challenge, 'login')) {
|
|
675
|
+
throw new ConsoleError(400, 'invalid_challenge', 'This sign-in request expired. Try again.');
|
|
676
|
+
}
|
|
677
|
+
const stored = db.findPasskey(credentialId);
|
|
678
|
+
const member = stored ? store.getMember(stored.member_id) : null;
|
|
679
|
+
if (!stored || !member)
|
|
680
|
+
throw new ConsoleError(401, 'unknown_credential', 'This passkey is not registered with this gateway.');
|
|
681
|
+
const userHandle = response.response?.userHandle;
|
|
682
|
+
if (userHandle && Buffer.from(userHandle, 'base64url').toString('utf8') !== member.id) {
|
|
683
|
+
throw new ConsoleError(401, 'unknown_credential', 'This passkey is not registered with this gateway.');
|
|
684
|
+
}
|
|
685
|
+
if (member.status !== 'active')
|
|
686
|
+
throw new ConsoleError(403, 'member_disabled', 'This member cannot sign in.');
|
|
687
|
+
const verification = await verifyPasskey(request.raw, response, challenge, stored);
|
|
688
|
+
db.touchCredential(stored.id, verification.authenticationInfo.newCounter);
|
|
689
|
+
return startMemberSession(request, member, 'passkey', stored.id);
|
|
690
|
+
}), PUBLIC);
|
|
691
|
+
}
|
|
692
|
+
/** Wallet sign-in (EIP-4361). */
|
|
693
|
+
function registerWalletRoutes(router) {
|
|
694
|
+
router.add('POST', '/auth/wallet/nonce', async ({ raw, body }) => {
|
|
695
|
+
const limited = limit(raw);
|
|
696
|
+
if (limited)
|
|
697
|
+
return limited;
|
|
698
|
+
return walletNonce(raw, objectBody(body).address);
|
|
699
|
+
}, PUBLIC);
|
|
700
|
+
router.add('POST', '/auth/wallet/verify', signInFailures('wallet', async (request) => {
|
|
701
|
+
const input = objectBody(request.body);
|
|
702
|
+
const message = stringField(input, 'message');
|
|
703
|
+
const signature = stringField(input, 'signature');
|
|
704
|
+
const enrollment = optionalString(input, 'enrollment');
|
|
705
|
+
const recovered = recoverSigner(message, signature);
|
|
706
|
+
const limited = limit(request.raw, `wallet:${recovered ?? 'invalid'}`);
|
|
707
|
+
if (limited)
|
|
708
|
+
return limited;
|
|
709
|
+
const signer = verifyWalletSignature(message, signature, recovered);
|
|
710
|
+
const existing = db.findWallet(signer);
|
|
711
|
+
if (existing) {
|
|
712
|
+
if (enrollment) {
|
|
713
|
+
const enrolled = db.peekEnrollment(enrollment);
|
|
714
|
+
if (enrolled !== existing.member_id)
|
|
715
|
+
throw new ConsoleError(409, 'credential_exists', 'This wallet is already linked to another member.');
|
|
716
|
+
db.consumeEnrollment(enrollment);
|
|
717
|
+
}
|
|
718
|
+
const member = store.getMember(existing.member_id);
|
|
719
|
+
if (!member)
|
|
720
|
+
throw new ConsoleError(401, 'unknown_credential', 'This wallet is not registered with this gateway.');
|
|
721
|
+
if (member.status !== 'active')
|
|
722
|
+
throw new ConsoleError(403, 'member_disabled', 'This member cannot sign in.');
|
|
723
|
+
db.touchCredential(existing.id);
|
|
724
|
+
return startMemberSession(request, member, 'wallet', existing.id);
|
|
725
|
+
}
|
|
726
|
+
const label = `Wallet ${signer.slice(0, 6)}…${signer.slice(-4)}`;
|
|
727
|
+
if (!enrollment) {
|
|
728
|
+
const { principal, member } = await signedInMember(request);
|
|
729
|
+
if (!member)
|
|
730
|
+
throw new ConsoleError(401, 'unknown_credential', 'This wallet is not registered. Use your invite link first.');
|
|
731
|
+
await requireFreshSignIn(request, principal);
|
|
732
|
+
addCredential(request.raw, member, { kind: 'wallet', label, address: signer }, 'session');
|
|
733
|
+
return me(principal);
|
|
734
|
+
}
|
|
735
|
+
const enrolled = enrollCredential(request.raw, enrollment, { kind: 'wallet', label, address: signer });
|
|
736
|
+
return startMemberSession(request, enrolled.member, 'wallet', enrolled.credential.id);
|
|
737
|
+
}), PUBLIC);
|
|
738
|
+
}
|
|
739
|
+
function registerOidcRoutes(router) {
|
|
740
|
+
router.add('GET', '/auth/oidc/start', async (request) => {
|
|
741
|
+
if (!oidc)
|
|
742
|
+
throw new ConsoleError(404, 'oidc_disabled', 'Single sign-on is not configured on this gateway.');
|
|
743
|
+
const limited = limit(request.raw);
|
|
744
|
+
if (limited)
|
|
745
|
+
return limited;
|
|
746
|
+
let enrollment = request.query.get('enrollment') || null;
|
|
747
|
+
if (enrollment && !db.peekEnrollment(enrollment))
|
|
748
|
+
return loginRedirectTo(request.res, 'invalid_enrollment');
|
|
749
|
+
if (!enrollment && request.query.get('link') === '1') {
|
|
750
|
+
// A signed-in member links their provider account via a fresh enrollment for themselves.
|
|
751
|
+
const { principal, member } = await signedInMember(request);
|
|
752
|
+
if (!member)
|
|
753
|
+
return loginRedirectTo(request.res, 'unauthenticated');
|
|
754
|
+
try {
|
|
755
|
+
await requireFreshSignIn(request, principal);
|
|
756
|
+
}
|
|
757
|
+
catch (error) {
|
|
758
|
+
if (error instanceof ConsoleError)
|
|
759
|
+
return loginRedirectTo(request.res, error.code);
|
|
760
|
+
throw error;
|
|
761
|
+
}
|
|
762
|
+
enrollment = db.createEnrollment(member.id);
|
|
763
|
+
}
|
|
764
|
+
let authorization;
|
|
765
|
+
try {
|
|
766
|
+
authorization = await oidc.authorizationUrl();
|
|
767
|
+
}
|
|
768
|
+
catch (error) {
|
|
769
|
+
deps.log(`Console: OIDC discovery failed: ${errorMessage(error)}`);
|
|
770
|
+
return loginRedirectTo(request.res, 'oidc_unavailable');
|
|
771
|
+
}
|
|
772
|
+
db.saveOidcState(authorization.state, { nonce: authorization.nonce, codeVerifier: authorization.codeVerifier, enrollment });
|
|
773
|
+
// Lax, not Strict: it has to come back on the provider's cross-site redirect.
|
|
774
|
+
appendSetCookie(request.res, serializeCookie(request.raw, OIDC_STATE_COOKIE, authorization.state, {
|
|
775
|
+
maxAgeSeconds: OIDC_STATE_TTL_MS / 1000, path: OIDC_COOKIE_PATH, sameSite: 'Lax',
|
|
776
|
+
}));
|
|
777
|
+
return redirect(request.res, authorization.url);
|
|
778
|
+
}, PUBLIC);
|
|
779
|
+
router.add('GET', '/auth/oidc/callback', async (request) => {
|
|
780
|
+
if (!oidc)
|
|
781
|
+
throw new ConsoleError(404, 'oidc_disabled', 'Single sign-on is not configured on this gateway.');
|
|
782
|
+
const limited = limit(request.raw);
|
|
783
|
+
if (limited)
|
|
784
|
+
return limited;
|
|
785
|
+
const { raw, query, res } = request;
|
|
786
|
+
// Every refusal here is a redirect, not a thrown error, so record it on the way out.
|
|
787
|
+
const loginRedirect = (error) => {
|
|
788
|
+
recordAudit(raw, { kind: 'system', id: null }, 'auth.sign_in_failed', { method: 'oidc', code: error });
|
|
789
|
+
return loginRedirectTo(res, error);
|
|
790
|
+
};
|
|
791
|
+
const state = query.get('state') ?? '';
|
|
792
|
+
const cookieState = readCookie(raw, OIDC_STATE_COOKIE) ?? '';
|
|
793
|
+
clearCookie(raw, res, OIDC_STATE_COOKIE, OIDC_COOKIE_PATH, 'Lax');
|
|
794
|
+
if (query.get('error'))
|
|
795
|
+
return loginRedirect('oidc_denied');
|
|
796
|
+
if (!state || !cookieState || !safeEqual(state, cookieState))
|
|
797
|
+
return loginRedirect('oidc_state');
|
|
798
|
+
const saved = db.takeOidcState(state);
|
|
799
|
+
const code = query.get('code');
|
|
800
|
+
if (!saved || !code)
|
|
801
|
+
return loginRedirect('oidc_state');
|
|
802
|
+
let identity;
|
|
803
|
+
try {
|
|
804
|
+
identity = await oidc.identityFromCode(code, saved.codeVerifier, saved.nonce);
|
|
805
|
+
}
|
|
806
|
+
catch (error) {
|
|
807
|
+
deps.log(`Console: OIDC sign-in rejected: ${errorMessage(error)}`);
|
|
808
|
+
return loginRedirect('oidc_failed');
|
|
809
|
+
}
|
|
810
|
+
let enrollmentMemberId = null;
|
|
811
|
+
if (saved.enrollmentHash) {
|
|
812
|
+
enrollmentMemberId = db.consumeEnrollmentHash(saved.enrollmentHash);
|
|
813
|
+
if (!enrollmentMemberId)
|
|
814
|
+
return loginRedirect('invalid_enrollment');
|
|
815
|
+
}
|
|
816
|
+
const resolved = db.transaction(() => resolveExternalMember(identity, enrollmentMemberId, true, raw));
|
|
817
|
+
if ('error' in resolved)
|
|
818
|
+
return loginRedirect(resolved.error === 'disabled' ? 'member_disabled' : resolved.error);
|
|
819
|
+
if (resolved.member.status !== 'active')
|
|
820
|
+
return loginRedirect('member_disabled');
|
|
821
|
+
startSession(raw, res, { kind: 'member', memberId: resolved.member.id }, 'oidc', db.findOidc(identity.issuer, identity.subject)?.id ?? null);
|
|
822
|
+
return redirect(res, '/console');
|
|
823
|
+
}, PUBLIC);
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Re-authentication: a fresh proof (passkey or wallet) for the signed-in
|
|
827
|
+
* member's current session. It never signs anyone else in: a credential
|
|
828
|
+
* of another member is refused and the session stays as it was.
|
|
829
|
+
*/
|
|
830
|
+
function registerReauthRoutes(router) {
|
|
831
|
+
router.add('POST', '/auth/reauth/passkey/options', async (request) => {
|
|
832
|
+
const memberId = reauthMemberId(request);
|
|
833
|
+
const limited = limit(request.raw, `reauth:${memberId}`);
|
|
834
|
+
if (limited)
|
|
835
|
+
return limited;
|
|
836
|
+
const rp = passkeyRp(request.raw);
|
|
837
|
+
const passkeys = memberPasskeys(memberId);
|
|
838
|
+
if (passkeys.length === 0)
|
|
839
|
+
throw new ConsoleError(400, 'no_passkey', 'You have no passkey. Confirm with your wallet instead.');
|
|
840
|
+
const options = await generateAuthenticationOptions({
|
|
841
|
+
rpID: rp.hostname,
|
|
842
|
+
userVerification: 'required',
|
|
843
|
+
allowCredentials: passkeys.map((row) => ({ id: row.webauthn_id, transports: parseTransports(row.transports) })),
|
|
844
|
+
});
|
|
845
|
+
// Bound to the member: a plain sign-in challenge (no member) can't be used here.
|
|
846
|
+
db.saveChallenge(options.challenge, 'login', memberId);
|
|
847
|
+
return options;
|
|
848
|
+
}, MEMBER_ONLY);
|
|
849
|
+
router.add('POST', '/auth/reauth/passkey/verify', async (request) => {
|
|
850
|
+
const memberId = reauthMemberId(request);
|
|
851
|
+
const limited = limit(request.raw, `reauth:${memberId}`);
|
|
852
|
+
if (limited)
|
|
853
|
+
return limited;
|
|
854
|
+
const response = objectBody(request.body).response;
|
|
855
|
+
const challenge = challengeOf(response);
|
|
856
|
+
const saved = challenge ? db.takeChallenge(challenge, 'login') : null;
|
|
857
|
+
if (!response || !challenge || !saved || saved.memberId !== memberId) {
|
|
858
|
+
throw new ConsoleError(400, 'invalid_challenge', 'This confirmation request expired. Try again.');
|
|
859
|
+
}
|
|
860
|
+
const stored = db.findPasskey(typeof response.id === 'string' ? response.id : '');
|
|
861
|
+
if (!stored)
|
|
862
|
+
throw wrongMember(request, 'passkey', 'This passkey is not registered with this gateway.');
|
|
863
|
+
const verification = await verifyPasskey(request.raw, response, challenge, stored);
|
|
864
|
+
if (stored.member_id !== memberId)
|
|
865
|
+
throw wrongMember(request, 'passkey', 'This passkey belongs to another member. Confirm with your own.');
|
|
866
|
+
db.touchCredential(stored.id, verification.authenticationInfo.newCounter);
|
|
867
|
+
return finishReauth(request, 'passkey', stored.id);
|
|
868
|
+
}, MEMBER_ONLY);
|
|
869
|
+
router.add('POST', '/auth/reauth/wallet/nonce', async (request) => {
|
|
870
|
+
const memberId = reauthMemberId(request);
|
|
871
|
+
const limited = limit(request.raw, `reauth:${memberId}`);
|
|
872
|
+
if (limited)
|
|
873
|
+
return limited;
|
|
874
|
+
return walletNonce(request.raw, objectBody(request.body).address);
|
|
875
|
+
}, MEMBER_ONLY);
|
|
876
|
+
router.add('POST', '/auth/reauth/wallet/verify', async (request) => {
|
|
877
|
+
const memberId = reauthMemberId(request);
|
|
878
|
+
const limited = limit(request.raw, `reauth:${memberId}`);
|
|
879
|
+
if (limited)
|
|
880
|
+
return limited;
|
|
881
|
+
const input = objectBody(request.body);
|
|
882
|
+
const signer = verifyWalletSignature(stringField(input, 'message'), stringField(input, 'signature'));
|
|
883
|
+
const credential = db.findWallet(signer);
|
|
884
|
+
if (!credential || credential.member_id !== memberId) {
|
|
885
|
+
throw wrongMember(request, 'wallet', 'This wallet is not one of your sign-in methods. Confirm with your own.');
|
|
886
|
+
}
|
|
887
|
+
db.touchCredential(credential.id);
|
|
888
|
+
return finishReauth(request, 'wallet', credential.id);
|
|
889
|
+
}, MEMBER_ONLY);
|
|
890
|
+
}
|
|
891
|
+
/** API-key sign-in, sign-out and the current principal. */
|
|
892
|
+
function registerSessionRoutes(router) {
|
|
893
|
+
router.add('POST', '/auth/api-key', signInFailures('api-key', async (request) => {
|
|
894
|
+
const secret = stringField(objectBody(request.body), 'key').trim();
|
|
895
|
+
const limited = limit(request.raw, `key:${sha256Hex(secret)}`);
|
|
896
|
+
if (limited)
|
|
897
|
+
return limited;
|
|
898
|
+
const key = store.findKeyBySecret(secret);
|
|
899
|
+
if (key?.status === 'revoked')
|
|
900
|
+
throw new ConsoleError(401, 'key_revoked', 'This API key has been revoked.');
|
|
901
|
+
if (key && key.expiresAt !== null && key.expiresAt <= deps.now())
|
|
902
|
+
throw new ConsoleError(401, 'key_expired', 'This API key has expired.');
|
|
903
|
+
if (!keyUsable(key))
|
|
904
|
+
throw new ConsoleError(401, 'invalid_key', 'This API key is not valid.');
|
|
905
|
+
const sessionId = startSession(request.raw, request.res, { kind: 'key', keyId: key.id }, 'api-key');
|
|
906
|
+
return me({ kind: 'key', keyId: key.id, sessionId });
|
|
907
|
+
}), PUBLIC);
|
|
908
|
+
router.add('POST', '/auth/logout', async ({ raw, res }) => {
|
|
909
|
+
const cookie = readCookie(raw, SESSION_COOKIE);
|
|
910
|
+
if (cookie)
|
|
911
|
+
db.deleteSessionBySecret(cookie);
|
|
912
|
+
clearCookie(raw, res, SESSION_COOKIE, '/console', 'Strict');
|
|
913
|
+
return respond(204);
|
|
914
|
+
}, PUBLIC);
|
|
915
|
+
router.add('GET', '/auth/me', async (request) => me(request.principal), { allow: ['member', 'key'] });
|
|
916
|
+
}
|
|
917
|
+
function authConfig(req) {
|
|
918
|
+
return {
|
|
919
|
+
setupRequired: setupClaimable(),
|
|
920
|
+
passkey: passkeyAvailable(req),
|
|
921
|
+
wallet: true,
|
|
922
|
+
oidc: oidcConfig ? { label: oidcConfig.label } : null,
|
|
923
|
+
cloudflareAccess: cloudflare !== null,
|
|
924
|
+
apiKeyLogin: true,
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
return {
|
|
928
|
+
authenticate,
|
|
929
|
+
registerRoutes,
|
|
930
|
+
me,
|
|
931
|
+
authConfig,
|
|
932
|
+
credentialsFor,
|
|
933
|
+
revokeMemberSessions(memberId) {
|
|
934
|
+
db.deleteMemberSessions(memberId);
|
|
935
|
+
},
|
|
936
|
+
revokeKeySessions(keyId) {
|
|
937
|
+
db.deleteKeySessions(keyId);
|
|
938
|
+
},
|
|
939
|
+
deleteCredential(memberId, credentialId, keepSessionId = null) {
|
|
940
|
+
const deleted = db.deleteCredential(memberId, credentialId);
|
|
941
|
+
if (deleted)
|
|
942
|
+
db.deleteMemberSessionsExcept(memberId, keepSessionId);
|
|
943
|
+
return deleted;
|
|
944
|
+
},
|
|
945
|
+
requireFreshSignIn,
|
|
946
|
+
createEnrollment(memberId) {
|
|
947
|
+
return db.createEnrollment(memberId);
|
|
948
|
+
},
|
|
949
|
+
setGatewayPort(port) {
|
|
950
|
+
gatewayPort = port;
|
|
951
|
+
},
|
|
952
|
+
createSetupLink() {
|
|
953
|
+
if (!setupClaimable())
|
|
954
|
+
throw new Error('The console has already been claimed by its owner; invite more members from the console.');
|
|
955
|
+
const token = db.createSetupToken();
|
|
956
|
+
// localhost, not 127.0.0.1: browsers refuse passkeys on IP addresses.
|
|
957
|
+
return `${publicUrl ?? `http://localhost:${gatewayPort}`}/console/setup#${token}`;
|
|
958
|
+
},
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
function objectBody(body) {
|
|
962
|
+
return body && typeof body === 'object' && !Array.isArray(body) ? body : {};
|
|
963
|
+
}
|
|
964
|
+
function stringField(input, name) {
|
|
965
|
+
const value = input[name];
|
|
966
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > 8192) {
|
|
967
|
+
throw new ConsoleError(400, 'invalid_request', `"${name}" is required.`);
|
|
968
|
+
}
|
|
969
|
+
return value;
|
|
970
|
+
}
|
|
971
|
+
function optionalString(input, name) {
|
|
972
|
+
const value = input[name];
|
|
973
|
+
return typeof value === 'string' && value.length > 0 && value.length <= 8192 ? value : null;
|
|
974
|
+
}
|
|
975
|
+
/** The challenge the browser signed, read from clientDataJSON so the server can find its saved copy. */
|
|
976
|
+
function challengeOf(response) {
|
|
977
|
+
const encoded = response?.response?.clientDataJSON;
|
|
978
|
+
if (typeof encoded !== 'string')
|
|
979
|
+
return null;
|
|
980
|
+
try {
|
|
981
|
+
const parsed = JSON.parse(Buffer.from(encoded, 'base64url').toString('utf8'));
|
|
982
|
+
return typeof parsed.challenge === 'string' ? parsed.challenge : null;
|
|
983
|
+
}
|
|
984
|
+
catch {
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
function parseTransports(value) {
|
|
989
|
+
if (!value)
|
|
990
|
+
return undefined;
|
|
991
|
+
try {
|
|
992
|
+
const parsed = JSON.parse(value);
|
|
993
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === 'string') : undefined;
|
|
994
|
+
}
|
|
995
|
+
catch {
|
|
996
|
+
return undefined;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
function wireAmount(amount) {
|
|
1000
|
+
if (typeof amount === 'number')
|
|
1001
|
+
return usdcToDecimalString(amount);
|
|
1002
|
+
if (typeof amount === 'string')
|
|
1003
|
+
return amount;
|
|
1004
|
+
return null;
|
|
1005
|
+
}
|
|
1006
|
+
/** Store limits are USDC base units (numbers); the console wants decimal strings for every period. */
|
|
1007
|
+
function toSpendLimits(value) {
|
|
1008
|
+
const source = value && typeof value === 'object' ? value : {};
|
|
1009
|
+
const limits = {};
|
|
1010
|
+
for (const period of BUDGET_PERIODS)
|
|
1011
|
+
limits[period] = wireAmount(source[period]);
|
|
1012
|
+
return limits;
|
|
1013
|
+
}
|
|
1014
|
+
//# sourceMappingURL=index.js.map
|