payment-kit 1.29.1 → 1.29.3
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/api/dev.ts +41 -2
- package/api/hono.d.ts +42 -0
- package/api/node-sqlite.d.ts +12 -0
- package/api/src/bootstrap.ts +47 -0
- package/api/src/crons/base.ts +3 -3
- package/api/src/crons/currency.ts +1 -1
- package/api/src/crons/index.ts +41 -37
- package/api/src/crons/metering-subscription-detection.ts +1 -1
- package/api/src/crons/overdue-detection.ts +2 -2
- package/api/src/crons/retry-pending-events.ts +6 -0
- package/api/src/crons/tenant-fanout.ts +82 -0
- package/api/src/host-node/did-connect-runtime-node.ts +33 -0
- package/api/src/host-node/serve-static-arc.ts +68 -0
- package/api/src/host-node/serve-static.ts +41 -0
- package/api/src/index.ts +22 -161
- package/api/src/integrations/app-store/client.ts +3 -4
- package/api/src/integrations/app-store/handlers/subscription.ts +7 -7
- package/api/src/integrations/app-store/signed-data-verifier.ts +3 -2
- package/api/src/integrations/arcblock/token.ts +21 -7
- package/api/src/integrations/google-play/handlers/subscription.ts +6 -6
- package/api/src/integrations/google-play/handlers/voided.ts +2 -2
- package/api/src/integrations/google-play/verify.ts +3 -2
- package/api/src/integrations/iap-reconcile.ts +3 -5
- package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
- package/api/src/integrations/stripe/handlers/subscription.ts +3 -3
- package/api/src/libs/archive/query.ts +19 -0
- package/api/src/libs/audit.ts +61 -4
- package/api/src/libs/auth.ts +247 -47
- package/api/src/libs/context.ts +89 -1
- package/api/src/libs/currency.ts +2 -2
- package/api/src/libs/dayjs.ts +8 -2
- package/api/src/libs/did-connect/runtime-did-connect-js.ts +88 -0
- package/api/src/libs/did-connect/tenant-identity.ts +221 -0
- package/api/src/libs/drivers/auth-storage.ts +118 -0
- package/api/src/libs/drivers/cron.ts +264 -0
- package/api/src/libs/drivers/db.ts +170 -0
- package/api/src/libs/drivers/identity.ts +142 -0
- package/api/src/libs/drivers/index.ts +40 -0
- package/api/src/libs/drivers/locks.ts +226 -0
- package/api/src/libs/drivers/migrate-runner.ts +70 -0
- package/api/src/libs/drivers/queue.ts +104 -0
- package/api/src/libs/drivers/secrets.ts +194 -0
- package/api/src/libs/env.ts +170 -54
- package/api/src/libs/exchange-rate/service.ts +7 -6
- package/api/src/libs/http-fetch-adapter.ts +60 -0
- package/api/src/libs/invoice.ts +1 -1
- package/api/src/libs/lock.ts +51 -47
- package/api/src/libs/logger.ts +48 -8
- package/api/src/libs/notification/index.ts +1 -1
- package/api/src/libs/notification/template/customer-credit-low-balance.ts +2 -1
- package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -1
- package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -1
- package/api/src/libs/overdraft-protection.ts +1 -1
- package/api/src/libs/payout.ts +1 -1
- package/api/src/libs/queue/index.ts +271 -52
- package/api/src/libs/queue/runtime.ts +175 -0
- package/api/src/libs/resource.ts +3 -3
- package/api/src/libs/secrets.ts +38 -0
- package/api/src/libs/session.ts +3 -2
- package/api/src/libs/subscription.ts +5 -5
- package/api/src/libs/tenant.ts +92 -0
- package/api/src/libs/url.ts +3 -3
- package/api/src/libs/util.ts +21 -13
- package/api/src/middlewares/hono/cdn.ts +63 -0
- package/api/src/middlewares/hono/context.ts +80 -0
- package/api/src/middlewares/hono/csrf.ts +83 -0
- package/api/src/middlewares/hono/fallback.ts +194 -0
- package/api/src/middlewares/hono/pipeline.ts +73 -0
- package/api/src/middlewares/hono/resource-mount.ts +42 -0
- package/api/src/middlewares/hono/resource.ts +63 -0
- package/api/src/middlewares/hono/security.ts +209 -0
- package/api/src/middlewares/hono/session.ts +114 -0
- package/api/src/middlewares/hono/xss.ts +61 -0
- package/api/src/queues/auto-recharge.ts +12 -10
- package/api/src/queues/checkout-session.ts +38 -21
- package/api/src/queues/credit-consume.ts +40 -36
- package/api/src/queues/credit-grant.ts +25 -18
- package/api/src/queues/credit-reconciliation.ts +7 -5
- package/api/src/queues/discount-status.ts +9 -6
- package/api/src/queues/event.ts +41 -11
- package/api/src/queues/exchange-rate-health.ts +49 -30
- package/api/src/queues/invoice.ts +18 -15
- package/api/src/queues/notification.ts +14 -7
- package/api/src/queues/payment.ts +64 -37
- package/api/src/queues/payout.ts +37 -21
- package/api/src/queues/refund.ts +36 -18
- package/api/src/queues/subscription.ts +83 -53
- package/api/src/queues/token-transfer.ts +15 -10
- package/api/src/queues/usage-record.ts +8 -5
- package/api/src/queues/vendors/commission.ts +7 -5
- package/api/src/queues/vendors/fulfillment-coordinator.ts +17 -13
- package/api/src/queues/vendors/fulfillment.ts +4 -2
- package/api/src/queues/vendors/return-processor.ts +5 -3
- package/api/src/queues/vendors/return-scanner.ts +5 -4
- package/api/src/queues/vendors/status-check.ts +10 -7
- package/api/src/queues/webhook.ts +60 -32
- package/api/src/routes/connect/shared.ts +1 -2
- package/api/src/routes/connect/subscribe.ts +3 -3
- package/api/src/routes/{archive.ts → hono/archive.ts} +69 -64
- package/api/src/routes/{auto-recharge-configs.ts → hono/auto-recharge-configs.ts} +39 -28
- package/api/src/routes/{checkout-sessions.ts → hono/checkout-sessions.ts} +790 -923
- package/api/src/routes/{coupons.ts → hono/coupons.ts} +93 -76
- package/api/src/routes/{credit-grants.ts → hono/credit-grants.ts} +140 -126
- package/api/src/routes/hono/credit-tokens.ts +43 -0
- package/api/src/routes/{credit-transactions.ts → hono/credit-transactions.ts} +37 -29
- package/api/src/routes/{customers.ts → hono/customers.ts} +199 -224
- package/api/src/routes/{donations.ts → hono/donations.ts} +41 -32
- package/api/src/routes/{entitlements.ts → hono/entitlements.ts} +28 -25
- package/api/src/routes/{events.ts → hono/events.ts} +107 -71
- package/api/src/routes/{exchange-rate-providers.ts → hono/exchange-rate-providers.ts} +138 -126
- package/api/src/routes/hono/exchange-rates.ts +77 -0
- package/api/src/routes/hono/index.ts +115 -0
- package/api/src/routes/{integrations → hono/integrations}/app-store.ts +68 -48
- package/api/src/routes/{integrations → hono/integrations}/google-play.ts +78 -58
- package/api/src/routes/hono/integrations/stripe.ts +74 -0
- package/api/src/routes/{invoices.ts → hono/invoices.ts} +253 -244
- package/api/src/routes/{meter-events.ts → hono/meter-events.ts} +120 -110
- package/api/src/routes/hono/meters.ts +288 -0
- package/api/src/routes/hono/passports.ts +73 -0
- package/api/src/routes/{payment-currencies.ts → hono/payment-currencies.ts} +219 -197
- package/api/src/routes/{payment-intents.ts → hono/payment-intents.ts} +136 -132
- package/api/src/routes/{payment-links.ts → hono/payment-links.ts} +145 -128
- package/api/src/routes/{payment-methods.ts → hono/payment-methods.ts} +125 -93
- package/api/src/routes/{payment-stats.ts → hono/payment-stats.ts} +30 -25
- package/api/src/routes/{payouts.ts → hono/payouts.ts} +55 -47
- package/api/src/routes/{prices.ts → hono/prices.ts} +265 -242
- package/api/src/routes/{pricing-table.ts → hono/pricing-table.ts} +94 -87
- package/api/src/routes/{products.ts → hono/products.ts} +172 -159
- package/api/src/routes/{promotion-codes.ts → hono/promotion-codes.ts} +207 -185
- package/api/src/routes/hono/redirect.ts +24 -0
- package/api/src/routes/{refunds.ts → hono/refunds.ts} +98 -83
- package/api/src/routes/{settings.ts → hono/settings.ts} +64 -55
- package/api/src/routes/{subscription-items.ts → hono/subscription-items.ts} +64 -57
- package/api/src/routes/{subscriptions.ts → hono/subscriptions.ts} +475 -528
- package/api/src/routes/{tax-rates.ts → hono/tax-rates.ts} +71 -70
- package/api/src/routes/hono/tool.ts +69 -0
- package/api/src/routes/{usage-records.ts → hono/usage-records.ts} +47 -42
- package/api/src/routes/{vendor.ts → hono/vendor.ts} +315 -167
- package/api/src/routes/{webhook-attempts.ts → hono/webhook-attempts.ts} +17 -13
- package/api/src/routes/hono/webhook-endpoints.ts +126 -0
- package/api/src/service.ts +814 -0
- package/api/src/store/migrations/20230911-seeding.ts +2 -1
- package/api/src/store/migrations/20260609-remove-did-space-jobs.ts +23 -0
- package/api/src/store/migrations/20260610-tenant-columns.ts +40 -0
- package/api/src/store/migrations/20260611-tenant-backfill.ts +33 -0
- package/api/src/store/models/auto-recharge-config.ts +22 -10
- package/api/src/store/models/checkout-session.ts +15 -14
- package/api/src/store/models/coupon.ts +29 -20
- package/api/src/store/models/credit-grant.ts +38 -29
- package/api/src/store/models/credit-transaction.ts +32 -21
- package/api/src/store/models/customer.ts +19 -17
- package/api/src/store/models/discount.ts +11 -2
- package/api/src/store/models/entitlement-grant.ts +21 -9
- package/api/src/store/models/entitlement-product.ts +21 -9
- package/api/src/store/models/entitlement.ts +19 -10
- package/api/src/store/models/event.ts +18 -9
- package/api/src/store/models/exchange-rate-provider.ts +17 -4
- package/api/src/store/models/invoice-item.ts +18 -9
- package/api/src/store/models/invoice.ts +16 -8
- package/api/src/store/models/meter-event.ts +27 -9
- package/api/src/store/models/meter.ts +31 -22
- package/api/src/store/models/payment-currency.ts +25 -8
- package/api/src/store/models/payment-intent.ts +15 -6
- package/api/src/store/models/payment-link.ts +15 -6
- package/api/src/store/models/payment-method.ts +38 -22
- package/api/src/store/models/payment-stat.ts +18 -9
- package/api/src/store/models/payout.ts +15 -6
- package/api/src/store/models/price-quote.ts +17 -8
- package/api/src/store/models/price.ts +24 -12
- package/api/src/store/models/pricing-table.ts +29 -20
- package/api/src/store/models/product-vendor.ts +20 -10
- package/api/src/store/models/product.ts +15 -6
- package/api/src/store/models/promotion-code.ts +14 -6
- package/api/src/store/models/refund.ts +15 -6
- package/api/src/store/models/revenue-snapshot.ts +21 -9
- package/api/src/store/models/setting.ts +18 -9
- package/api/src/store/models/setup-intent.ts +36 -27
- package/api/src/store/models/subscription-item.ts +21 -9
- package/api/src/store/models/subscription-schedule.ts +21 -9
- package/api/src/store/models/subscription.ts +21 -10
- package/api/src/store/models/tax-rate.ts +29 -21
- package/api/src/store/models/usage-record.ts +11 -2
- package/api/src/store/models/webhook-attempt.ts +18 -9
- package/api/src/store/models/webhook-endpoint.ts +18 -9
- package/api/src/store/scoped-core.ts +55 -0
- package/api/src/store/scoped.ts +247 -0
- package/api/src/store/sequelize.ts +82 -23
- package/api/src/store/sql-migrations.ts +20 -0
- package/api/src/store/tenant-backfill.ts +260 -0
- package/api/src/store/tenant-model.ts +124 -0
- package/api/src/store/tenant-tables.ts +50 -0
- package/api/tests/bootstrap/bootstrap.spec.ts +162 -0
- package/api/tests/crons/tenant-fanout.spec.ts +158 -0
- package/api/tests/embedded/embedded-multi-mode-d3.spec.ts +257 -0
- package/api/tests/fixtures/bare-query-violation.ts +13 -0
- package/api/tests/fixtures/core-env-violation.ts +10 -0
- package/api/tests/fixtures/host-read-violation.ts +19 -0
- package/api/tests/fixtures/tenants.ts +4 -0
- package/api/tests/integrations/iap-tenant.spec.ts +284 -0
- package/api/tests/libs/archive-query.spec.ts +26 -0
- package/api/tests/libs/audit-tenant.spec.ts +153 -0
- package/api/tests/libs/context.spec.ts +204 -0
- package/api/tests/libs/core-config.spec.ts +115 -0
- package/api/tests/libs/cron-driver-d2.spec.ts +237 -0
- package/api/tests/libs/crons-conservation-d2.spec.ts +52 -0
- package/api/tests/libs/did-connect-runtime-js.spec.ts +98 -0
- package/api/tests/libs/did-connect-tenant-identity.spec.ts +159 -0
- package/api/tests/libs/lock-tenant.spec.ts +66 -0
- package/api/tests/libs/scoped.spec.ts +222 -0
- package/api/tests/libs/secrets-facade.spec.ts +52 -0
- package/api/tests/libs/service-host.spec.ts +37 -0
- package/api/tests/libs/tenancy-slot-authority.spec.ts +209 -0
- package/api/tests/libs/tenant-middleware.spec.ts +42 -0
- package/api/tests/libs/tenant-scanner.spec.ts +120 -0
- package/api/tests/middlewares/hono/cdn.spec.ts +70 -0
- package/api/tests/middlewares/hono/context.spec.ts +113 -0
- package/api/tests/middlewares/hono/csrf.spec.ts +136 -0
- package/api/tests/middlewares/hono/fallback.spec.ts +67 -0
- package/api/tests/middlewares/hono/pipeline.spec.ts +47 -0
- package/api/tests/middlewares/hono/security.spec.ts +181 -0
- package/api/tests/middlewares/hono/session.spec.ts +42 -0
- package/api/tests/middlewares/hono/xss.spec.ts +81 -0
- package/api/tests/models/tenant-backfill.spec.ts +287 -0
- package/api/tests/models/tenant-columns-model.spec.ts +46 -0
- package/api/tests/models/tenant-columns.spec.ts +161 -0
- package/api/tests/queues/credit-consume-batch.spec.ts +8 -1
- package/api/tests/queues/credit-consume.spec.ts +8 -1
- package/api/tests/queues/event-tenant.spec.ts +292 -0
- package/api/tests/queues/exchange-rate-health-tenant-d6.spec.ts +62 -0
- package/api/tests/queues/queue-parity.spec.ts +249 -0
- package/api/tests/queues/queue-runtime-surface.spec.ts +277 -0
- package/api/tests/queues/queue-teardown-d2.spec.ts +127 -0
- package/api/tests/queues/tenant-matrix-a.spec.ts +245 -0
- package/api/tests/queues/tenant-matrix-b.spec.ts +168 -0
- package/api/tests/routes/connect/hono-attach.spec.ts +107 -0
- package/api/tests/service/collapse.spec.ts +96 -0
- package/api/tests/service/didconnect-storage-slot.spec.ts +60 -0
- package/api/tests/service/fail-closed-http.spec.ts +79 -0
- package/api/tests/service/static-arc-handler.spec.ts +101 -0
- package/api/tests/service/static-externalized.spec.ts +48 -0
- package/api/tests/store/tenant-crosscut.spec.ts +202 -0
- package/api/tests/store/tenant-model-spike.spec.ts +177 -0
- package/api/tests/store/tenant-model.spec.ts +162 -0
- package/api/tests/store/tenant-residual.spec.ts +196 -0
- package/api/third.d.ts +4 -0
- package/blocklet.yml +1 -1
- package/cloudflare/MIGRATION-RUNBOOK.md +3 -8
- package/cloudflare/README.md +34 -27
- package/cloudflare/STAGING-MIGRATION-GUIDE.md +3 -15
- package/cloudflare/build.ts +33 -13
- package/cloudflare/cf-adapter.ts +419 -0
- package/cloudflare/did-connect-runtime.ts +96 -0
- package/cloudflare/did-connect-token-storage.ts +151 -0
- package/cloudflare/esbuild-cf-config.cjs +407 -0
- package/cloudflare/migrations/0006_tenant_columns.sql +46 -0
- package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
- package/cloudflare/migrations/0008_schema_parity.sql +16 -0
- package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
- package/cloudflare/queue-runtime-mode.ts +13 -0
- package/cloudflare/run-build.js +33 -403
- package/cloudflare/scripts/cf-package-import-probe.mjs +90 -0
- package/cloudflare/scripts/didconnect-mock-smoke.mjs +140 -0
- package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
- package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
- package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
- package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
- package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
- package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/wallet-authenticator.ts +16 -1
- package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +18 -3
- package/cloudflare/shims/cron.ts +38 -158
- package/cloudflare/shims/events.ts +124 -0
- package/cloudflare/shims/fastq.ts +15 -1
- package/cloudflare/shims/nedb-storage.ts +16 -8
- package/cloudflare/shims/xss.ts +8 -0
- package/cloudflare/tenant-middleware.ts +36 -0
- package/cloudflare/tests/cf-adapter.spec.ts +244 -0
- package/cloudflare/tests/did-connect-token-storage.spec.ts +105 -0
- package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
- package/cloudflare/tests/worker-handler-gate.spec.ts +69 -0
- package/cloudflare/vite.config.ts +53 -45
- package/cloudflare/worker.ts +261 -448
- package/cloudflare/wrangler.json +0 -6
- package/cloudflare/wrangler.jsonc +0 -6
- package/cloudflare/wrangler.local-e2e.jsonc +25 -0
- package/cloudflare/wrangler.staging.json +0 -6
- package/jest.config.js +3 -1
- package/package.json +33 -38
- package/scripts/bootstrap-inject.ts +166 -0
- package/scripts/core-env-whitelist.json +1 -0
- package/scripts/e2e-12b-runtime.ts +149 -0
- package/scripts/e2e-core-config.ts +125 -0
- package/scripts/e2e-d1-tenancy.ts +116 -0
- package/scripts/e2e-d2-cron-queue.ts +139 -0
- package/scripts/e2e-d3-embedded-multi.ts +171 -0
- package/scripts/e2e-hono-s2.ts +125 -0
- package/scripts/e2e-hono-s3e.ts +135 -0
- package/scripts/e2e-hono-s4.ts +114 -0
- package/scripts/e2e-migration-contract.ts +100 -0
- package/scripts/e2e-s0.ts +61 -0
- package/scripts/e2e-s1.ts +107 -0
- package/scripts/e2e-s2.ts +178 -0
- package/scripts/e2e-s3.ts +110 -0
- package/scripts/e2e-s4.ts +191 -0
- package/scripts/e2e-s5.ts +139 -0
- package/scripts/e2e-s6.ts +127 -0
- package/scripts/e2e-tenant-model.ts +119 -0
- package/scripts/e2e-tenant-worker.ts +199 -0
- package/scripts/gen-sql-migrations.js +46 -0
- package/scripts/phase8-codemod.js +219 -0
- package/scripts/phase9a-env-getters-codemod.js +82 -0
- package/scripts/scan-core-env.js +109 -0
- package/scripts/scan-tenant-queries.js +235 -0
- package/scripts/schema-drift-guard.ts +210 -0
- package/scripts/tenant-scan-whitelist.json +1 -0
- package/src/app.tsx +2 -1
- package/src/env.d.ts +13 -1
- package/src/libs/service-host.ts +13 -0
- package/tsconfig.json +1 -1
- package/vite.arc.config.ts +159 -0
- package/api/src/libs/did-space.ts +0 -235
- package/api/src/libs/middleware.ts +0 -50
- package/api/src/libs/security.ts +0 -192
- package/api/src/queues/space.ts +0 -662
- package/api/src/routes/credit-tokens.ts +0 -38
- package/api/src/routes/exchange-rates.ts +0 -87
- package/api/src/routes/index.ts +0 -142
- package/api/src/routes/integrations/stripe.ts +0 -61
- package/api/src/routes/meters.ts +0 -274
- package/api/src/routes/passports.ts +0 -68
- package/api/src/routes/redirect.ts +0 -20
- package/api/src/routes/tool.ts +0 -65
- package/api/src/routes/webhook-endpoints.ts +0 -126
- package/api/tests/routes/credit-grants.spec.ts +0 -1261
- package/cloudflare/did-connect-auth.ts +0 -527
- package/cloudflare/shims/did-space-js.ts +0 -17
- package/cloudflare/shims/did-space.ts +0 -11
- package/cloudflare/shims/express-compat/index.ts +0 -80
- package/cloudflare/shims/express-compat/types.ts +0 -41
- package/cloudflare/shims/lock.ts +0 -115
- package/cloudflare/shims/queue.ts +0 -611
- package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
- package/cloudflare/tests/shims/queue-scheduled.spec.ts +0 -186
package/api/src/libs/audit.ts
CHANGED
|
@@ -5,9 +5,58 @@ import type { EventType } from '../store/models';
|
|
|
5
5
|
import { Event } from '../store/models/event';
|
|
6
6
|
import { events } from './event';
|
|
7
7
|
import { context } from './context';
|
|
8
|
+
import logger from './logger';
|
|
9
|
+
import { TENANT_CONTEXT_MISSING, TENANT_MISMATCH, TenantError } from './tenant';
|
|
8
10
|
|
|
9
11
|
const API_VERSION = '2023-09-05';
|
|
10
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Phase 4 (W1-3): tenant of an event, fail-closed.
|
|
15
|
+
* Order: model.instance_did, then TenantContext. Both missing or mutually
|
|
16
|
+
* contradictory -> reject. The rejection only kills the EVENT (the business
|
|
17
|
+
* transaction that triggered the hook is never blocked — see
|
|
18
|
+
* reportAuditFailure and the fire-and-forget catch sites in store/models).
|
|
19
|
+
*/
|
|
20
|
+
function resolveEventTenant(model?: { instance_did?: string | null } | null): string {
|
|
21
|
+
const fromModel = model?.instance_did || undefined;
|
|
22
|
+
// the EXPLICIT context tenant (withTenant), not the single-mode default
|
|
23
|
+
// fill — otherwise every model row of a non-default tenant would falsely
|
|
24
|
+
// conflict with the deployment app DID in single mode
|
|
25
|
+
const explicitContext = context.getContext().instanceDid || undefined;
|
|
26
|
+
if (fromModel && explicitContext && fromModel !== explicitContext) {
|
|
27
|
+
throw new TenantError(
|
|
28
|
+
TENANT_MISMATCH,
|
|
29
|
+
`event tenant conflict: model carries ${fromModel} but context is ${explicitContext}`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
const tenant = fromModel || explicitContext;
|
|
33
|
+
if (tenant) return tenant;
|
|
34
|
+
// neither source explicit: single mode falls back to the app DID,
|
|
35
|
+
// multi mode fails closed
|
|
36
|
+
return context.getInstanceDid();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Create an event row under its resolved tenant. The event belongs to the
|
|
41
|
+
* model's tenant, which may differ from the ambient context (e.g. a system /
|
|
42
|
+
* cross-tenant path acting on another tenant's row). Now that Event extends
|
|
43
|
+
* TenantModel, the create must run under that tenant so stampTenant stamps the
|
|
44
|
+
* matching instance_did instead of rejecting the explicit one as a conflict.
|
|
45
|
+
*/
|
|
46
|
+
function createEventUnderTenant(instanceDid: string, values: any): Promise<any> {
|
|
47
|
+
return context.withTenant(instanceDid, () => Event.create(values));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Structured fire-and-forget alert for failed event creation. Tenant
|
|
52
|
+
* rejections keep their dedicated codes so monitoring/tests can assert them;
|
|
53
|
+
* everything else is EVENT_CREATE_FAILED. Mode-independent and never throws.
|
|
54
|
+
*/
|
|
55
|
+
export function reportAuditFailure(err: any): void {
|
|
56
|
+
const code = err?.code === TENANT_MISMATCH || err?.code === TENANT_CONTEXT_MISSING ? err.code : 'EVENT_CREATE_FAILED';
|
|
57
|
+
logger.error('[audit] event creation failed', { code, message: err?.message || String(err) });
|
|
58
|
+
}
|
|
59
|
+
|
|
11
60
|
/**
|
|
12
61
|
* Invoke every registered listener for `eventName` and await any Promise
|
|
13
62
|
* results. EventEmitter.emit() returns sync — listener async work would
|
|
@@ -58,6 +107,7 @@ export function createEvent(
|
|
|
58
107
|
}
|
|
59
108
|
|
|
60
109
|
async function doCreateEvent(scope: string, type: LiteralUnion<EventType, string>, model: any, options: any = {}) {
|
|
110
|
+
const instanceDid = resolveEventTenant(model);
|
|
61
111
|
const data: any = {
|
|
62
112
|
object: model.dataValues,
|
|
63
113
|
};
|
|
@@ -65,8 +115,9 @@ async function doCreateEvent(scope: string, type: LiteralUnion<EventType, string
|
|
|
65
115
|
data.previous_attributes = pick(model._previousDataValues, options.fields);
|
|
66
116
|
}
|
|
67
117
|
|
|
68
|
-
const event = await
|
|
118
|
+
const event = await createEventUnderTenant(instanceDid, {
|
|
69
119
|
type,
|
|
120
|
+
instance_did: instanceDid,
|
|
70
121
|
api_version: API_VERSION,
|
|
71
122
|
livemode: !!model.livemode,
|
|
72
123
|
object_id: model.id,
|
|
@@ -113,8 +164,10 @@ export async function createStatusEvent(
|
|
|
113
164
|
}
|
|
114
165
|
|
|
115
166
|
const suffix = config[data.object.status];
|
|
116
|
-
const
|
|
167
|
+
const instanceDid = resolveEventTenant(model);
|
|
168
|
+
const event = await createEventUnderTenant(instanceDid, {
|
|
117
169
|
type: [prefix, suffix].join('.'),
|
|
170
|
+
instance_did: instanceDid,
|
|
118
171
|
api_version: API_VERSION,
|
|
119
172
|
livemode: !!model.livemode,
|
|
120
173
|
object_id: model.id,
|
|
@@ -150,8 +203,10 @@ export async function createCustomEvent(
|
|
|
150
203
|
return;
|
|
151
204
|
}
|
|
152
205
|
|
|
153
|
-
const
|
|
206
|
+
const instanceDid = resolveEventTenant(model);
|
|
207
|
+
const event = await createEventUnderTenant(instanceDid, {
|
|
154
208
|
type: [prefix, suffix].join('.'),
|
|
209
|
+
instance_did: instanceDid,
|
|
155
210
|
api_version: API_VERSION,
|
|
156
211
|
livemode: !!model.livemode,
|
|
157
212
|
object_id: model.id,
|
|
@@ -185,9 +240,11 @@ export async function createFlexibleEvent(
|
|
|
185
240
|
} = {}
|
|
186
241
|
) {
|
|
187
242
|
const { livemode = false, requestedBy, metadata = {} } = options;
|
|
243
|
+
const instanceDid = resolveEventTenant(null);
|
|
188
244
|
|
|
189
|
-
const event = await
|
|
245
|
+
const event = await createEventUnderTenant(instanceDid, {
|
|
190
246
|
type,
|
|
247
|
+
instance_did: instanceDid,
|
|
191
248
|
api_version: API_VERSION,
|
|
192
249
|
livemode,
|
|
193
250
|
object_id: objectId,
|
package/api/src/libs/auth.ts
CHANGED
|
@@ -1,18 +1,53 @@
|
|
|
1
|
+
import os from 'os';
|
|
1
2
|
import path from 'path';
|
|
2
3
|
|
|
3
|
-
import AuthStorage from '@arcblock/did-connect-storage-nedb';
|
|
4
|
-
// @ts-ignore
|
|
5
|
-
import { BlockletService } from '@blocklet/sdk/service/auth';
|
|
6
|
-
import { getWallet, getAccessWallet } from '@blocklet/sdk/lib/wallet';
|
|
7
|
-
import { WalletAuthenticator } from '@blocklet/sdk/lib/wallet-authenticator';
|
|
8
|
-
import { WalletHandlers } from '@blocklet/sdk/lib/wallet-handler';
|
|
9
|
-
import type { Request } from 'express';
|
|
10
4
|
import type { LiteralUnion } from 'type-fest';
|
|
11
5
|
import type { WalletObject } from '@ocap/wallet';
|
|
6
|
+
import env, { blockletAppId } from './env';
|
|
7
|
+
import { getIdentityDriver } from './drivers';
|
|
12
8
|
|
|
13
|
-
import env from './env';
|
|
14
9
|
import logger from './logger';
|
|
15
10
|
|
|
11
|
+
// Phase 13b: every blocklet-runtime binding below is constructed LAZILY, on first
|
|
12
|
+
// property access, instead of at module import. Importing this module — and the
|
|
13
|
+
// modules that transitively pull it in (libs/util, libs/payment, the models) —
|
|
14
|
+
// therefore runs NO blocklet side effects, so createEmbeddedPaymentService +
|
|
15
|
+
// rpc.entitlements.check work in a bare host with only the explicit config/db/
|
|
16
|
+
// tenancy slots: no BLOCKLET_APP_SK/PK, no BLOCKLET_DATA_DIR, no ABT_NODE_*, no
|
|
17
|
+
// notification patch. The node/full-handler and background-lifecycle paths still
|
|
18
|
+
// materialize the real bindings on first use — transparently, through the proxies —
|
|
19
|
+
// so every existing consumer (`wallet`, `ethWallet`, `blocklet`, `handlers`,
|
|
20
|
+
// `authenticator`) keeps working unchanged.
|
|
21
|
+
|
|
22
|
+
/** Transparent lazy proxy: defer factory() until the first property access. */
|
|
23
|
+
function lazyProxy<T extends object>(factory: () => T): T {
|
|
24
|
+
let instance: T | undefined;
|
|
25
|
+
const resolve = (): T => {
|
|
26
|
+
instance ??= factory();
|
|
27
|
+
return instance;
|
|
28
|
+
};
|
|
29
|
+
return new Proxy({} as T, {
|
|
30
|
+
get(_t, prop) {
|
|
31
|
+
const obj = resolve();
|
|
32
|
+
const value: any = (obj as any)[prop];
|
|
33
|
+
if (typeof value !== 'function') return value;
|
|
34
|
+
// Don't re-bind jest mock functions: binding returns a plain wrapper that
|
|
35
|
+
// strips jest's mock helpers (.mockImplementation / .mockResolvedValue),
|
|
36
|
+
// which would make `jest.spyOn(blocklet, 'method')` unusable. Mocks ignore
|
|
37
|
+
// `this`, so leaving them unbound is safe. No-op in production (no mocks).
|
|
38
|
+
if (value._isMockFunction) return value;
|
|
39
|
+
return value.bind(obj);
|
|
40
|
+
},
|
|
41
|
+
set(_t, prop, value) {
|
|
42
|
+
(resolve() as any)[prop] = value;
|
|
43
|
+
return true;
|
|
44
|
+
},
|
|
45
|
+
has(_t, prop) {
|
|
46
|
+
return prop in (resolve() as any);
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
16
51
|
// Workaround #2: @blocklet/sdk's notification.getSender() uses
|
|
17
52
|
// `getWallet().address` (BLOCKLET_APP_SK derived) as the sender appDid for
|
|
18
53
|
// relay/EventBus broadcasts. On migrated blocklets that address (e.g. zNKti3…)
|
|
@@ -28,51 +63,214 @@ import logger from './logger';
|
|
|
28
63
|
// Path must be `@blocklet/sdk/service/notification` (no `lib/`) — in Node it
|
|
29
64
|
// is a thin re-export that resolves via the module cache to the same object
|
|
30
65
|
// as `@blocklet/sdk/lib/service/notification`; in CF Workers the esbuild
|
|
31
|
-
// config aliases it to a no-op shim (patching a no-op is harmless).
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
66
|
+
// config aliases it to a no-op shim (patching a no-op is harmless).
|
|
67
|
+
//
|
|
68
|
+
// Phase 13b: applied LAZILY (once) the first time a blocklet binding is
|
|
69
|
+
// materialized, instead of at module import — so a bare host that never touches a
|
|
70
|
+
// binding never requires the wallet/notification modules. The override closure
|
|
71
|
+
// still reads getWallet()/getAccessWallet() at send time, exactly as before.
|
|
72
|
+
let notificationPatched = false;
|
|
73
|
+
function ensureNotificationPatch(): void {
|
|
74
|
+
if (notificationPatched) return;
|
|
75
|
+
notificationPatched = true;
|
|
76
|
+
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
77
|
+
const { getWallet, getAccessWallet } = require('@blocklet/sdk/lib/wallet');
|
|
78
|
+
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
79
|
+
const notificationExports = require('@blocklet/sdk/service/notification');
|
|
80
|
+
notificationExports.getSender = () => ({
|
|
81
|
+
appDid: blockletAppId() || getWallet(undefined, '', 'sk').address,
|
|
82
|
+
wallet: getAccessWallet(),
|
|
83
|
+
});
|
|
84
|
+
// Note: do NOT invoke getSender() here — that would force a wallet read at patch
|
|
85
|
+
// time. Log only the expected appId (config-derived, env-free).
|
|
86
|
+
logger.info('[sdk-patch] notification.getSender overridden', { expectedAppId: blockletAppId() });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function makeWallet(...args: any[]): WalletObject {
|
|
90
|
+
ensureNotificationPatch();
|
|
91
|
+
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
92
|
+
const { getWallet } = require('@blocklet/sdk/lib/wallet');
|
|
93
|
+
return getWallet(...args);
|
|
94
|
+
}
|
|
47
95
|
|
|
48
|
-
|
|
49
|
-
|
|
96
|
+
// The blocklet-server business wallets — env-derived (BLOCKLET_APP_SK), lazy as
|
|
97
|
+
// before. They are the FALLBACK when the active IdentityDriver provides no
|
|
98
|
+
// getBusinessWallet (blocklet-server / tests); the @blocklet/sdk getWallet alone
|
|
99
|
+
// handles the remote-sign / delegation / migration cases a bare appSk cannot.
|
|
100
|
+
const envWallet: WalletObject = lazyProxy(() => makeWallet());
|
|
101
|
+
const envEthWallet: WalletObject = lazyProxy(() => makeWallet('ethereum'));
|
|
50
102
|
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
103
|
+
// THE SINGLE SEAM (wallet-authenticator-dynamic.md §0 — "差异收敛在 IdentityDriver"):
|
|
104
|
+
// the active business wallet is whatever the active IdentityDriver provides, else
|
|
105
|
+
// the env wallet. arc-node + CF inject a driver whose getBusinessWallet resolves the
|
|
106
|
+
// current request/job tenant's wallet from the warmed cache (fail-closed outside a
|
|
107
|
+
// warmed scope); blocklet-server's default driver provides none → env wallet,
|
|
108
|
+
// unchanged. No consumer branches on the runtime — only on driver capability. The
|
|
109
|
+
// driver is consulted on EVERY access so two concurrent tenants never share a wallet.
|
|
110
|
+
function activeBusinessWallet(chain: 'arcblock' | 'ethereum'): WalletObject {
|
|
111
|
+
const driver = getIdentityDriver();
|
|
112
|
+
if (typeof driver.getBusinessWallet === 'function') {
|
|
113
|
+
return driver.getBusinessWallet(chain);
|
|
114
|
+
}
|
|
115
|
+
return chain === 'ethereum' ? envEthWallet : envWallet;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Per-access proxy onto the active tenant's business wallet (see activeBusinessWallet). */
|
|
119
|
+
function businessWalletProxy(chain: 'arcblock' | 'ethereum'): WalletObject {
|
|
120
|
+
return new Proxy({} as WalletObject, {
|
|
121
|
+
get(_t, prop) {
|
|
122
|
+
const w = activeBusinessWallet(chain) as any;
|
|
123
|
+
const value = w[prop];
|
|
124
|
+
if (typeof value !== 'function') return value;
|
|
125
|
+
if (value._isMockFunction) return value; // keep jest spies usable (parity with lazyProxy)
|
|
126
|
+
return value.bind(w);
|
|
127
|
+
},
|
|
128
|
+
set(_t, prop, value) {
|
|
129
|
+
(activeBusinessWallet(chain) as any)[prop] = value;
|
|
130
|
+
return true;
|
|
131
|
+
},
|
|
132
|
+
has(_t, prop) {
|
|
133
|
+
return prop in (activeBusinessWallet(chain) as any);
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const wallet: WalletObject = businessWalletProxy('arcblock');
|
|
139
|
+
export const ethWallet: WalletObject = businessWalletProxy('ethereum');
|
|
140
|
+
|
|
141
|
+
// S3-CF Phase 1 (DID convergence): the DID-Connect token storage is a host slot.
|
|
142
|
+
export interface DidConnectTokenStorage {
|
|
143
|
+
read(token: string): Promise<any> | any;
|
|
144
|
+
create(token: string, status?: string): Promise<any> | any;
|
|
145
|
+
update(token: string, updates: Record<string, any>): Promise<any> | any;
|
|
146
|
+
delete(token: string): Promise<any> | any;
|
|
147
|
+
[key: string]: any;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// S3-CF Phase 1 (DID convergence) — the DID-Connect RUNTIME is host-injectable.
|
|
58
151
|
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
152
|
+
// The boundary is by HOST, not by node-vs-CF:
|
|
153
|
+
// - blocklet-server → the @blocklet/sdk WalletAuthenticator/WalletHandlers
|
|
154
|
+
// wrapper (autoConnect / notification relay /
|
|
155
|
+
// federated login). The ONLY runtime that uses SDK.
|
|
156
|
+
// - arc-node embedded + CF → the REAL @arcblock/did-connect-js stack, identity
|
|
157
|
+
// from AUTH_SERVICE.getInstanceAppIdentity, host-
|
|
158
|
+
// injected tokenStorage. They differ ONLY in the host
|
|
159
|
+
// adapter (storage/DB/event/waitUntil, CF chain
|
|
160
|
+
// config/txEncoder/timeout), NOT in SDK-vs-non-SDK.
|
|
161
|
+
// Unified = the host-injection + identity-resolution mechanism; SDK is one runtime
|
|
162
|
+
// implementation, not the node default. Every host injects via setDidConnectRuntime
|
|
163
|
+
// BEFORE `handlers` first materializes (lazyProxy → buildConnectRoutesHono).
|
|
164
|
+
export interface DidConnectRuntime {
|
|
165
|
+
/** Build the DID-Connect authenticator (signs sessions/certs). */
|
|
166
|
+
createAuthenticator(): any;
|
|
167
|
+
/** Wrap the authenticator + tokenStorage into the WalletHandlers used to attach routes. */
|
|
168
|
+
createHandlers(opts: { authenticator: any; tokenStorage: DidConnectTokenStorage }): any;
|
|
169
|
+
/** The DID-Connect token store; falls back to the blocklet-server nedb default when omitted. */
|
|
170
|
+
tokenStorage?: DidConnectTokenStorage;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let injectedRuntime: DidConnectRuntime | null = null;
|
|
174
|
+
let injectedTokenStorage: DidConnectTokenStorage | null = null;
|
|
175
|
+
|
|
176
|
+
/** Inject the full DID-Connect runtime (CF: real @arcblock/did-connect-js stack). */
|
|
177
|
+
export function setDidConnectRuntime(runtime: DidConnectRuntime | null): void {
|
|
178
|
+
injectedRuntime = runtime;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Inject only the DID-Connect token storage (storage slot; runtime keeps its default authenticator/handlers). */
|
|
182
|
+
export function setDidConnectTokenStorage(storage: DidConnectTokenStorage | null): void {
|
|
183
|
+
injectedTokenStorage = storage;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// The blocklet-server runtime — the ONLY runtime that uses the @blocklet/sdk
|
|
187
|
+
// wallet wrapper (autoConnect / notification relay / memberAppInfo / federated
|
|
188
|
+
// login / BlockletService). This is NOT "the node default": arc-node embedded and
|
|
189
|
+
// CF use the AUTH_SERVICE + @arcblock/did-connect-js runtime instead. The
|
|
190
|
+
// blocklet-server bootstrap injects this explicitly; it is also the legacy
|
|
191
|
+
// fallback when no runtime is injected (tests / a bare blocklet-server start).
|
|
192
|
+
//
|
|
193
|
+
// The signing wallet is derived from BLOCKLET_APP_PK (permanent app pk → address =
|
|
194
|
+
// appId) to fix the migrated-blocklet rotating-session-key cert mismatch
|
|
195
|
+
// ("Agent did does not match with certificate issuer", @blocklet/sdk PR #12810).
|
|
196
|
+
export function createBlockletServerDidConnectRuntime(): DidConnectRuntime {
|
|
197
|
+
return {
|
|
198
|
+
createAuthenticator() {
|
|
199
|
+
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
200
|
+
const { WalletAuthenticator } = require('@blocklet/sdk/lib/wallet-authenticator');
|
|
201
|
+
// @blocklet/sdk bundles @arcblock/did-connect-js@4.0.2, which made `txEncoder`
|
|
202
|
+
// mandatory for encoding *Tx prepareTx claims (scan-to-pay → TransferV3Tx) and
|
|
203
|
+
// dropped the old internal @ocap/client fallback (now only a devDependency; the
|
|
204
|
+
// shipped dist never requires it). The SDK's WalletAuthenticator wrapper does not
|
|
205
|
+
// inject one, so we pass the same CBOR encoder the CF/arc runtime uses
|
|
206
|
+
// (`createTxEncoder` from @ocap/client/encode). It passes through the wrapper's
|
|
207
|
+
// `...getAuthenticatorProps(options)` spread untouched.
|
|
208
|
+
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
209
|
+
const { createTxEncoder } = require('@ocap/client/encode');
|
|
210
|
+
return new WalletAuthenticator({ wallet: makeWallet(undefined, '', 'sk'), txEncoder: createTxEncoder() });
|
|
211
|
+
},
|
|
212
|
+
createHandlers({ authenticator: auth, tokenStorage }) {
|
|
213
|
+
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
214
|
+
const { WalletHandlers } = require('@blocklet/sdk/lib/wallet-handler');
|
|
215
|
+
return new WalletHandlers({ authenticator: auth, tokenStorage });
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Consume the injected runtime. When none is injected we fall back to the
|
|
221
|
+
// blocklet-server (SDK) runtime as a LEGACY compat for a bare blocklet-server /
|
|
222
|
+
// test start — NOT as a generic node default. CF and arc-node embedded hosts MUST
|
|
223
|
+
// inject their AUTH_SERVICE runtime; if a CF host ever reaches this fallback, the
|
|
224
|
+
// workerd @blocklet/sdk wallet-* shims are fail-fast (they throw on construct), so
|
|
225
|
+
// it can never silently register zero DID routes. (A spec also asserts the
|
|
226
|
+
// AUTH_SERVICE runtime never imports the @blocklet/sdk wallet modules.)
|
|
227
|
+
function activeRuntime(): DidConnectRuntime {
|
|
228
|
+
return injectedRuntime ?? createBlockletServerDidConnectRuntime();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function buildTokenStorage(): DidConnectTokenStorage {
|
|
232
|
+
// priority: runtime-provided store → explicit storage slot → blocklet-server
|
|
233
|
+
// nedb default (file-backed; the node blocklet-server host only).
|
|
234
|
+
if (injectedRuntime?.tokenStorage) return injectedRuntime.tokenStorage;
|
|
235
|
+
if (injectedTokenStorage) return injectedTokenStorage;
|
|
236
|
+
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
237
|
+
const AuthStorage = require('@arcblock/did-connect-storage-nedb');
|
|
238
|
+
return new AuthStorage({
|
|
239
|
+
// `env.dataDir` (the blocklet runtime data dir) is undefined in a bare
|
|
240
|
+
// embedded host like arc — `store/sequelize.ts` already guards its own
|
|
241
|
+
// use, but this DID-Connect token store was the one unguarded path and
|
|
242
|
+
// crashed `buildConnectRoutesHono` with `path.join(undefined, …)`. Fall
|
|
243
|
+
// back to the OS temp dir (DID-Connect session tokens are ephemeral; the
|
|
244
|
+
// blocklet server still gets its real dataDir natively).
|
|
245
|
+
dbPath: path.join(env.dataDir || os.tmpdir(), 'auth.db'),
|
|
70
246
|
// @ts-ignore
|
|
71
247
|
onload: console.warn,
|
|
72
|
-
})
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export const authenticator: any = lazyProxy(() => activeRuntime().createAuthenticator());
|
|
252
|
+
|
|
253
|
+
export const handlers: any = lazyProxy(() => {
|
|
254
|
+
const tokenStorage = buildTokenStorage();
|
|
255
|
+
return activeRuntime().createHandlers({ authenticator, tokenStorage });
|
|
73
256
|
});
|
|
74
257
|
|
|
75
|
-
|
|
258
|
+
// The user directory (`blocklet.getUser` etc.). SAME single seam as the wallet:
|
|
259
|
+
// the active IdentityDriver's `directory()` if it provides one, else the real
|
|
260
|
+
// @blocklet/sdk BlockletService. arc-node injects a DID-echo directory (the real
|
|
261
|
+
// BlockletService can't construct without BLOCKLET_APP_ID); CF resolves the real
|
|
262
|
+
// BlockletService to its build-alias shim; blocklet-server gets the real one. No
|
|
263
|
+
// `isCfWorker`/runtime branch — only "does the driver provide a directory".
|
|
264
|
+
export const blocklet: any = lazyProxy(() => {
|
|
265
|
+
const driver = getIdentityDriver();
|
|
266
|
+
if (typeof driver.directory === 'function') {
|
|
267
|
+
return driver.directory();
|
|
268
|
+
}
|
|
269
|
+
ensureNotificationPatch();
|
|
270
|
+
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
271
|
+
const { BlockletService } = require('@blocklet/sdk/service/auth');
|
|
272
|
+
return new BlockletService();
|
|
273
|
+
});
|
|
76
274
|
|
|
77
275
|
export async function getVaultAddress() {
|
|
78
276
|
try {
|
|
@@ -88,7 +286,9 @@ export async function getVaultAddress() {
|
|
|
88
286
|
}
|
|
89
287
|
|
|
90
288
|
export type CallbackArgs = {
|
|
91
|
-
|
|
289
|
+
// did-connect-js passes its framework-adapted request to handler callbacks
|
|
290
|
+
// (createHonoRequest under attachHono); only `.context` is read here.
|
|
291
|
+
request: { context: Record<string, any>; [key: string]: any };
|
|
92
292
|
userDid: string;
|
|
93
293
|
userPk: string;
|
|
94
294
|
didwallet: {
|
package/api/src/libs/context.ts
CHANGED
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
import { AsyncLocalStorage, AsyncResource } from 'async_hooks';
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
TENANT_CONTEXT_MISSING,
|
|
5
|
+
TenantError,
|
|
6
|
+
assertValidInstanceDid,
|
|
7
|
+
getDefaultInstanceDid,
|
|
8
|
+
getTenantMode,
|
|
9
|
+
} from './tenant';
|
|
10
|
+
|
|
11
|
+
export * from './tenant';
|
|
12
|
+
|
|
3
13
|
interface RequestContext {
|
|
4
14
|
requestedBy?: string;
|
|
5
15
|
requestId?: string;
|
|
16
|
+
instanceDid?: string;
|
|
6
17
|
}
|
|
7
18
|
|
|
8
19
|
class RequestContextManager {
|
|
9
20
|
private storage = new AsyncLocalStorage<RequestContext>();
|
|
10
21
|
private contexts = new Map<string, RequestContext>();
|
|
22
|
+
// System-operation flag: when set, TenantModel bypasses tenant scoping so a
|
|
23
|
+
// legitimate cross-tenant read (queue dispatch, IAP bundle->tenant reverse
|
|
24
|
+
// lookup, event fan-out) can load rows across tenants. Entered ONLY via the
|
|
25
|
+
// system* helpers — a normal route context can never read across tenants.
|
|
26
|
+
private systemStorage = new AsyncLocalStorage<boolean>();
|
|
11
27
|
|
|
12
28
|
getContext(requestId?: string): RequestContext {
|
|
13
29
|
if (requestId && this.contexts.has(requestId)) {
|
|
@@ -20,16 +36,73 @@ class RequestContextManager {
|
|
|
20
36
|
return this.getContext(requestId).requestedBy;
|
|
21
37
|
}
|
|
22
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Run fn with the given tenant. Nested calls shadow the outer tenant and restore it on exit.
|
|
41
|
+
* Other context fields (requestId, requestedBy) are inherited from the enclosing scope.
|
|
42
|
+
*/
|
|
43
|
+
withTenant<T>(instanceDid: string, fn: () => Promise<T> | T): Promise<T> {
|
|
44
|
+
try {
|
|
45
|
+
assertValidInstanceDid(instanceDid);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
return Promise.reject(err);
|
|
48
|
+
}
|
|
49
|
+
const parent = this.storage.getStore();
|
|
50
|
+
// frozen so fn cannot mutate the stored context and affect sibling calls
|
|
51
|
+
const next = Object.freeze({ ...parent, instanceDid });
|
|
52
|
+
return this.storage.run(next, () => Promise.resolve(fn()));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Current tenant. Single mode falls back to the deployment app DID;
|
|
57
|
+
* multi mode fails closed with TENANT_CONTEXT_MISSING.
|
|
58
|
+
*/
|
|
59
|
+
getInstanceDid(): string {
|
|
60
|
+
const instanceDid = this.storage.getStore()?.instanceDid;
|
|
61
|
+
if (instanceDid) return instanceDid;
|
|
62
|
+
if (getTenantMode() === 'single') return getDefaultInstanceDid();
|
|
63
|
+
throw new TenantError(TENANT_CONTEXT_MISSING, 'tenant context is missing in multi-tenant mode');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Non-throwing peek at the established tenant context — returns the stored
|
|
68
|
+
* instanceDid or undefined when no context is set (regardless of tenant mode).
|
|
69
|
+
* Used by the DID-Connect tenant-context middleware to decide whether the
|
|
70
|
+
* request is already scoped (e.g. the CF worker wrapped /api/* in withTenant) or
|
|
71
|
+
* needs its own Host→tenant resolution.
|
|
72
|
+
*/
|
|
73
|
+
peekInstanceDid(): string | undefined {
|
|
74
|
+
return this.storage.getStore()?.instanceDid;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Run fn as a system operation: TenantModel scoping is bypassed for the span
|
|
79
|
+
* of fn so legitimate cross-tenant reads can load rows regardless of tenant.
|
|
80
|
+
* The scope ends when fn settles — callers must enforce the row's tenant
|
|
81
|
+
* themselves (e.g. assertJobObjectTenant). Explicit by construction: only the
|
|
82
|
+
* system* helpers enter this, so a normal route can never read across tenants.
|
|
83
|
+
*/
|
|
84
|
+
runAsSystem<T>(fn: () => Promise<T> | T): Promise<T> {
|
|
85
|
+
return this.systemStorage.run(true, () => Promise.resolve(fn()));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** True inside a runAsSystem span — TenantModel checks this to skip scoping. */
|
|
89
|
+
isSystem(): boolean {
|
|
90
|
+
return this.systemStorage.getStore() === true;
|
|
91
|
+
}
|
|
92
|
+
|
|
23
93
|
run<T>(context: RequestContext, fn: () => Promise<T> | T): Promise<T> {
|
|
24
94
|
const requestId = context.requestId || `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
95
|
+
// inherit tenant from the enclosing scope unless explicitly provided
|
|
96
|
+
const instanceDid = context.instanceDid ?? this.storage.getStore()?.instanceDid;
|
|
25
97
|
|
|
26
98
|
this.contexts.set(requestId, {
|
|
27
99
|
...context,
|
|
100
|
+
instanceDid,
|
|
28
101
|
requestId,
|
|
29
102
|
});
|
|
30
103
|
|
|
31
104
|
return new Promise((resolve, reject) => {
|
|
32
|
-
this.storage.run({ ...context, requestId }, async () => {
|
|
105
|
+
this.storage.run({ ...context, instanceDid, requestId }, async () => {
|
|
33
106
|
const resource = new AsyncResource('RequestContext');
|
|
34
107
|
try {
|
|
35
108
|
const result = await resource.runInAsyncScope(fn);
|
|
@@ -46,3 +119,18 @@ class RequestContextManager {
|
|
|
46
119
|
}
|
|
47
120
|
|
|
48
121
|
export const context = new RequestContextManager();
|
|
122
|
+
|
|
123
|
+
/** Run fn with the given tenant — also the test injection helper for jest. */
|
|
124
|
+
export function withTenant<T>(instanceDid: string, fn: () => Promise<T> | T): Promise<T> {
|
|
125
|
+
return context.withTenant(instanceDid, fn);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Current tenant; see RequestContextManager#getInstanceDid for mode semantics. */
|
|
129
|
+
export function getInstanceDid(): string {
|
|
130
|
+
return context.getInstanceDid();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** True inside a runAsSystem span; TenantModel uses it to bypass scoping. */
|
|
134
|
+
export function isSystemContext(): boolean {
|
|
135
|
+
return context.isSystem();
|
|
136
|
+
}
|
package/api/src/libs/currency.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { fromTokenToUnit, fromUnitToToken } from '@ocap/util';
|
|
2
|
-
import { getUrl } from '@blocklet/sdk';
|
|
2
|
+
import { getUrl } from '@blocklet/sdk/lib/component';
|
|
3
3
|
import { PaymentCurrency, Price, Product, RechargeConfig } from '../store/models';
|
|
4
4
|
import { trimDecimals } from './math-utils';
|
|
5
|
-
import { createPaymentLink } from '../routes/payment-links';
|
|
5
|
+
import { createPaymentLink } from '../routes/hono/payment-links';
|
|
6
6
|
import logger from './logger';
|
|
7
7
|
|
|
8
8
|
export async function formatCurrencyToken(amount: string, currencyId: string) {
|
package/api/src/libs/dayjs.ts
CHANGED
|
@@ -5,8 +5,14 @@ import relativeTime from 'dayjs/plugin/relativeTime';
|
|
|
5
5
|
import timezone from 'dayjs/plugin/timezone'; // dependent on utc plugin
|
|
6
6
|
import utc from 'dayjs/plugin/utc';
|
|
7
7
|
|
|
8
|
-
import(
|
|
9
|
-
|
|
8
|
+
// Use explicit `.js` so the dynamic import resolves under Node ESM (strict
|
|
9
|
+
// resolution: dayjs ships no `exports` map, so the extensionless subpath
|
|
10
|
+
// `dayjs/locale/en` fails when payment-core is embedded in a Node ESM host
|
|
11
|
+
// like arc). Webpack/blocklet-server tolerate the extension too.
|
|
12
|
+
// eslint-disable-next-line import/extensions -- explicit .js required for Node ESM hosts (arc)
|
|
13
|
+
import('dayjs/locale/en.js');
|
|
14
|
+
// eslint-disable-next-line import/extensions -- explicit .js required for Node ESM hosts (arc)
|
|
15
|
+
import('dayjs/locale/zh.js');
|
|
10
16
|
|
|
11
17
|
dayjs.extend(relativeTime);
|
|
12
18
|
dayjs.extend(localizedFormat);
|