Bind each customer to a Verae userId for hop tracing
Some checks are pending
offline / test (push) Waiting to run
Some checks are pending
offline / test (push) Waiting to run
Signup registers/binds a Verae central user and stores veraeUserId. Public access stays the zappier API key. Chain JWTs stay server-side behind tokenRef. Authz, billing, and jobs.watch carry veraeUserId.
This commit is contained in:
parent
1b199ca4d4
commit
345aeeead9
79 changed files with 703 additions and 95 deletions
|
|
@ -25,6 +25,7 @@ All subjects are prefixed with `verae.zapier.` to isolate this platform from oth
|
|||
| `tenantId` | string | yes | Owning tenant |
|
||||
| `jobId` | string | yes | Verae job id |
|
||||
| `tokenRef` | string | preferred | Opaque ref to resolve Verae credentials (avoid raw JWT) |
|
||||
| `veraeUserId` | string | preferred | Stable Verae central user id (`vu_…`); not a JWT |
|
||||
| `veraeToken` | string | discouraged | Only if tokenRef unavailable; redacted in logs |
|
||||
| `enqueuedAt` | ISO-8601 | yes | Enqueue time |
|
||||
| `attempt` | number | yes | Delivery attempt (0-based) |
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ One MD file per source module.
|
|||
- [zappier/statement](zappier/statement.md)
|
||||
- [zappier/upstream](zappier/upstream.md)
|
||||
- [zappier/usage](zappier/usage.md)
|
||||
- [zappier/verae-bind](zappier/verae-bind.md)
|
||||
- [verae-zapier-middleware/app](verae-zapier-middleware/app.md)
|
||||
- [verae-zapier-middleware/clients/veraeClient](verae-zapier-middleware/clients/veraeClient.md)
|
||||
- [verae-zapier-middleware/config](verae-zapier-middleware/config.md)
|
||||
|
|
@ -43,6 +44,7 @@ One MD file per source module.
|
|||
- [verae-zapier-middleware/debug/trace](verae-zapier-middleware/debug/trace.md)
|
||||
- [verae-zapier-middleware/errors](verae-zapier-middleware/errors.md)
|
||||
- [verae-zapier-middleware/index](verae-zapier-middleware/index.md)
|
||||
- [verae-zapier-middleware/lib/identity](verae-zapier-middleware/lib/identity.md)
|
||||
- [verae-zapier-middleware/lib/receiptPdf](verae-zapier-middleware/lib/receiptPdf.md)
|
||||
- [verae-zapier-middleware/lib/tokens](verae-zapier-middleware/lib/tokens.md)
|
||||
- [verae-zapier-middleware/middleware/authenticate](verae-zapier-middleware/middleware/authenticate.md)
|
||||
|
|
@ -71,6 +73,7 @@ One MD file per source module.
|
|||
- [verae-zapier-middleware/store/db](verae-zapier-middleware/store/db.md)
|
||||
- [verae-zapier-middleware/store/jobWatchers](verae-zapier-middleware/store/jobWatchers.md)
|
||||
- [verae-zapier-middleware/store/tenants](verae-zapier-middleware/store/tenants.md)
|
||||
- [verae-zapier-middleware/store/tokenRefs](verae-zapier-middleware/store/tokenRefs.md)
|
||||
- [verae-zapier-middleware/store/usage](verae-zapier-middleware/store/usage.md)
|
||||
- [verae-zapier-middleware/store/webhooks](verae-zapier-middleware/store/webhooks.md)
|
||||
- [verae-zapier-middleware/workers/inProcessJobPoller](verae-zapier-middleware/workers/inProcessJobPoller.md)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-access-authz`
|
||||
**Source:** `packages/verae-access-authz/src/policy.js`
|
||||
**Lines:** 99
|
||||
**Lines:** 101
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/clients/veraeClient.js`
|
||||
**Lines:** 352
|
||||
**Lines:** 386
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -24,6 +24,7 @@ _None extracted._
|
|||
| `delay` | `ms` | ms: `number` | `Promise<void>` | see Call graph |
|
||||
| `mockLogin` | `{ username, password }` | ms: `number` | `Promise<void>` | see Call graph |
|
||||
| `mockValidate` | `token` | — | `unknown` | see Call graph |
|
||||
| `mockCreateUser` | `{ username, password, role = 'user' }` | — | `unknown` | see Call graph |
|
||||
| `mockCreateTimestamp` | `{ data, hashAlg, sha256, publicMetadata, privateMetadata }` | — | `unknown` | see Call graph |
|
||||
| `mockGetStatus` | `jobId` | — | `unknown` | see Call graph |
|
||||
| `mockLookupHash` | `sha256` | — | `unknown` | see Call graph |
|
||||
|
|
@ -38,6 +39,8 @@ _None extracted._
|
|||
| `setTimeout` | `(` |
|
||||
| `login` | `credentials` |
|
||||
| `validate` | `token` |
|
||||
| `createUser` | `adminToken, body` |
|
||||
| `bindUser` | `credentials` |
|
||||
| `createTimestamp` | `token, body` |
|
||||
| `lookupHash` | `token, sha256` |
|
||||
| `createBatchTimestamp` | `token, body` |
|
||||
|
|
@ -54,10 +57,11 @@ _None extracted._
|
|||
- `../config.js`
|
||||
- `../errors.js`
|
||||
- `../debug/logger.js`
|
||||
- `../lib/identity.js`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`net`, `createDebugger`, `sha256Hex`, `createHash`, `update`, `digest`, `delay`, `setTimeout`, `mockLogin`, `now`, `toISOString`, `randomUUID`, `includes`, `mockValidate`, `startsWith`, `replace`, `mockCreateTimestamp`, `toLowerCase`, `get`, `set`, `mockGetStatus`, `mockLookupHash`, `mockVerify`, `request`, `debug`, `fetch`, `stringify`, `text`, `parse`, `error`, `client`, `login`, `validate`, `createTimestamp`, `lookupHash`, `createBatchTimestamp`, `push`, `getStatus`, `encodeURIComponent`, `getBatchStatus`, `verify`, `verifyBatch`, `getJobVerification`, `waitForJob`, `jobs`, `clearMockJobs`, `clear`
|
||||
`net`, `createDebugger`, `sha256Hex`, `createHash`, `update`, `digest`, `delay`, `setTimeout`, `mockLogin`, `now`, `toISOString`, `stableVeraeUserId`, `includes`, `mockValidate`, `startsWith`, `replace`, `mockCreateUser`, `mockCreateTimestamp`, `toLowerCase`, `get`, `randomUUID`, `set`, `mockGetStatus`, `mockLookupHash`, `mockVerify`, `request`, `debug`, `fetch`, `stringify`, `text`, `parse`, `error`, `client`, `login`, `validate`, `createUser`, `bindUser`, `createTimestamp`, `lookupHash`, `createBatchTimestamp`, `push`, `getStatus`, `encodeURIComponent`, `getBatchStatus`, `verify`, `verifyBatch`, `getJobVerification`, `waitForJob`, `jobs`, `clearMockJobs`, `clear`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
41
docs/modules/verae-zapier-middleware/lib/identity.md
Normal file
41
docs/modules/verae-zapier-middleware/lib/identity.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# `verae-zapier-middleware/lib/identity`
|
||||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/lib/identity.js`
|
||||
**Lines:** 17
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-zapier-middleware`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
`normalizeVeraeUsername`, `stableVeraeUserId`
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `normalizeVeraeUsername` | `username` | — | `unknown` | see Call graph |
|
||||
| `stableVeraeUserId` | `username` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `node:crypto`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`normalizeVeraeUsername`, `trim`, `toLowerCase`, `stableVeraeUserId`, `createHash`, `update`, `digest`, `slice`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
## Return values (how to read this)
|
||||
|
||||
- HTTP route handlers return Express `res.json(...)` bodies (see route docs).
|
||||
- Zapier `perform` functions return a **single object** (creates) or an **array** (triggers/searches).
|
||||
- Pricing functions return integer **cents** on `Quote.totalCents`.
|
||||
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/middleware/authenticate.js`
|
||||
**Lines:** 28
|
||||
**Lines:** 34
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ _No top-level functions extracted._
|
|||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`createDebugger`, `asyncHandler`, `async`, `extractBearerToken`, `resolveAuthContext`, `debug`, `next`
|
||||
`createDebugger`, `asyncHandler`, `async`, `extractBearerToken`, `resolveAuthContext`, `startsWith`, `debug`, `next`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/nats/publishers.js`
|
||||
**Lines:** 96
|
||||
**Lines:** 98
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/services/authService.js`
|
||||
**Lines:** 142
|
||||
**Lines:** 154
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ _None extracted._
|
|||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`createDebugger`, `loginWithCredentials`, `debug`, `login`, `issueSessionToken`, `loginWithApiKey`, `getTenantByApiKey`, `resolveAuthContext`, `isApiKey`, `parseSessionToken`, `getTenant`, `parse`, `now`, `validateSession`, `validate`
|
||||
`createDebugger`, `loginWithCredentials`, `debug`, `login`, `upsertTenant`, `issueSessionToken`, `loginWithApiKey`, `getTenantByApiKey`, `resolveAuthContext`, `isApiKey`, `parseSessionToken`, `getTenant`, `parse`, `now`, `validateSession`, `validate`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/services/tenantService.js`
|
||||
**Lines:** 138
|
||||
**Lines:** 150
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ _None extracted._
|
|||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`createDebugger`, `slugify`, `toLowerCase`, `replace`, `slice`, `validateVeraeCredentials`, `login`, `selfServeSignup`, `randomUUID`, `createTenant`, `info`, `provision`, `provisionTenant`, `has`, `getTenant`, `listProvisionedTenants`, `listTenants`, `map`
|
||||
`createDebugger`, `slugify`, `toLowerCase`, `replace`, `slice`, `validateVeraeCredentials`, `login`, `selfServeSignup`, `bindUser`, `randomUUID`, `createTenant`, `info`, `provision`, `provisionTenant`, `has`, `getTenant`, `listProvisionedTenants`, `listTenants`, `map`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/services/timestampService.js`
|
||||
**Lines:** 170
|
||||
**Lines:** 171
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -53,13 +53,14 @@ _None extracted._
|
|||
- `../config.js`
|
||||
- `../clients/veraeClient.js`
|
||||
- `../store/jobWatchers.js`
|
||||
- `../store/tokenRefs.js`
|
||||
- `./entitlementService.js`
|
||||
- `../debug/logger.js`
|
||||
- `../debug/trace-context.js`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`createDebugger`, `enqueueWatchForJob`, `getTraceId`, `import`, `enqueueWatch`, `debug`, `enqueueJob`, `createTimestamp`, `checkEntitlement`, `recordUsage`, `createTimestampAndWait`, `waitForJobEvent`, `getStatus`, `waitForJob`, `createBatchTimestamp`, `getJobStatus`, `getBatchJobStatus`, `getBatchStatus`, `getJobVerification`, `lookupHash`
|
||||
`createDebugger`, `enqueueWatchForJob`, `getTraceId`, `import`, `enqueueWatch`, `issueTokenRef`, `debug`, `enqueueJob`, `createTimestamp`, `checkEntitlement`, `recordUsage`, `createTimestampAndWait`, `waitForJobEvent`, `getStatus`, `waitForJob`, `createBatchTimestamp`, `getJobStatus`, `getBatchJobStatus`, `getBatchStatus`, `getJobVerification`, `lookupHash`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/store/db.js`
|
||||
**Lines:** 118
|
||||
**Lines:** 120
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/store/tenants.js`
|
||||
**Lines:** 138
|
||||
**Lines:** 142
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -30,8 +30,9 @@ _None extracted._
|
|||
plan = 'free',
|
||||
veraeUsername,
|
||||
veraePassword,
|
||||
veraeUserId,
|
||||
contract = null,
|
||||
apiKey = generateApiKey(` | params: `object`, params.id: `string`, params.name: `string`, params.plan: `string`, params.veraeUsername: `string`, params.veraePassword: `string`, params.contract: `object|null`, params.apiKey: `string`, params.metadata: `object` | `{ tenant: Tenant, apiKey: string ` — } | see Call graph |
|
||||
apiKey = generateApiKey(` | params: `object`, params.id: `string`, params.name: `string`, params.plan: `string`, params.veraeUsername: `string`, params.veraePassword: `string`, params.veraeUserId: `string`, params.contract: `object|null`, params.apiKey: `string`, params.metadata: `object` | `{ tenant: Tenant, apiKey: string ` — } | see Call graph |
|
||||
| `resolveLimits` | `tenant` | tenant: `Tenant` | `{
|
||||
* timestamps: number|null,
|
||||
* verifications: number|null,
|
||||
|
|
|
|||
48
docs/modules/verae-zapier-middleware/store/tokenRefs.md
Normal file
48
docs/modules/verae-zapier-middleware/store/tokenRefs.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# `verae-zapier-middleware/store/tokenRefs`
|
||||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/store/tokenRefs.js`
|
||||
**Lines:** 21
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-zapier-middleware`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
`issueTokenRef`, `resolveTokenRef`
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `issueTokenRef` | `tenantId` | — | `unknown` | see Call graph |
|
||||
| `resolveTokenRef` | `ref` | — | `unknown` | see Call graph |
|
||||
|
||||
## Methods (class / object)
|
||||
|
||||
| Name | Parameters |
|
||||
|------|------------|
|
||||
| `persist` | `(none)` |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `node:crypto`
|
||||
- `./db.js`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`issueTokenRef`, `randomBytes`, `toString`, `getStore`, `toISOString`, `persist`, `resolveTokenRef`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
## Return values (how to read this)
|
||||
|
||||
- HTTP route handlers return Express `res.json(...)` bodies (see route docs).
|
||||
- Zapier `perform` functions return a **single object** (creates) or an **array** (triggers/searches).
|
||||
- Pricing functions return integer **cents** on `Quote.totalCents`.
|
||||
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/workers/jobPollerWorker.js`
|
||||
**Lines:** 159
|
||||
**Lines:** 161
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -34,11 +34,12 @@ _None extracted._
|
|||
- `../nats/publishers.js`
|
||||
- `../clients/veraeClient.js`
|
||||
- `../store/tenants.js`
|
||||
- `../store/tokenRefs.js`
|
||||
- `../debug/trace.js`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`createDebugger`, `polling`, `resolveVeraeToken`, `getTenant`, `login`, `handleWatch`, `withTrace`, `async`, `publishJobEvent`, `ack`, `getStatus`, `debug`, `nak`, `startJobPollerWorker`, `stopJobPollerWorker`, `connectNats`, `ensureStreams`, `consumer`, `add`, `get`, `info`, `consume`, `parse`, `string`, `error`, `abort`
|
||||
`createDebugger`, `polling`, `resolveVeraeToken`, `resolveTokenRef`, `getTenant`, `login`, `handleWatch`, `withTrace`, `async`, `publishJobEvent`, `ack`, `getStatus`, `debug`, `nak`, `startJobPollerWorker`, `stopJobPollerWorker`, `connectNats`, `ensureStreams`, `consumer`, `add`, `get`, `info`, `consume`, `parse`, `string`, `error`, `abort`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier-account-balance`
|
||||
**Source:** `packages/zappier-account-balance/src/books.js`
|
||||
**Lines:** 90
|
||||
**Lines:** 100
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -30,8 +30,9 @@ Implementation module in `zappier-account-balance`. The tables below are extract
|
|||
| Name | Parameters |
|
||||
|------|------------|
|
||||
| `constructor` | `(none)` |
|
||||
| `remember` | `customerId, veraeUserId` |
|
||||
| `prepaidCents` | `customerId` |
|
||||
| `adjust` | `{ customerId, cents, reason, agent, kind = 'credit' }` |
|
||||
| `adjust` | `{ customerId, cents, reason, agent, kind = 'credit', veraeUserId }` |
|
||||
| `recordUsage` | `entry` |
|
||||
| `recordPayment` | `entry` |
|
||||
| `statement` | `customerId` |
|
||||
|
|
@ -42,7 +43,7 @@ _No imports detected._
|
|||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`constructor`, `prepaidCents`, `adjust`, `trunc`, `now`, `toString`, `random`, `slice`, `toISOString`, `unshift`, `recordUsage`, `recordPayment`, `statement`, `filter`, `match`, `handle`, `endsWith`
|
||||
`constructor`, `remember`, `prepaidCents`, `adjust`, `trunc`, `now`, `toString`, `random`, `slice`, `toISOString`, `unshift`, `recordUsage`, `recordPayment`, `statement`, `filter`, `match`, `handle`, `endsWith`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier`
|
||||
**Source:** `packages/zappier/src/admin.ts`
|
||||
**Lines:** 591
|
||||
**Lines:** 593
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier`
|
||||
**Source:** `packages/zappier/src/app.ts`
|
||||
**Lines:** 272
|
||||
**Lines:** 278
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ Implementation module in `zappier`. The tables below are extracted from the sour
|
|||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`join`, `next`, `parse`, `status`, `json`, `buildApp`, `seeded`, `seedAdminUsersFromEnv`, `rateCard`, `getRateCard`, `tiers`, `getTiers`, `natsPublish`, `toISOString`, `express`, `use`, `load`, `setup`, `adminLoginRouter`, `adminAuth`, `adminRouter`, `static`, `async`, `import`, `toDataURL`, `portalRouter`, `get`, `apiKeyAuth`, `middleware`, `meter`, `post`, `toUpperCase`, `proxyVerae`, `toLowerCase`, `createHash`, `update`, `digest`, `randomUUID`, `set`, `isFinite`, `map`, `unshift`, `filter`, `setUTCDate`, `setUTCHours`, `summaryFor`, `find`, `applyMonthlyCredit`
|
||||
`join`, `next`, `parse`, `status`, `json`, `buildApp`, `seeded`, `seedAdminUsersFromEnv`, `rateCard`, `getRateCard`, `tiers`, `getTiers`, `list`, `find`, `natsPublish`, `toISOString`, `express`, `use`, `load`, `setup`, `adminLoginRouter`, `adminAuth`, `adminRouter`, `static`, `async`, `import`, `toDataURL`, `portalRouter`, `get`, `apiKeyAuth`, `middleware`, `meter`, `post`, `toUpperCase`, `proxyVerae`, `toLowerCase`, `createHash`, `update`, `digest`, `randomUUID`, `set`, `isFinite`, `map`, `unshift`, `filter`, `setUTCDate`, `setUTCHours`, `summaryFor`, `applyMonthlyCredit`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier`
|
||||
**Source:** `packages/zappier/src/auth.ts`
|
||||
**Lines:** 84
|
||||
**Lines:** 87
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ Implementation module in `zappier`. The tables below are extracted from the sour
|
|||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`findByApiKey`, `findByEmail`, `list`, `save`, `constructor`, `arrays`, `map`, `find`, `toLowerCase`, `findIndex`, `push`, `apiKeyAuth`, `header`, `status`, `json`, `next`
|
||||
`id`, `findByApiKey`, `findByEmail`, `list`, `save`, `constructor`, `arrays`, `map`, `find`, `toLowerCase`, `findIndex`, `push`, `apiKeyAuth`, `header`, `status`, `json`, `next`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier`
|
||||
**Source:** `packages/zappier/src/billing-nats.ts`
|
||||
**Lines:** 130
|
||||
**Lines:** 138
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -29,11 +29,13 @@ Implementation module in `zappier`. The tables below are extracted from the sour
|
|||
| `decode` | `buf: Uint8Array` | — | `unknown` | see Call graph |
|
||||
| `authzAllow` | `plane: AccessPlane,
|
||||
subject: string,
|
||||
extra: { principal?: string; kind?: string } = {},` | — | `unknown` | see Call graph |
|
||||
extra: { principal?: string; kind?: string; veraeUserId?: string } = {},` | — | `unknown` | see Call graph |
|
||||
| `natsStatement` | `customerId: string,
|
||||
plane: AccessPlane = 'web',` | — | `unknown` | see Call graph |
|
||||
plane: AccessPlane = 'web',
|
||||
veraeUserId?: string,` | — | `unknown` | see Call graph |
|
||||
| `natsAdjust` | `payload: {
|
||||
customerId: string;
|
||||
veraeUserId?: string;
|
||||
cents: number;
|
||||
reason: string;
|
||||
agent: string;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier`
|
||||
**Source:** `packages/zappier/src/portal.ts`
|
||||
**Lines:** 328
|
||||
**Lines:** 337
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -51,10 +51,11 @@ Implementation module in `zappier`. The tables below are extracted from the sour
|
|||
- `./credits`
|
||||
- `./statement`
|
||||
- `./billing-nats`
|
||||
- `./verae-bind`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`now`, `reload`, `customer`, `publicProfile`, `sessionAuth`, `header`, `startsWith`, `slice`, `get`, `list`, `find`, `status`, `json`, `next`, `save`, `portalRouter`, `post`, `trim`, `test`, `hashPassword`, `findByEmail`, `randomBytes`, `toString`, `create`, `verifyPassword`, `verifyTotp`, `use`, `delete`, `setUTCDate`, `setUTCHours`, `summaryFor`, `tiers`, `applyMonthlyCredit`, `rateCard`, `async`, `natsStatement`, `composeStatement`, `listFor`, `type`, `send`, `renderInvoiceHtml`, `generateTotpSecret`, `totpUri`, `qr`, `isInteger`, `natsPublish`, `natsAdjust`, `put`
|
||||
`now`, `reload`, `customer`, `publicProfile`, `sessionAuth`, `header`, `startsWith`, `slice`, `get`, `list`, `find`, `status`, `json`, `next`, `save`, `portalRouter`, `post`, `async`, `trim`, `test`, `hashPassword`, `findByEmail`, `randomBytes`, `toString`, `bindVeraeUser`, `create`, `verifyPassword`, `verifyTotp`, `use`, `delete`, `setUTCDate`, `setUTCHours`, `summaryFor`, `tiers`, `applyMonthlyCredit`, `rateCard`, `natsStatement`, `composeStatement`, `listFor`, `type`, `send`, `renderInvoiceHtml`, `generateTotpSecret`, `totpUri`, `qr`, `isInteger`, `natsPublish`, `natsAdjust`, `put`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier`
|
||||
**Source:** `packages/zappier/src/statement.ts`
|
||||
**Lines:** 39
|
||||
**Lines:** 41
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -22,6 +22,7 @@ _None extracted._
|
|||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `composeStatement` | `args: {
|
||||
customerId: string;
|
||||
veraeUserId?: string;
|
||||
prepaidCents: number;
|
||||
credits: CreditAdjustment[];
|
||||
usage: UsageEntry[];
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier`
|
||||
**Source:** `packages/zappier/src/upstream.ts`
|
||||
**Lines:** 46
|
||||
**Lines:** 48
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
|
|||
46
docs/modules/zappier/verae-bind.md
Normal file
46
docs/modules/zappier/verae-bind.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# `zappier/verae-bind`
|
||||
|
||||
**Package:** `zappier`
|
||||
**Source:** `packages/zappier/src/verae-bind.ts`
|
||||
**Lines:** 77
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `zappier`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
`normalizeVeraeUsername`, `stableVeraeUserId`, `VeraeBind`, `bindVeraeUser`
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
| Kind | Name |
|
||||
|------|------|
|
||||
| type | `VeraeBind` |
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `normalizeVeraeUsername` | `username: string` | — | `unknown` | see Call graph |
|
||||
| `stableVeraeUserId` | `username: string` | — | `unknown` | see Call graph |
|
||||
| `mockBind` | `email: string` | — | `unknown` | see Call graph |
|
||||
| `bindVeraeUser` | `email: string` | — | `unknown` | see Call graph |
|
||||
| `login` | `username: string, pass: string` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `crypto`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`normalizeVeraeUsername`, `trim`, `toLowerCase`, `stableVeraeUserId`, `createHash`, `update`, `digest`, `slice`, `mockBind`, `bindVeraeUser`, `replace`, `randomBytes`, `toString`, `async`, `fetch`, `stringify`, `json`, `login`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
## Return values (how to read this)
|
||||
|
||||
- HTTP route handlers return Express `res.json(...)` bodies (see route docs).
|
||||
- Zapier `perform` functions return a **single object** (creates) or an **array** (triggers/searches).
|
||||
- Pricing functions return integer **cents** on `Quote.totalCents`.
|
||||
|
||||
|
|
@ -68,6 +68,7 @@ Markdown indexes: https://zapier.georgelambert.org/index-md.html
|
|||
modules/zappier/statement
|
||||
modules/zappier/upstream
|
||||
modules/zappier/usage
|
||||
modules/zappier/verae-bind
|
||||
modules/verae-zapier-middleware/app
|
||||
modules/verae-zapier-middleware/clients/veraeClient
|
||||
modules/verae-zapier-middleware/config
|
||||
|
|
@ -79,6 +80,7 @@ Markdown indexes: https://zapier.georgelambert.org/index-md.html
|
|||
modules/verae-zapier-middleware/debug/trace
|
||||
modules/verae-zapier-middleware/errors
|
||||
modules/verae-zapier-middleware/index
|
||||
modules/verae-zapier-middleware/lib/identity
|
||||
modules/verae-zapier-middleware/lib/receiptPdf
|
||||
modules/verae-zapier-middleware/lib/tokens
|
||||
modules/verae-zapier-middleware/middleware/authenticate
|
||||
|
|
@ -107,6 +109,7 @@ Markdown indexes: https://zapier.georgelambert.org/index-md.html
|
|||
modules/verae-zapier-middleware/store/db
|
||||
modules/verae-zapier-middleware/store/jobWatchers
|
||||
modules/verae-zapier-middleware/store/tenants
|
||||
modules/verae-zapier-middleware/store/tokenRefs
|
||||
modules/verae-zapier-middleware/store/usage
|
||||
modules/verae-zapier-middleware/store/webhooks
|
||||
modules/verae-zapier-middleware/workers/inProcessJobPoller
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-access-authz`
|
||||
**Source:** `packages/verae-access-authz/src/policy.js`
|
||||
**Lines:** 99
|
||||
**Lines:** 101
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/clients/veraeClient.js`
|
||||
**Lines:** 352
|
||||
**Lines:** 386
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -24,6 +24,7 @@ _None extracted._
|
|||
| `delay` | `ms` | ms: `number` | `Promise<void>` | see Call graph |
|
||||
| `mockLogin` | `{ username, password }` | ms: `number` | `Promise<void>` | see Call graph |
|
||||
| `mockValidate` | `token` | — | `unknown` | see Call graph |
|
||||
| `mockCreateUser` | `{ username, password, role = 'user' }` | — | `unknown` | see Call graph |
|
||||
| `mockCreateTimestamp` | `{ data, hashAlg, sha256, publicMetadata, privateMetadata }` | — | `unknown` | see Call graph |
|
||||
| `mockGetStatus` | `jobId` | — | `unknown` | see Call graph |
|
||||
| `mockLookupHash` | `sha256` | — | `unknown` | see Call graph |
|
||||
|
|
@ -38,6 +39,8 @@ _None extracted._
|
|||
| `setTimeout` | `(` |
|
||||
| `login` | `credentials` |
|
||||
| `validate` | `token` |
|
||||
| `createUser` | `adminToken, body` |
|
||||
| `bindUser` | `credentials` |
|
||||
| `createTimestamp` | `token, body` |
|
||||
| `lookupHash` | `token, sha256` |
|
||||
| `createBatchTimestamp` | `token, body` |
|
||||
|
|
@ -54,10 +57,11 @@ _None extracted._
|
|||
- `../config.js`
|
||||
- `../errors.js`
|
||||
- `../debug/logger.js`
|
||||
- `../lib/identity.js`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`net`, `createDebugger`, `sha256Hex`, `createHash`, `update`, `digest`, `delay`, `setTimeout`, `mockLogin`, `now`, `toISOString`, `randomUUID`, `includes`, `mockValidate`, `startsWith`, `replace`, `mockCreateTimestamp`, `toLowerCase`, `get`, `set`, `mockGetStatus`, `mockLookupHash`, `mockVerify`, `request`, `debug`, `fetch`, `stringify`, `text`, `parse`, `error`, `client`, `login`, `validate`, `createTimestamp`, `lookupHash`, `createBatchTimestamp`, `push`, `getStatus`, `encodeURIComponent`, `getBatchStatus`, `verify`, `verifyBatch`, `getJobVerification`, `waitForJob`, `jobs`, `clearMockJobs`, `clear`
|
||||
`net`, `createDebugger`, `sha256Hex`, `createHash`, `update`, `digest`, `delay`, `setTimeout`, `mockLogin`, `now`, `toISOString`, `stableVeraeUserId`, `includes`, `mockValidate`, `startsWith`, `replace`, `mockCreateUser`, `mockCreateTimestamp`, `toLowerCase`, `get`, `randomUUID`, `set`, `mockGetStatus`, `mockLookupHash`, `mockVerify`, `request`, `debug`, `fetch`, `stringify`, `text`, `parse`, `error`, `client`, `login`, `validate`, `createUser`, `bindUser`, `createTimestamp`, `lookupHash`, `createBatchTimestamp`, `push`, `getStatus`, `encodeURIComponent`, `getBatchStatus`, `verify`, `verifyBatch`, `getJobVerification`, `waitForJob`, `jobs`, `clearMockJobs`, `clear`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
41
docs/sphinx/modules/verae-zapier-middleware/lib/identity.md
Normal file
41
docs/sphinx/modules/verae-zapier-middleware/lib/identity.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# `verae-zapier-middleware/lib/identity`
|
||||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/lib/identity.js`
|
||||
**Lines:** 17
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-zapier-middleware`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
`normalizeVeraeUsername`, `stableVeraeUserId`
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `normalizeVeraeUsername` | `username` | — | `unknown` | see Call graph |
|
||||
| `stableVeraeUserId` | `username` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `node:crypto`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`normalizeVeraeUsername`, `trim`, `toLowerCase`, `stableVeraeUserId`, `createHash`, `update`, `digest`, `slice`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
## Return values (how to read this)
|
||||
|
||||
- HTTP route handlers return Express `res.json(...)` bodies (see route docs).
|
||||
- Zapier `perform` functions return a **single object** (creates) or an **array** (triggers/searches).
|
||||
- Pricing functions return integer **cents** on `Quote.totalCents`.
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
verae-zapier-middleware.lib.identity
|
||||
====================================
|
||||
|
||||
Generated API sheet for ``verae-zapier-middleware/lib/identity``.
|
||||
|
||||
.. include:: identity.md
|
||||
:parser: myst_parser.sphinx_
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/middleware/authenticate.js`
|
||||
**Lines:** 28
|
||||
**Lines:** 34
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ _No top-level functions extracted._
|
|||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`createDebugger`, `asyncHandler`, `async`, `extractBearerToken`, `resolveAuthContext`, `debug`, `next`
|
||||
`createDebugger`, `asyncHandler`, `async`, `extractBearerToken`, `resolveAuthContext`, `startsWith`, `debug`, `next`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/nats/publishers.js`
|
||||
**Lines:** 96
|
||||
**Lines:** 98
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/services/authService.js`
|
||||
**Lines:** 142
|
||||
**Lines:** 154
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ _None extracted._
|
|||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`createDebugger`, `loginWithCredentials`, `debug`, `login`, `issueSessionToken`, `loginWithApiKey`, `getTenantByApiKey`, `resolveAuthContext`, `isApiKey`, `parseSessionToken`, `getTenant`, `parse`, `now`, `validateSession`, `validate`
|
||||
`createDebugger`, `loginWithCredentials`, `debug`, `login`, `upsertTenant`, `issueSessionToken`, `loginWithApiKey`, `getTenantByApiKey`, `resolveAuthContext`, `isApiKey`, `parseSessionToken`, `getTenant`, `parse`, `now`, `validateSession`, `validate`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/services/tenantService.js`
|
||||
**Lines:** 138
|
||||
**Lines:** 150
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ _None extracted._
|
|||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`createDebugger`, `slugify`, `toLowerCase`, `replace`, `slice`, `validateVeraeCredentials`, `login`, `selfServeSignup`, `randomUUID`, `createTenant`, `info`, `provision`, `provisionTenant`, `has`, `getTenant`, `listProvisionedTenants`, `listTenants`, `map`
|
||||
`createDebugger`, `slugify`, `toLowerCase`, `replace`, `slice`, `validateVeraeCredentials`, `login`, `selfServeSignup`, `bindUser`, `randomUUID`, `createTenant`, `info`, `provision`, `provisionTenant`, `has`, `getTenant`, `listProvisionedTenants`, `listTenants`, `map`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/services/timestampService.js`
|
||||
**Lines:** 170
|
||||
**Lines:** 171
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -53,13 +53,14 @@ _None extracted._
|
|||
- `../config.js`
|
||||
- `../clients/veraeClient.js`
|
||||
- `../store/jobWatchers.js`
|
||||
- `../store/tokenRefs.js`
|
||||
- `./entitlementService.js`
|
||||
- `../debug/logger.js`
|
||||
- `../debug/trace-context.js`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`createDebugger`, `enqueueWatchForJob`, `getTraceId`, `import`, `enqueueWatch`, `debug`, `enqueueJob`, `createTimestamp`, `checkEntitlement`, `recordUsage`, `createTimestampAndWait`, `waitForJobEvent`, `getStatus`, `waitForJob`, `createBatchTimestamp`, `getJobStatus`, `getBatchJobStatus`, `getBatchStatus`, `getJobVerification`, `lookupHash`
|
||||
`createDebugger`, `enqueueWatchForJob`, `getTraceId`, `import`, `enqueueWatch`, `issueTokenRef`, `debug`, `enqueueJob`, `createTimestamp`, `checkEntitlement`, `recordUsage`, `createTimestampAndWait`, `waitForJobEvent`, `getStatus`, `waitForJob`, `createBatchTimestamp`, `getJobStatus`, `getBatchJobStatus`, `getBatchStatus`, `getJobVerification`, `lookupHash`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/store/db.js`
|
||||
**Lines:** 118
|
||||
**Lines:** 120
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/store/tenants.js`
|
||||
**Lines:** 138
|
||||
**Lines:** 142
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -30,8 +30,9 @@ _None extracted._
|
|||
plan = 'free',
|
||||
veraeUsername,
|
||||
veraePassword,
|
||||
veraeUserId,
|
||||
contract = null,
|
||||
apiKey = generateApiKey(` | params: `object`, params.id: `string`, params.name: `string`, params.plan: `string`, params.veraeUsername: `string`, params.veraePassword: `string`, params.contract: `object|null`, params.apiKey: `string`, params.metadata: `object` | `{ tenant: Tenant, apiKey: string ` — } | see Call graph |
|
||||
apiKey = generateApiKey(` | params: `object`, params.id: `string`, params.name: `string`, params.plan: `string`, params.veraeUsername: `string`, params.veraePassword: `string`, params.veraeUserId: `string`, params.contract: `object|null`, params.apiKey: `string`, params.metadata: `object` | `{ tenant: Tenant, apiKey: string ` — } | see Call graph |
|
||||
| `resolveLimits` | `tenant` | tenant: `Tenant` | `{
|
||||
* timestamps: number|null,
|
||||
* verifications: number|null,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
# `verae-zapier-middleware/store/tokenRefs`
|
||||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/store/tokenRefs.js`
|
||||
**Lines:** 21
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-zapier-middleware`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
`issueTokenRef`, `resolveTokenRef`
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `issueTokenRef` | `tenantId` | — | `unknown` | see Call graph |
|
||||
| `resolveTokenRef` | `ref` | — | `unknown` | see Call graph |
|
||||
|
||||
## Methods (class / object)
|
||||
|
||||
| Name | Parameters |
|
||||
|------|------------|
|
||||
| `persist` | `(none)` |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `node:crypto`
|
||||
- `./db.js`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`issueTokenRef`, `randomBytes`, `toString`, `getStore`, `toISOString`, `persist`, `resolveTokenRef`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
## Return values (how to read this)
|
||||
|
||||
- HTTP route handlers return Express `res.json(...)` bodies (see route docs).
|
||||
- Zapier `perform` functions return a **single object** (creates) or an **array** (triggers/searches).
|
||||
- Pricing functions return integer **cents** on `Quote.totalCents`.
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
verae-zapier-middleware.store.tokenRefs
|
||||
=======================================
|
||||
|
||||
Generated API sheet for ``verae-zapier-middleware/store/tokenRefs``.
|
||||
|
||||
.. include:: tokenRefs.md
|
||||
:parser: myst_parser.sphinx_
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `verae-zapier-middleware`
|
||||
**Source:** `packages/verae-zapier-middleware/src/workers/jobPollerWorker.js`
|
||||
**Lines:** 159
|
||||
**Lines:** 161
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -34,11 +34,12 @@ _None extracted._
|
|||
- `../nats/publishers.js`
|
||||
- `../clients/veraeClient.js`
|
||||
- `../store/tenants.js`
|
||||
- `../store/tokenRefs.js`
|
||||
- `../debug/trace.js`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`createDebugger`, `polling`, `resolveVeraeToken`, `getTenant`, `login`, `handleWatch`, `withTrace`, `async`, `publishJobEvent`, `ack`, `getStatus`, `debug`, `nak`, `startJobPollerWorker`, `stopJobPollerWorker`, `connectNats`, `ensureStreams`, `consumer`, `add`, `get`, `info`, `consume`, `parse`, `string`, `error`, `abort`
|
||||
`createDebugger`, `polling`, `resolveVeraeToken`, `resolveTokenRef`, `getTenant`, `login`, `handleWatch`, `withTrace`, `async`, `publishJobEvent`, `ack`, `getStatus`, `debug`, `nak`, `startJobPollerWorker`, `stopJobPollerWorker`, `connectNats`, `ensureStreams`, `consumer`, `add`, `get`, `info`, `consume`, `parse`, `string`, `error`, `abort`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier-account-balance`
|
||||
**Source:** `packages/zappier-account-balance/src/books.js`
|
||||
**Lines:** 90
|
||||
**Lines:** 100
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -30,8 +30,9 @@ Implementation module in `zappier-account-balance`. The tables below are extract
|
|||
| Name | Parameters |
|
||||
|------|------------|
|
||||
| `constructor` | `(none)` |
|
||||
| `remember` | `customerId, veraeUserId` |
|
||||
| `prepaidCents` | `customerId` |
|
||||
| `adjust` | `{ customerId, cents, reason, agent, kind = 'credit' }` |
|
||||
| `adjust` | `{ customerId, cents, reason, agent, kind = 'credit', veraeUserId }` |
|
||||
| `recordUsage` | `entry` |
|
||||
| `recordPayment` | `entry` |
|
||||
| `statement` | `customerId` |
|
||||
|
|
@ -42,7 +43,7 @@ _No imports detected._
|
|||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`constructor`, `prepaidCents`, `adjust`, `trunc`, `now`, `toString`, `random`, `slice`, `toISOString`, `unshift`, `recordUsage`, `recordPayment`, `statement`, `filter`, `match`, `handle`, `endsWith`
|
||||
`constructor`, `remember`, `prepaidCents`, `adjust`, `trunc`, `now`, `toString`, `random`, `slice`, `toISOString`, `unshift`, `recordUsage`, `recordPayment`, `statement`, `filter`, `match`, `handle`, `endsWith`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier`
|
||||
**Source:** `packages/zappier/src/admin.ts`
|
||||
**Lines:** 591
|
||||
**Lines:** 593
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier`
|
||||
**Source:** `packages/zappier/src/app.ts`
|
||||
**Lines:** 272
|
||||
**Lines:** 278
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ Implementation module in `zappier`. The tables below are extracted from the sour
|
|||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`join`, `next`, `parse`, `status`, `json`, `buildApp`, `seeded`, `seedAdminUsersFromEnv`, `rateCard`, `getRateCard`, `tiers`, `getTiers`, `natsPublish`, `toISOString`, `express`, `use`, `load`, `setup`, `adminLoginRouter`, `adminAuth`, `adminRouter`, `static`, `async`, `import`, `toDataURL`, `portalRouter`, `get`, `apiKeyAuth`, `middleware`, `meter`, `post`, `toUpperCase`, `proxyVerae`, `toLowerCase`, `createHash`, `update`, `digest`, `randomUUID`, `set`, `isFinite`, `map`, `unshift`, `filter`, `setUTCDate`, `setUTCHours`, `summaryFor`, `find`, `applyMonthlyCredit`
|
||||
`join`, `next`, `parse`, `status`, `json`, `buildApp`, `seeded`, `seedAdminUsersFromEnv`, `rateCard`, `getRateCard`, `tiers`, `getTiers`, `list`, `find`, `natsPublish`, `toISOString`, `express`, `use`, `load`, `setup`, `adminLoginRouter`, `adminAuth`, `adminRouter`, `static`, `async`, `import`, `toDataURL`, `portalRouter`, `get`, `apiKeyAuth`, `middleware`, `meter`, `post`, `toUpperCase`, `proxyVerae`, `toLowerCase`, `createHash`, `update`, `digest`, `randomUUID`, `set`, `isFinite`, `map`, `unshift`, `filter`, `setUTCDate`, `setUTCHours`, `summaryFor`, `applyMonthlyCredit`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier`
|
||||
**Source:** `packages/zappier/src/auth.ts`
|
||||
**Lines:** 84
|
||||
**Lines:** 87
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ Implementation module in `zappier`. The tables below are extracted from the sour
|
|||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`findByApiKey`, `findByEmail`, `list`, `save`, `constructor`, `arrays`, `map`, `find`, `toLowerCase`, `findIndex`, `push`, `apiKeyAuth`, `header`, `status`, `json`, `next`
|
||||
`id`, `findByApiKey`, `findByEmail`, `list`, `save`, `constructor`, `arrays`, `map`, `find`, `toLowerCase`, `findIndex`, `push`, `apiKeyAuth`, `header`, `status`, `json`, `next`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier`
|
||||
**Source:** `packages/zappier/src/billing-nats.ts`
|
||||
**Lines:** 130
|
||||
**Lines:** 138
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -29,11 +29,13 @@ Implementation module in `zappier`. The tables below are extracted from the sour
|
|||
| `decode` | `buf: Uint8Array` | — | `unknown` | see Call graph |
|
||||
| `authzAllow` | `plane: AccessPlane,
|
||||
subject: string,
|
||||
extra: { principal?: string; kind?: string } = {},` | — | `unknown` | see Call graph |
|
||||
extra: { principal?: string; kind?: string; veraeUserId?: string } = {},` | — | `unknown` | see Call graph |
|
||||
| `natsStatement` | `customerId: string,
|
||||
plane: AccessPlane = 'web',` | — | `unknown` | see Call graph |
|
||||
plane: AccessPlane = 'web',
|
||||
veraeUserId?: string,` | — | `unknown` | see Call graph |
|
||||
| `natsAdjust` | `payload: {
|
||||
customerId: string;
|
||||
veraeUserId?: string;
|
||||
cents: number;
|
||||
reason: string;
|
||||
agent: string;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier`
|
||||
**Source:** `packages/zappier/src/portal.ts`
|
||||
**Lines:** 328
|
||||
**Lines:** 337
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -51,10 +51,11 @@ Implementation module in `zappier`. The tables below are extracted from the sour
|
|||
- `./credits`
|
||||
- `./statement`
|
||||
- `./billing-nats`
|
||||
- `./verae-bind`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`now`, `reload`, `customer`, `publicProfile`, `sessionAuth`, `header`, `startsWith`, `slice`, `get`, `list`, `find`, `status`, `json`, `next`, `save`, `portalRouter`, `post`, `trim`, `test`, `hashPassword`, `findByEmail`, `randomBytes`, `toString`, `create`, `verifyPassword`, `verifyTotp`, `use`, `delete`, `setUTCDate`, `setUTCHours`, `summaryFor`, `tiers`, `applyMonthlyCredit`, `rateCard`, `async`, `natsStatement`, `composeStatement`, `listFor`, `type`, `send`, `renderInvoiceHtml`, `generateTotpSecret`, `totpUri`, `qr`, `isInteger`, `natsPublish`, `natsAdjust`, `put`
|
||||
`now`, `reload`, `customer`, `publicProfile`, `sessionAuth`, `header`, `startsWith`, `slice`, `get`, `list`, `find`, `status`, `json`, `next`, `save`, `portalRouter`, `post`, `async`, `trim`, `test`, `hashPassword`, `findByEmail`, `randomBytes`, `toString`, `bindVeraeUser`, `create`, `verifyPassword`, `verifyTotp`, `use`, `delete`, `setUTCDate`, `setUTCHours`, `summaryFor`, `tiers`, `applyMonthlyCredit`, `rateCard`, `natsStatement`, `composeStatement`, `listFor`, `type`, `send`, `renderInvoiceHtml`, `generateTotpSecret`, `totpUri`, `qr`, `isInteger`, `natsPublish`, `natsAdjust`, `put`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier`
|
||||
**Source:** `packages/zappier/src/statement.ts`
|
||||
**Lines:** 39
|
||||
**Lines:** 41
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -22,6 +22,7 @@ _None extracted._
|
|||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `composeStatement` | `args: {
|
||||
customerId: string;
|
||||
veraeUserId?: string;
|
||||
prepaidCents: number;
|
||||
credits: CreditAdjustment[];
|
||||
usage: UsageEntry[];
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier`
|
||||
**Source:** `packages/zappier/src/upstream.ts`
|
||||
**Lines:** 46
|
||||
**Lines:** 48
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
|
|||
46
docs/sphinx/modules/zappier/verae-bind.md
Normal file
46
docs/sphinx/modules/zappier/verae-bind.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# `zappier/verae-bind`
|
||||
|
||||
**Package:** `zappier`
|
||||
**Source:** `packages/zappier/src/verae-bind.ts`
|
||||
**Lines:** 77
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `zappier`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
`normalizeVeraeUsername`, `stableVeraeUserId`, `VeraeBind`, `bindVeraeUser`
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
| Kind | Name |
|
||||
|------|------|
|
||||
| type | `VeraeBind` |
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `normalizeVeraeUsername` | `username: string` | — | `unknown` | see Call graph |
|
||||
| `stableVeraeUserId` | `username: string` | — | `unknown` | see Call graph |
|
||||
| `mockBind` | `email: string` | — | `unknown` | see Call graph |
|
||||
| `bindVeraeUser` | `email: string` | — | `unknown` | see Call graph |
|
||||
| `login` | `username: string, pass: string` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `crypto`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`normalizeVeraeUsername`, `trim`, `toLowerCase`, `stableVeraeUserId`, `createHash`, `update`, `digest`, `slice`, `mockBind`, `bindVeraeUser`, `replace`, `randomBytes`, `toString`, `async`, `fetch`, `stringify`, `json`, `login`
|
||||
|
||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||
|
||||
## Return values (how to read this)
|
||||
|
||||
- HTTP route handlers return Express `res.json(...)` bodies (see route docs).
|
||||
- Zapier `perform` functions return a **single object** (creates) or an **array** (triggers/searches).
|
||||
- Pricing functions return integer **cents** on `Quote.totalCents`.
|
||||
|
||||
7
docs/sphinx/modules/zappier/verae-bind.rst
Normal file
7
docs/sphinx/modules/zappier/verae-bind.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
zappier.verae-bind
|
||||
==================
|
||||
|
||||
Generated API sheet for ``zappier/verae-bind``.
|
||||
|
||||
.. include:: verae-bind.md
|
||||
:parser: myst_parser.sphinx_
|
||||
|
|
@ -59,7 +59,7 @@ Zapier cloud **never** connects to NATS. Internal services do: middleware worker
|
|||
|
||||
| Address | Kind | Payload (required fields) |
|
||||
|---------|------|---------------------------|
|
||||
| `verae.zapier.jobs.watch` | JetStream work queue | `tenantId`, `jobId`, `tokenRef`, `enqueuedAt`, `attempt`, `maxAttempts`, `intervalMs`, `traceId` |
|
||||
| `verae.zapier.jobs.watch` | JetStream work queue | `tenantId`, `veraeUserId`, `jobId`, `tokenRef`, `enqueuedAt`, `attempt`, `maxAttempts`, `intervalMs`, `traceId` |
|
||||
| `verae.zapier.jobs.events` | JetStream events | `event` (`timestamp.completed\|failed\|timeout`), `tenantId`, `jobId`, `status`, `traceId`, `emittedAt` |
|
||||
| `verae.zapier.webhooks.deliver` | JetStream work queue | `hookId`, `tenantId`, `targetUrl`, `event`, `payload`, `attempt`, `traceId` |
|
||||
| `verae.zapier.usage` | optional | `tenantId`, `action`, `amount`, `at` |
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Zapier cloud still never connects to NATS. The edge process **does** publish and
|
|||
|-----------|---------|------|------|
|
||||
| IN HTTPS | `/v1/*` | Zapier Platform app | JSON + `x-api-key` |
|
||||
| OUT HTTPS | middleware `/zapier/v1/*` | verae-middleware | same tenant request |
|
||||
| OUT NATS | `verae.billing.usage.recorded` | account-balance | meter event |
|
||||
| OUT NATS | `verae.billing.usage.recorded` | account-balance | meter event (`customerId`, `veraeUserId`) |
|
||||
| OUT NATS | `verae.billing.payment.recorded` | account-balance / others | portal reload |
|
||||
| OUT NATS | `verae.billing.credit.applied` | account-balance / others | admin/CS credit |
|
||||
| OUT NATS | `verae.billing.statement.get` | account-balance | portal/admin review |
|
||||
|
|
|
|||
|
|
@ -89,6 +89,8 @@ export function authorize(req) {
|
|||
plane,
|
||||
subject: internal,
|
||||
principal: req?.principal || null,
|
||||
veraeUserId: req?.veraeUserId || req?.payload?.veraeUserId || null,
|
||||
traceId: req?.traceId || req?.payload?.traceId || null,
|
||||
reason: 'ok',
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,19 @@ test('access-prefixed address is mapped to internal', () => {
|
|||
assert.equal(authorize({ plane: 'web', subject: addr }).allow, false);
|
||||
});
|
||||
|
||||
test('allow echoes veraeUserId for hop tracing', () => {
|
||||
const out = authorize({
|
||||
plane: 'web',
|
||||
subject: 'verae.billing.statement.get',
|
||||
principal: 'cust_1',
|
||||
veraeUserId: 'vu_deadbeefdeadbeef',
|
||||
traceId: 'tr-1',
|
||||
});
|
||||
assert.equal(out.allow, true);
|
||||
assert.equal(out.veraeUserId, 'vu_deadbeefdeadbeef');
|
||||
assert.equal(out.traceId, 'tr-1');
|
||||
});
|
||||
|
||||
test('unknown plane and authz subjects denied', () => {
|
||||
assert.equal(authorize({ plane: 'partner', subject: 'verae.archive.put' }).allow, false);
|
||||
assert.equal(authorize({ plane: 'web', subject: 'verae.access.authz.check' }).allow, false);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { createHash, randomUUID } from 'node:crypto';
|
|||
import { config } from '../config.js';
|
||||
import { AppError } from '../errors.js';
|
||||
import { createDebugger } from '../debug/logger.js';
|
||||
import { stableVeraeUserId } from '../lib/identity.js';
|
||||
|
||||
const log = createDebugger('http');
|
||||
|
||||
|
|
@ -39,7 +40,7 @@ async function mockLogin({ username, password }) {
|
|||
token: `mock-jwt-${username}`,
|
||||
expiresAt,
|
||||
user: {
|
||||
id: randomUUID(),
|
||||
id: stableVeraeUserId(username),
|
||||
username,
|
||||
role: username.includes('admin') ? 'admin' : 'user',
|
||||
},
|
||||
|
|
@ -53,13 +54,24 @@ async function mockValidate(token) {
|
|||
const username = token.replace('mock-jwt-', '');
|
||||
return {
|
||||
valid: true,
|
||||
userId: randomUUID(),
|
||||
userId: stableVeraeUserId(username),
|
||||
username,
|
||||
role: 'user',
|
||||
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
async function mockCreateUser({ username, password, role = 'user' }) {
|
||||
if (!username || !password) {
|
||||
throw new AppError('Invalid input', { status: 400, code: 'VALIDATION_ERROR' });
|
||||
}
|
||||
return {
|
||||
id: stableVeraeUserId(username),
|
||||
username,
|
||||
role,
|
||||
};
|
||||
}
|
||||
|
||||
async function mockCreateTimestamp({ data, hashAlg, sha256, publicMetadata, privateMetadata }) {
|
||||
if (!data && !sha256) {
|
||||
throw new AppError('Invalid input data', { status: 400, code: 'VALIDATION_ERROR' });
|
||||
|
|
@ -228,6 +240,28 @@ export const veraeClient = {
|
|||
return request('/auth/validate', { token });
|
||||
},
|
||||
|
||||
/**
|
||||
* Admin-only Verae user register. JWT is not returned to callers of bind.
|
||||
* @param {string} adminToken
|
||||
* @param {{ username: string, password: string, role?: string }} body
|
||||
*/
|
||||
async createUser(adminToken, body) {
|
||||
if (config.mockVerae) return mockCreateUser(body);
|
||||
return request('/auth/users', { method: 'POST', token: adminToken, body });
|
||||
},
|
||||
|
||||
/**
|
||||
* Login (or create via admin) and return the stable user id — never the JWT.
|
||||
* @param {{ username: string, password: string }} credentials
|
||||
*/
|
||||
async bindUser(credentials) {
|
||||
const login = await this.login(credentials);
|
||||
return {
|
||||
veraeUserId: login.user?.id || stableVeraeUserId(credentials.username),
|
||||
veraeUsername: login.user?.username || credentials.username,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} token
|
||||
* @param {{ data: string, hashAlg?: string }} body
|
||||
|
|
|
|||
16
packages/verae-zapier-middleware/src/lib/identity.js
Normal file
16
packages/verae-zapier-middleware/src/lib/identity.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Stable Verae user id derived from username/email.
|
||||
* Live /auth/login.user.id wins when present; this is the mock/offline id.
|
||||
*/
|
||||
import { createHash } from 'node:crypto';
|
||||
|
||||
export function normalizeVeraeUsername(username) {
|
||||
return String(username || '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
export function stableVeraeUserId(username) {
|
||||
const n = normalizeVeraeUsername(username);
|
||||
return `vu_${createHash('sha256').update(n).digest('hex').slice(0, 16)}`;
|
||||
}
|
||||
|
|
@ -18,8 +18,14 @@ export const authenticate = asyncHandler(async (req, res, next) => {
|
|||
extractBearerToken(req.headers.authorization) ?? req.headers['x-api-key'] ?? null;
|
||||
|
||||
req.auth = await resolveAuthContext(rawToken);
|
||||
const headerId = req.headers['x-verae-user-id'];
|
||||
if (typeof headerId === 'string' && headerId.startsWith('vu_') && !req.auth.veraeUserId) {
|
||||
req.auth.veraeUserId = headerId;
|
||||
if (req.auth.tenant) req.auth.tenant.veraeUserId = headerId;
|
||||
}
|
||||
log.debug('authenticated', {
|
||||
tenantId: req.auth.tenantId,
|
||||
veraeUserId: req.auth.veraeUserId,
|
||||
method: req.auth.authMethod,
|
||||
plan: req.auth.tenant?.plan,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -37,9 +37,11 @@ export async function enqueueWatch(partial) {
|
|||
log.debug('enqueueWatch', {
|
||||
subject: SUBJECTS.JOBS_WATCH,
|
||||
tenantId: msg.tenantId,
|
||||
veraeUserId: msg.veraeUserId,
|
||||
jobId: msg.jobId,
|
||||
attempt: msg.attempt,
|
||||
traceId: msg.traceId,
|
||||
hasTokenRef: Boolean(msg.tokenRef),
|
||||
});
|
||||
|
||||
const js = await requireJs();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
import { veraeClient } from '../clients/veraeClient.js';
|
||||
import { getTenantByApiKey, getTenant } from '../store/tenants.js';
|
||||
import { getTenantByApiKey, getTenant, upsertTenant } from '../store/tenants.js';
|
||||
import { issueSessionToken, parseSessionToken, isApiKey } from '../lib/tokens.js';
|
||||
import { AppError } from '../errors.js';
|
||||
import { createDebugger } from '../debug/logger.js';
|
||||
|
|
@ -33,6 +33,10 @@ export async function loginWithCredentials({ username, password, tenant }) {
|
|||
}
|
||||
|
||||
const tenantId = tenant?.id ?? `user:${verae.user.username}`;
|
||||
const veraeUserId = verae.user?.id;
|
||||
if (tenant && veraeUserId && tenant.veraeUserId !== veraeUserId) {
|
||||
upsertTenant({ ...tenant, veraeUserId });
|
||||
}
|
||||
const accessToken = issueSessionToken({
|
||||
tenantId,
|
||||
veraeToken: verae.token,
|
||||
|
|
@ -43,9 +47,10 @@ export async function loginWithCredentials({ username, password, tenant }) {
|
|||
accessToken,
|
||||
expiresAt: verae.expiresAt,
|
||||
tenant: tenant
|
||||
? { id: tenant.id, name: tenant.name, plan: tenant.plan }
|
||||
: { id: tenantId, name: verae.user.username, plan: 'free' },
|
||||
? { id: tenant.id, name: tenant.name, plan: tenant.plan, veraeUserId: veraeUserId || tenant.veraeUserId }
|
||||
: { id: tenantId, name: verae.user.username, plan: 'free', veraeUserId },
|
||||
user: verae.user,
|
||||
veraeUserId,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -93,8 +98,14 @@ export async function resolveAuthContext(rawToken) {
|
|||
const tenant = getTenant(session.tenant.id) ?? session.tenant;
|
||||
return {
|
||||
tenantId: session.tenant.id,
|
||||
tenant: { id: session.tenant.id, name: session.tenant.name, plan: session.tenant.plan },
|
||||
tenant: {
|
||||
id: session.tenant.id,
|
||||
name: session.tenant.name,
|
||||
plan: session.tenant.plan,
|
||||
veraeUserId: session.veraeUserId || tenant?.veraeUserId,
|
||||
},
|
||||
veraeToken: parsed.veraeToken,
|
||||
veraeUserId: session.veraeUserId || tenant?.veraeUserId,
|
||||
authMethod: 'api_key',
|
||||
fullTenant: tenant,
|
||||
};
|
||||
|
|
@ -114,9 +125,10 @@ export async function resolveAuthContext(rawToken) {
|
|||
return {
|
||||
tenantId: parsed.tenantId,
|
||||
tenant: tenant
|
||||
? { id: tenant.id, name: tenant.name, plan: tenant.plan }
|
||||
? { id: tenant.id, name: tenant.name, plan: tenant.plan, veraeUserId: tenant.veraeUserId }
|
||||
: { id: parsed.tenantId, plan: 'free' },
|
||||
veraeToken: parsed.veraeToken,
|
||||
veraeUserId: tenant?.veraeUserId,
|
||||
authMethod: 'session',
|
||||
fullTenant: tenant,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export async function selfServeSignup({ email, name, veraeUsername, veraePasswor
|
|||
});
|
||||
}
|
||||
|
||||
await validateVeraeCredentials(veraeUsername, veraePassword);
|
||||
const bound = await veraeClient.bindUser({ username: veraeUsername, password: veraePassword });
|
||||
|
||||
const id = `tenant-${slugify(email)}-${randomUUID().slice(0, 8)}`;
|
||||
const { tenant, apiKey } = createTenant({
|
||||
|
|
@ -54,14 +54,21 @@ export async function selfServeSignup({ email, name, veraeUsername, veraePasswor
|
|||
plan: 'free',
|
||||
veraeUsername,
|
||||
veraePassword,
|
||||
veraeUserId: bound.veraeUserId,
|
||||
contract: null,
|
||||
metadata: { email, audience: 'self-serve', createdVia: 'signup' },
|
||||
});
|
||||
|
||||
log.info('self-serve signup', { tenantId: tenant.id });
|
||||
log.info('self-serve signup', { tenantId: tenant.id, veraeUserId: bound.veraeUserId });
|
||||
|
||||
return {
|
||||
tenant: { id: tenant.id, name: tenant.name, plan: tenant.plan, email },
|
||||
tenant: {
|
||||
id: tenant.id,
|
||||
name: tenant.name,
|
||||
plan: tenant.plan,
|
||||
email,
|
||||
veraeUserId: bound.veraeUserId,
|
||||
},
|
||||
apiKey,
|
||||
zapierSetup: {
|
||||
authType: 'custom',
|
||||
|
|
@ -102,7 +109,7 @@ export async function provisionTenant({
|
|||
});
|
||||
}
|
||||
|
||||
await validateVeraeCredentials(veraeUsername, veraePassword);
|
||||
const bound = await veraeClient.bindUser({ username: veraeUsername, password: veraePassword });
|
||||
|
||||
const tenantId = id ?? `tenant-${slugify(name)}-${randomUUID().slice(0, 8)}`;
|
||||
if (getTenant(tenantId)) {
|
||||
|
|
@ -115,12 +122,16 @@ export async function provisionTenant({
|
|||
plan,
|
||||
veraeUsername,
|
||||
veraePassword,
|
||||
veraeUserId: bound.veraeUserId,
|
||||
contract,
|
||||
metadata: { ...metadata, audience, createdVia: 'provision' },
|
||||
});
|
||||
|
||||
log.info('tenant provisioned', { tenantId: tenant.id, plan, audience });
|
||||
return { tenant: { id: tenant.id, name: tenant.name, plan: tenant.plan }, apiKey };
|
||||
log.info('tenant provisioned', { tenantId: tenant.id, plan, audience, veraeUserId: bound.veraeUserId });
|
||||
return {
|
||||
tenant: { id: tenant.id, name: tenant.name, plan: tenant.plan, veraeUserId: bound.veraeUserId },
|
||||
apiKey,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -132,6 +143,7 @@ export function listProvisionedTenants() {
|
|||
name: tenant.name,
|
||||
plan: tenant.plan,
|
||||
audience: tenant.metadata?.audience ?? 'unknown',
|
||||
veraeUserId: tenant.veraeUserId,
|
||||
createdAt: tenant.createdAt,
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import { config } from '../config.js';
|
||||
import { veraeClient } from '../clients/veraeClient.js';
|
||||
import { enqueueJob } from '../store/jobWatchers.js';
|
||||
import { issueTokenRef } from '../store/tokenRefs.js';
|
||||
import { checkEntitlement, recordUsage } from './entitlementService.js';
|
||||
import { createDebugger } from '../debug/logger.js';
|
||||
import { getTraceId } from '../debug/trace-context.js';
|
||||
|
|
@ -25,8 +26,8 @@ async function enqueueWatchForJob(ctx, jobId) {
|
|||
await enqueueWatch({
|
||||
tenantId: ctx.tenantId,
|
||||
jobId,
|
||||
// Prefer re-login in worker; include token for MVP simplicity when mock
|
||||
veraeToken: ctx.veraeToken,
|
||||
tokenRef: issueTokenRef(ctx.tenantId),
|
||||
veraeUserId: ctx.veraeUserId || ctx.tenant?.veraeUserId,
|
||||
maxAttempts: config.jobPollMaxAttempts,
|
||||
intervalMs: config.jobPollIntervalMs,
|
||||
traceId,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export function emptyStore() {
|
|||
usage: {},
|
||||
webhooks: [],
|
||||
jobWatchers: [],
|
||||
tokenRefs: {},
|
||||
blobs: {},
|
||||
shares: {},
|
||||
trees: {},
|
||||
|
|
@ -59,6 +60,7 @@ export function loadStore(path = config.storePath) {
|
|||
usage: parsed.usage ?? {},
|
||||
webhooks: Array.isArray(parsed.webhooks) ? parsed.webhooks : [],
|
||||
jobWatchers: Array.isArray(parsed.jobWatchers) ? parsed.jobWatchers : [],
|
||||
tokenRefs: parsed.tokenRefs ?? {},
|
||||
};
|
||||
log.debug('store loaded', { path, tenants: Object.keys(store.tenants).length });
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const log = createDebugger('billing');
|
|||
* @property {string} plan
|
||||
* @property {string} veraeUsername
|
||||
* @property {string} veraePassword
|
||||
* @property {string} [veraeUserId]
|
||||
* @property {object|null} contract
|
||||
* @property {object} [metadata]
|
||||
* @property {string} createdAt
|
||||
|
|
@ -68,6 +69,7 @@ export function upsertTenant(tenant) {
|
|||
* @param {string} [params.plan='free']
|
||||
* @param {string} params.veraeUsername
|
||||
* @param {string} params.veraePassword
|
||||
* @param {string} [params.veraeUserId]
|
||||
* @param {object|null} [params.contract=null]
|
||||
* @param {string} [params.apiKey]
|
||||
* @param {object} [params.metadata]
|
||||
|
|
@ -79,6 +81,7 @@ export function createTenant({
|
|||
plan = 'free',
|
||||
veraeUsername,
|
||||
veraePassword,
|
||||
veraeUserId,
|
||||
contract = null,
|
||||
apiKey = generateApiKey(),
|
||||
metadata = {},
|
||||
|
|
@ -91,6 +94,7 @@ export function createTenant({
|
|||
plan,
|
||||
veraeUsername,
|
||||
veraePassword,
|
||||
veraeUserId,
|
||||
contract,
|
||||
metadata,
|
||||
createdAt: new Date().toISOString(),
|
||||
|
|
|
|||
20
packages/verae-zapier-middleware/src/store/tokenRefs.js
Normal file
20
packages/verae-zapier-middleware/src/store/tokenRefs.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Opaque tokenRef → tenantId. Workers resolve a fresh Verae JWT; NATS never carries it.
|
||||
*/
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { getStore, persist } from './db.js';
|
||||
|
||||
export function issueTokenRef(tenantId) {
|
||||
const ref = `tref_${randomBytes(12).toString('hex')}`;
|
||||
const store = getStore();
|
||||
if (!store.tokenRefs) store.tokenRefs = {};
|
||||
store.tokenRefs[ref] = { tenantId, createdAt: new Date().toISOString() };
|
||||
persist();
|
||||
return ref;
|
||||
}
|
||||
|
||||
export function resolveTokenRef(ref) {
|
||||
if (!ref) return null;
|
||||
const row = getStore().tokenRefs?.[ref];
|
||||
return row?.tenantId ?? null;
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import { connectNats, ensureStreams } from '../nats/connection.js';
|
|||
import { publishJobEvent } from '../nats/publishers.js';
|
||||
import { veraeClient } from '../clients/veraeClient.js';
|
||||
import { getTenant } from '../store/tenants.js';
|
||||
import { resolveTokenRef } from '../store/tokenRefs.js';
|
||||
import { withTrace } from '../debug/trace.js';
|
||||
|
||||
const log = createDebugger('jobs');
|
||||
|
|
@ -26,9 +27,10 @@ let abort = null;
|
|||
async function resolveVeraeToken(msg) {
|
||||
if (msg.veraeToken) return msg.veraeToken;
|
||||
|
||||
const tenant = getTenant(msg.tenantId);
|
||||
const tenantId = msg.tokenRef ? resolveTokenRef(msg.tokenRef) || msg.tenantId : msg.tenantId;
|
||||
const tenant = getTenant(tenantId);
|
||||
if (!tenant?.veraeUsername) {
|
||||
throw new Error(`Cannot resolve token for tenant ${msg.tenantId}`);
|
||||
throw new Error(`Cannot resolve token for tenant ${tenantId}`);
|
||||
}
|
||||
const login = await veraeClient.login({
|
||||
username: tenant.veraeUsername,
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ describe('tenancy', () => {
|
|||
const body = await res.json();
|
||||
assert.equal(body.tenant.plan, 'free');
|
||||
assert.ok(body.apiKey.startsWith('zmw_'));
|
||||
assert.match(body.tenant.veraeUserId, /^vu_[0-9a-f]{16}$/);
|
||||
assert.doesNotMatch(JSON.stringify(body), /mock-jwt|eyJhbGci/);
|
||||
});
|
||||
|
||||
it('enterprise without contract rejected', async () => {
|
||||
|
|
|
|||
27
packages/verae-zapier-middleware/test/unit/identity.test.js
Normal file
27
packages/verae-zapier-middleware/test/unit/identity.test.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { stableVeraeUserId } from '../../src/lib/identity.js';
|
||||
import { issueTokenRef, resolveTokenRef } from '../../src/store/tokenRefs.js';
|
||||
import { useTempStore } from '../helpers.js';
|
||||
|
||||
describe('verae identity', () => {
|
||||
it('stableVeraeUserId is deterministic and not a JWT', () => {
|
||||
const a = stableVeraeUserId('Ada@Example.com');
|
||||
const b = stableVeraeUserId('ada@example.com');
|
||||
assert.equal(a, b);
|
||||
assert.match(a, /^vu_[0-9a-f]{16}$/);
|
||||
assert.doesNotMatch(a, /eyJ/);
|
||||
});
|
||||
|
||||
it('tokenRef resolves tenant and is not a Verae JWT', () => {
|
||||
const ctx = useTempStore();
|
||||
try {
|
||||
const ref = issueTokenRef('tenant-1');
|
||||
assert.match(ref, /^tref_/);
|
||||
assert.equal(resolveTokenRef(ref), 'tenant-1');
|
||||
assert.doesNotMatch(ref, /mock-jwt|eyJ/);
|
||||
} finally {
|
||||
ctx.cleanup();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -18,6 +18,9 @@ describe('veraeClient mock', () => {
|
|||
it('create → wait → completed', async () => {
|
||||
const login = await veraeClient.login({ username: 'u', password: 'p' });
|
||||
assert.ok(login.token.startsWith('mock-jwt-'));
|
||||
const again = await veraeClient.login({ username: 'u', password: 'p' });
|
||||
assert.equal(login.user.id, again.user.id);
|
||||
assert.match(login.user.id, /^vu_[0-9a-f]{16}$/);
|
||||
|
||||
const { jobId } = await veraeClient.createTimestamp(login.token, {
|
||||
data: 'hello',
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ You need an API key **before** you can connect Zapier.
|
|||
3. Enter name, email, and a password of at least 8 characters.
|
||||
4. You start on the **Free** plan and receive an **API key immediately**.
|
||||
5. Copy the key. It is sent as `x-api-key` on every Zapier request.
|
||||
6. Signup also **registers you on Verae central** and stores a stable `veraeUserId` on your account. That id rides every internal hop (billing, authz, job watch) so CS/sales and the chain can see the same person. You never paste the Verae JWT into Zapier — that token stays on the server (`tokenRef` on NATS).
|
||||
|
||||
If your company already created an account (you received a key by email), sign up with the **same email**. The existing plan and key are kept; your password is attached to that account.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
- NS1 `nats-server` stays on **127.0.0.1:4222**. Operators use `scripts/nats-tunnel.sh`; it is not a public bind.
|
||||
- File bytes and private metadata never go on chain. Private metadata is only on authenticated archive replies.
|
||||
- API keys are `x-api-key` / Bearer tokens on HTTPS. Treat them like passwords; regenerating kills old Zaps.
|
||||
- The Verae central JWT from `/auth/login` is **not** your Zapier/portal token. Middleware holds it (or re-logins via `tokenRef`). `veraeUserId` is the public correlation id (`vu_…`).
|
||||
- Bloom filters are **not** an access-control list. On a hit, middleware still checks tenant/share before returning private records.
|
||||
|
||||
If a trace (simulator or `DEBUG_VERAE`) ever shows a `zapier-platform-app` hop with a `verae.*` subject, that is a bug — do not push the app.
|
||||
|
|
|
|||
|
|
@ -5,16 +5,23 @@ export class AccountBooks {
|
|||
constructor() {
|
||||
/** @type {Record<string, number>} */
|
||||
this.prepaid = {};
|
||||
/** @type {Record<string, string>} */
|
||||
this.veraeUserIds = {};
|
||||
this.credits = [];
|
||||
this.usage = [];
|
||||
this.payments = [];
|
||||
}
|
||||
|
||||
remember(customerId, veraeUserId) {
|
||||
if (customerId && veraeUserId) this.veraeUserIds[customerId] = veraeUserId;
|
||||
}
|
||||
|
||||
prepaidCents(customerId) {
|
||||
return this.prepaid[customerId] ?? 0;
|
||||
}
|
||||
|
||||
adjust({ customerId, cents, reason, agent, kind = 'credit' }) {
|
||||
adjust({ customerId, cents, reason, agent, kind = 'credit', veraeUserId }) {
|
||||
this.remember(customerId, veraeUserId);
|
||||
const delta = Math.trunc(Number(cents) || 0);
|
||||
const next = this.prepaidCents(customerId) + delta;
|
||||
this.prepaid[customerId] = next;
|
||||
|
|
@ -34,6 +41,7 @@ export class AccountBooks {
|
|||
}
|
||||
|
||||
recordUsage(entry) {
|
||||
this.remember(entry.customerId, entry.veraeUserId);
|
||||
const cents = Number(entry.cents) || 0;
|
||||
const customerId = entry.customerId;
|
||||
const next = this.prepaidCents(customerId) - cents;
|
||||
|
|
@ -63,6 +71,7 @@ export class AccountBooks {
|
|||
const match = (rows) => rows.filter((r) => r.customerId === customerId).slice(0, 100);
|
||||
return {
|
||||
customerId,
|
||||
veraeUserId: this.veraeUserIds[customerId],
|
||||
prepaidCents: this.prepaidCents(customerId),
|
||||
credits: match(this.credits),
|
||||
usage: match(this.usage),
|
||||
|
|
@ -74,6 +83,7 @@ export class AccountBooks {
|
|||
export function handle(subject, payload, books) {
|
||||
const p = payload || {};
|
||||
if (subject.endsWith('balance.get') || subject.endsWith('statement.get')) {
|
||||
books.remember(p.customerId, p.veraeUserId);
|
||||
return books.statement(p.customerId);
|
||||
}
|
||||
if (subject.endsWith('balance.adjust') || subject.endsWith('credit.applied')) {
|
||||
|
|
|
|||
|
|
@ -5,11 +5,16 @@ import { SUBJECTS } from '../src/subjects.js';
|
|||
|
||||
test('adjust credits prepaid and statement lists credits usage payments', () => {
|
||||
const books = new AccountBooks();
|
||||
handle(SUBJECTS.BALANCE_ADJUST, { customerId: 'c1', cents: 500, reason: 'goodwill', agent: 'cs' }, books);
|
||||
handle(
|
||||
SUBJECTS.BALANCE_ADJUST,
|
||||
{ customerId: 'c1', veraeUserId: 'vu_abc', cents: 500, reason: 'goodwill', agent: 'cs' },
|
||||
books,
|
||||
);
|
||||
handle(SUBJECTS.USAGE_RECORDED, { customerId: 'c1', endpointId: 'timestamp', cents: 4 }, books);
|
||||
handle(SUBJECTS.PAYMENT_RECORDED, { customerId: 'c1', cents: 1000, reason: 'reload' }, books);
|
||||
const st = handle(SUBJECTS.STATEMENT_GET, { customerId: 'c1' }, books);
|
||||
assert.equal(st.prepaidCents, 1496);
|
||||
assert.equal(st.veraeUserId, 'vu_abc');
|
||||
assert.equal(st.credits[0].cents, 500);
|
||||
assert.equal(st.usage[0].endpointId, 'timestamp');
|
||||
assert.equal(st.payments[0].kind, 'payment');
|
||||
|
|
|
|||
|
|
@ -248,10 +248,11 @@ export function adminRouter(
|
|||
agent: typeof agent === 'string' ? agent : 'admin',
|
||||
});
|
||||
customers.save({ ...customer, balanceCents: (customer.balanceCents ?? 0) + delta });
|
||||
natsPublish(BILLING_SUBJECTS.CREDIT_APPLIED, rec, 'staff');
|
||||
natsPublish(BILLING_SUBJECTS.CREDIT_APPLIED, { ...rec, veraeUserId: customer.veraeUserId }, 'staff');
|
||||
void natsAdjust(
|
||||
{
|
||||
customerId,
|
||||
veraeUserId: customer.veraeUserId,
|
||||
cents: delta,
|
||||
reason: rec.reason,
|
||||
agent: rec.agent,
|
||||
|
|
@ -273,7 +274,7 @@ export function adminRouter(
|
|||
res.status(404).json({ error: 'customer not found' });
|
||||
return;
|
||||
}
|
||||
const fromNats = await natsStatement(customer.id, 'staff');
|
||||
const fromNats = await natsStatement(customer.id, 'staff', customer.veraeUserId);
|
||||
if (fromNats) {
|
||||
res.json({ ...fromNats, name: customer.name, tierId: customer.tierId, source: 'nats' });
|
||||
return;
|
||||
|
|
@ -281,6 +282,7 @@ export function adminRouter(
|
|||
res.json({
|
||||
...composeStatement({
|
||||
customerId: customer.id,
|
||||
veraeUserId: customer.veraeUserId,
|
||||
prepaidCents: customer.balanceCents ?? 0,
|
||||
credits: credits.list(customer.id),
|
||||
usage: accounting.usage.listFor(customer.id),
|
||||
|
|
|
|||
|
|
@ -102,8 +102,14 @@ export function buildApp(deps: AppDeps = {}): {
|
|||
};
|
||||
const items: StoredItem[] = [];
|
||||
const credits = new CreditLedger();
|
||||
const onUsage = (e: { customerId: string; endpointId: string; cents: number }) =>
|
||||
natsPublish(BILLING_SUBJECTS.USAGE_RECORDED, { ...e, at: new Date().toISOString() }, 'api');
|
||||
const onUsage = (e: { customerId: string; endpointId: string; cents: number }) => {
|
||||
const c = customers.list().find((row) => row.id === e.customerId);
|
||||
natsPublish(
|
||||
BILLING_SUBJECTS.USAGE_RECORDED,
|
||||
{ ...e, veraeUserId: c?.veraeUserId, at: new Date().toISOString() },
|
||||
'api',
|
||||
);
|
||||
};
|
||||
const hashIndex = new Map<
|
||||
string,
|
||||
{ jobId: string; sha256: string; data?: string; timestamp: string }
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ export interface Customer {
|
|||
/** How this customer is billed. Absent means 'stripe' (the default). */
|
||||
billingType?: BillingType;
|
||||
email?: string;
|
||||
/** Stable Verae central user id (not a JWT). */
|
||||
veraeUserId?: string;
|
||||
veraeUsername?: string;
|
||||
/** scrypt hash for portal login. Absent = no portal password set yet. */
|
||||
passwordHash?: string;
|
||||
/** Base32 TOTP secret. Present once 2FA setup begins. */
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export type AccessPlane = 'zapier' | 'web' | 'api' | 'leaf' | 'staff';
|
|||
|
||||
export type BillingStatement = {
|
||||
customerId: string;
|
||||
veraeUserId?: string;
|
||||
prepaidCents: number;
|
||||
credits: unknown[];
|
||||
usage: unknown[];
|
||||
|
|
@ -54,7 +55,7 @@ function decode(buf: Uint8Array): unknown {
|
|||
async function authzAllow(
|
||||
plane: AccessPlane,
|
||||
subject: string,
|
||||
extra: { principal?: string; kind?: string } = {},
|
||||
extra: { principal?: string; kind?: string; veraeUserId?: string } = {},
|
||||
): Promise<boolean> {
|
||||
const http = process.env.AUTHZ_URL;
|
||||
if (!http && !(await nc())) return true;
|
||||
|
|
@ -83,13 +84,18 @@ async function authzAllow(
|
|||
export async function natsStatement(
|
||||
customerId: string,
|
||||
plane: AccessPlane = 'web',
|
||||
veraeUserId?: string,
|
||||
): Promise<BillingStatement | null> {
|
||||
const c = await nc();
|
||||
if (!c) return null;
|
||||
if (!(await authzAllow(plane, BILLING_SUBJECTS.STATEMENT_GET, { principal: customerId }))) return null;
|
||||
if (
|
||||
!(await authzAllow(plane, BILLING_SUBJECTS.STATEMENT_GET, { principal: customerId, veraeUserId }))
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const m = await c.request(
|
||||
BILLING_SUBJECTS.STATEMENT_GET,
|
||||
encode({ customerId, plane }),
|
||||
encode({ customerId, plane, veraeUserId }),
|
||||
{ timeout: 2000 },
|
||||
);
|
||||
return decode(m.data) as BillingStatement;
|
||||
|
|
@ -98,6 +104,7 @@ export async function natsStatement(
|
|||
export async function natsAdjust(
|
||||
payload: {
|
||||
customerId: string;
|
||||
veraeUserId?: string;
|
||||
cents: number;
|
||||
reason: string;
|
||||
agent: string;
|
||||
|
|
@ -111,6 +118,7 @@ export async function natsAdjust(
|
|||
!(await authzAllow(plane, BILLING_SUBJECTS.BALANCE_ADJUST, {
|
||||
principal: payload.agent,
|
||||
kind: payload.kind,
|
||||
veraeUserId: payload.veraeUserId,
|
||||
}))
|
||||
) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { UsageRepo } from './usage';
|
|||
import { CreditLedger } from './credits';
|
||||
import { composeStatement } from './statement';
|
||||
import { BILLING_SUBJECTS, natsPublish, natsStatement, natsAdjust } from './billing-nats';
|
||||
import { bindVeraeUser } from './verae-bind';
|
||||
|
||||
/**
|
||||
* Customer portal API (/portal/api): signup, login with optional TOTP 2FA,
|
||||
|
|
@ -63,6 +64,7 @@ function publicProfile(c: Customer) {
|
|||
tierId: c.tierId,
|
||||
billingType: c.billingType ?? 'stripe',
|
||||
apiKey: c.apiKey,
|
||||
veraeUserId: c.veraeUserId,
|
||||
balanceCents: c.balanceCents ?? 0,
|
||||
totpEnabled: c.totpEnabled ?? false,
|
||||
emailInvoicing: c.emailInvoicing ?? false,
|
||||
|
|
@ -97,7 +99,7 @@ export function portalRouter(deps: PortalDeps): Router {
|
|||
|
||||
/* ---------------- auth ---------------- */
|
||||
|
||||
router.post('/signup', (req, res) => {
|
||||
router.post('/signup', async (req, res) => {
|
||||
const { name, email, password } = req.body ?? {};
|
||||
if (typeof name !== 'string' || name.trim().length === 0) {
|
||||
res.status(400).json({ error: 'name is required' });
|
||||
|
|
@ -133,6 +135,10 @@ export function portalRouter(deps: PortalDeps): Router {
|
|||
balanceCents: 0,
|
||||
};
|
||||
}
|
||||
if (!customer.veraeUserId) {
|
||||
const bind = await bindVeraeUser(email);
|
||||
customer = { ...customer, veraeUserId: bind.veraeUserId, veraeUsername: bind.veraeUsername };
|
||||
}
|
||||
save(deps, customer);
|
||||
const session = deps.sessions.create(customer.id, ttl);
|
||||
res.status(201).json({ token: session.token, customer: publicProfile(customer) });
|
||||
|
|
@ -203,7 +209,7 @@ export function portalRouter(deps: PortalDeps): Router {
|
|||
});
|
||||
|
||||
router.get('/statement', async (req, res) => {
|
||||
const fromNats = await natsStatement(req.customer!.id, 'web');
|
||||
const fromNats = await natsStatement(req.customer!.id, 'web', req.customer!.veraeUserId);
|
||||
if (fromNats) {
|
||||
res.json({ ...fromNats, source: 'nats' });
|
||||
return;
|
||||
|
|
@ -211,6 +217,7 @@ export function portalRouter(deps: PortalDeps): Router {
|
|||
res.json({
|
||||
...composeStatement({
|
||||
customerId: req.customer!.id,
|
||||
veraeUserId: req.customer!.veraeUserId,
|
||||
prepaidCents: req.customer!.balanceCents ?? 0,
|
||||
credits: (deps.credits || new CreditLedger()).list(req.customer!.id),
|
||||
usage: deps.usage.listFor(req.customer!.id),
|
||||
|
|
@ -290,6 +297,7 @@ export function portalRouter(deps: PortalDeps): Router {
|
|||
BILLING_SUBJECTS.PAYMENT_RECORDED,
|
||||
{
|
||||
customerId: customer.id,
|
||||
veraeUserId: customer.veraeUserId,
|
||||
cents: result.creditedCents,
|
||||
reason: 'reload',
|
||||
},
|
||||
|
|
@ -298,6 +306,7 @@ export function portalRouter(deps: PortalDeps): Router {
|
|||
void natsAdjust(
|
||||
{
|
||||
customerId: customer.id,
|
||||
veraeUserId: customer.veraeUserId,
|
||||
cents: result.creditedCents,
|
||||
reason: 'reload',
|
||||
agent: 'portal',
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { UsageEntry } from './usage';
|
|||
|
||||
export function composeStatement(args: {
|
||||
customerId: string;
|
||||
veraeUserId?: string;
|
||||
prepaidCents: number;
|
||||
credits: CreditAdjustment[];
|
||||
usage: UsageEntry[];
|
||||
|
|
@ -21,6 +22,7 @@ export function composeStatement(args: {
|
|||
}));
|
||||
return {
|
||||
customerId: args.customerId,
|
||||
veraeUserId: args.veraeUserId,
|
||||
prepaidCents: args.prepaidCents,
|
||||
credits: args.credits.filter((c) => c.customerId === args.customerId),
|
||||
usage: args.usage
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ export async function proxyVerae(
|
|||
if (key) headers['x-api-key'] = key;
|
||||
const auth = req.header('authorization');
|
||||
if (auth) headers.authorization = auth;
|
||||
const veraeUserId = req.customer?.veraeUserId;
|
||||
if (veraeUserId) headers['x-verae-user-id'] = veraeUserId;
|
||||
const method = req.method.toUpperCase();
|
||||
const init: RequestInit = { method, headers };
|
||||
if (method !== 'GET' && method !== 'HEAD') {
|
||||
|
|
|
|||
76
packages/zappier/src/verae-bind.ts
Normal file
76
packages/zappier/src/verae-bind.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* Bind a portal customer to a Verae central user id.
|
||||
* The public credential stays the zappier API key. The Verae JWT never leaves the server.
|
||||
*/
|
||||
import { createHash, randomBytes } from 'crypto';
|
||||
|
||||
export function normalizeVeraeUsername(username: string): string {
|
||||
return username.trim().toLowerCase();
|
||||
}
|
||||
|
||||
export function stableVeraeUserId(username: string): string {
|
||||
const n = normalizeVeraeUsername(username);
|
||||
return `vu_${createHash('sha256').update(n).digest('hex').slice(0, 16)}`;
|
||||
}
|
||||
|
||||
export type VeraeBind = {
|
||||
veraeUserId: string;
|
||||
veraeUsername: string;
|
||||
bound: boolean;
|
||||
};
|
||||
|
||||
function mockBind(email: string): VeraeBind {
|
||||
const veraeUsername = normalizeVeraeUsername(email);
|
||||
return { veraeUserId: stableVeraeUserId(veraeUsername), veraeUsername, bound: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Register or look up the customer on api.veraetime.net.
|
||||
* MOCK_VERAE (default) or missing VERAE_API_BASE_URL → stable id, no network.
|
||||
*/
|
||||
export async function bindVeraeUser(email: string): Promise<VeraeBind> {
|
||||
const mock = process.env.MOCK_VERAE !== 'false';
|
||||
const base = (process.env.VERAE_API_BASE_URL || '').replace(/\/$/, '');
|
||||
if (mock || !base) return mockBind(email);
|
||||
|
||||
const veraeUsername = normalizeVeraeUsername(email);
|
||||
const adminUser = process.env.VERAE_ADMIN_USER;
|
||||
const adminPass = process.env.VERAE_ADMIN_PASSWORD;
|
||||
const password = `vt_${randomBytes(18).toString('base64url')}`;
|
||||
|
||||
const login = async (username: string, pass: string) => {
|
||||
const r = await fetch(`${base}/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ username, password: pass }),
|
||||
});
|
||||
if (!r.ok) throw new Error(`verae login ${r.status}`);
|
||||
return r.json() as Promise<{ token: string; user?: { id?: string; username?: string } }>;
|
||||
};
|
||||
|
||||
if (!adminUser || !adminPass) return mockBind(email);
|
||||
|
||||
try {
|
||||
const admin = await login(adminUser, adminPass);
|
||||
const created = await fetch(`${base}/auth/users`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
authorization: `Bearer ${admin.token}`,
|
||||
},
|
||||
body: JSON.stringify({ username: veraeUsername, password, role: 'user' }),
|
||||
});
|
||||
if (!created.ok && created.status !== 409) {
|
||||
return mockBind(email);
|
||||
}
|
||||
const user = (await created.json().catch(() => ({}))) as { id?: string; username?: string };
|
||||
const id = user.id || (await login(veraeUsername, password)).user?.id;
|
||||
return {
|
||||
veraeUserId: id || stableVeraeUserId(veraeUsername),
|
||||
veraeUsername,
|
||||
bound: true,
|
||||
};
|
||||
} catch {
|
||||
return mockBind(email);
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ async function signup(app: ReturnType<typeof buildApp>['app'], email = 'ada@exam
|
|||
.post('/portal/api/signup')
|
||||
.send({ name: 'Ada', email, password: 'super-secret-1' });
|
||||
expect(res.status).toBe(201);
|
||||
return res.body as { token: string; customer: { id: string; apiKey: string } };
|
||||
return res.body as { token: string; customer: { id: string; apiKey: string; veraeUserId?: string } };
|
||||
}
|
||||
|
||||
describe('portal signup + login', () => {
|
||||
|
|
@ -34,6 +34,9 @@ describe('portal signup + login', () => {
|
|||
expect(me.status).toBe(200);
|
||||
expect(me.body.tierId).toBe('free');
|
||||
expect(me.body.balanceCents).toBe(0);
|
||||
expect(customer.veraeUserId).toMatch(/^vu_[0-9a-f]{16}$/);
|
||||
expect(me.body.veraeUserId).toBe(customer.veraeUserId);
|
||||
expect(JSON.stringify(me.body)).not.toMatch(/eyJ|mock-jwt|veraeToken/);
|
||||
});
|
||||
|
||||
it('never leaks passwordHash or totpSecret through the API', async () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue