Separate Zapier, web, API, and leaf access planes with NATS authz
Some checks are pending
offline / test (push) Waiting to run
Some checks are pending
offline / test (push) Waiting to run
Zapier is one ingress. Direct web, customer API, and S2S leaf nodes are their own services. Every hop to an internal subject must pass verae.access.authz.check (default deny by plane).
This commit is contained in:
parent
ac38676645
commit
1b199ca4d4
117 changed files with 2640 additions and 105 deletions
|
|
@ -126,3 +126,12 @@ One MD file per source module.
|
|||
- [zappier-sales-pricing/server](zappier-sales-pricing/server.md)
|
||||
- [zappier-accounting-export/nats-billing](zappier-accounting-export/nats-billing.md)
|
||||
- [zappier-accounting-export/server](zappier-accounting-export/server.md)
|
||||
- [verae-access-authz/client](verae-access-authz/client.md)
|
||||
- [verae-access-authz/policy](verae-access-authz/policy.md)
|
||||
- [verae-access-authz/server](verae-access-authz/server.md)
|
||||
- [verae-access-authz/subjects](verae-access-authz/subjects.md)
|
||||
- [verae-access-web/gate](verae-access-web/gate.md)
|
||||
- [verae-access-web/server](verae-access-web/server.md)
|
||||
- [verae-access-api/server](verae-access-api/server.md)
|
||||
- [verae-access-leaf/server](verae-access-leaf/server.md)
|
||||
- [verae-access-zapier/server](verae-access-zapier/server.md)
|
||||
|
|
|
|||
41
docs/modules/verae-access-api/server.md
Normal file
41
docs/modules/verae-access-api/server.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# `verae-access-api/server`
|
||||
|
||||
**Package:** `verae-access-api`
|
||||
**Source:** `packages/verae-access-api/src/server.js`
|
||||
**Lines:** 60
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-access-api`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
_No named `export` / `module.exports` keys detected._
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `check` | `subject, principal, extra = {}` | — | `unknown` | see Call graph |
|
||||
| `json` | `code, obj` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `node:http`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`replace`, `check`, `fetch`, `stringify`, `json`, `createServer`, `async`, `writeHead`, `end`, `match`, `push`, `concat`, `listen`, `write`
|
||||
|
||||
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`.
|
||||
|
||||
42
docs/modules/verae-access-authz/client.md
Normal file
42
docs/modules/verae-access-authz/client.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# `verae-access-authz/client`
|
||||
|
||||
**Package:** `verae-access-authz`
|
||||
**Source:** `packages/verae-access-authz/src/client.js`
|
||||
**Lines:** 39
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-access-authz`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
`authorizeThenRequest`, `authorizeThenPublish`
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `authorizeThenRequest` | `nc, sc, { plane, subject, principal, payload, timeout = 2000 }` | — | `unknown` | see Call graph |
|
||||
| | _Call verae.access.authz.check then the internal subject. Fail closed._ | | | |
|
||||
| `authorizeThenPublish` | `nc, sc, { plane, subject, principal, payload, timeout = 2000 }` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `./subjects.js`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`authorizeThenRequest`, `request`, `encode`, `stringify`, `parse`, `decode`, `authorizeThenPublish`, `publish`
|
||||
|
||||
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`.
|
||||
|
||||
42
docs/modules/verae-access-authz/policy.md
Normal file
42
docs/modules/verae-access-authz/policy.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# `verae-access-authz/policy`
|
||||
|
||||
**Package:** `verae-access-authz`
|
||||
**Source:** `packages/verae-access-authz/src/policy.js`
|
||||
**Lines:** 99
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-access-authz`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
`POLICY`, `authorize`
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `prefixAllowed` | `allow, subject` | — | `unknown` | see Call graph |
|
||||
| `authorize` | `req` | — | `unknown` | see Call graph |
|
||||
| `deny` | `plane, subject, reason` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `./subjects.js`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`workers`, `freeze`, `web`, `prefixAllowed`, `some`, `endsWith`, `startsWith`, `authorize`, `parseAddress`, `deny`, `includes`, `mismatch`
|
||||
|
||||
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`.
|
||||
|
||||
44
docs/modules/verae-access-authz/server.md
Normal file
44
docs/modules/verae-access-authz/server.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# `verae-access-authz/server`
|
||||
|
||||
**Package:** `verae-access-authz`
|
||||
**Source:** `packages/verae-access-authz/src/server.js`
|
||||
**Lines:** 84
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-access-authz`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
_No named `export` / `module.exports` keys detected._
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `startNats` | `(none)` | — | `unknown` | see Call graph |
|
||||
| `readBody` | `req` | — | `unknown` | see Call graph |
|
||||
| `json` | `code, obj` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `node:http`
|
||||
- `./policy.js`
|
||||
- `./subjects.js`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`startNats`, `import`, `connect`, `split`, `subscribe`, `async`, `parse`, `decode`, `authorize`, `publish`, `encode`, `stringify`, `toISOString`, `respond`, `write`, `readBody`, `on`, `push`, `resolve`, `concat`, `toString`, `createServer`, `writeHead`, `end`, `json`, `listen`
|
||||
|
||||
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`.
|
||||
|
||||
43
docs/modules/verae-access-authz/subjects.md
Normal file
43
docs/modules/verae-access-authz/subjects.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# `verae-access-authz/subjects`
|
||||
|
||||
**Package:** `verae-access-authz`
|
||||
**Source:** `packages/verae-access-authz/src/subjects.js`
|
||||
**Lines:** 37
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-access-authz`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
`PLANES`, `SUBJECTS`, `accessAddress`, `parseAddress`
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `accessAddress` | `plane, internalSubject` | — | `unknown` | see Call graph |
|
||||
| | _verae.billing.statement.get + plane web → verae.access.web.billing.statement.get_ | | | |
|
||||
| `parseAddress` | `subject, planeHint` | — | `unknown` | see Call graph |
|
||||
| | _Parse an access or internal subject into { plane, internal }._ | | | |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
_No imports detected._
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`ingress`, `internal`, `freeze`, `accessAddress`, `includes`, `startsWith`, `slice`, `parseAddress`, `match`
|
||||
|
||||
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`.
|
||||
|
||||
44
docs/modules/verae-access-leaf/server.md
Normal file
44
docs/modules/verae-access-leaf/server.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# `verae-access-leaf/server`
|
||||
|
||||
**Package:** `verae-access-leaf`
|
||||
**Source:** `packages/verae-access-leaf/src/server.js`
|
||||
**Lines:** 95
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-access-leaf`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
_No named `export` / `module.exports` keys detected._
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `check` | `subject, principal` | — | `unknown` | see Call graph |
|
||||
| `authorizedToken` | `got` | — | `unknown` | see Call graph |
|
||||
| `forward` | `body` | — | `unknown` | see Call graph |
|
||||
| `startNats` | `(none)` | — | `unknown` | see Call graph |
|
||||
| `json` | `code, obj` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `node:http`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`subjects`, `replace`, `check`, `fetch`, `stringify`, `json`, `authorizedToken`, `forward`, `import`, `connect`, `split`, `publish`, `encode`, `flush`, `close`, `startNats`, `async`, `subscribe`, `parse`, `decode`, `respond`, `write`, `createServer`, `writeHead`, `end`, `push`, `concat`, `toString`, `listen`
|
||||
|
||||
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`.
|
||||
|
||||
42
docs/modules/verae-access-web/gate.md
Normal file
42
docs/modules/verae-access-web/gate.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# `verae-access-web/gate`
|
||||
|
||||
**Package:** `verae-access-web`
|
||||
**Source:** `packages/verae-access-web/src/gate.js`
|
||||
**Lines:** 51
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-access-web`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
`PLANE`, `AUTHZ_CHECK`, `check`, `statement`, `reload`
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `check` | `subject, { principal, kind, payload } = {}` | — | `unknown` | see Call graph |
|
||||
| `statement` | `customerId, principal` | — | `unknown` | see Call graph |
|
||||
| `reload` | `customerId, cents, principal` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
_No imports detected._
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`replace`, `check`, `fetch`, `stringify`, `json`, `statement`, `import`, `connect`, `split`, `request`, `encode`, `parse`, `decode`, `close`, `reload`
|
||||
|
||||
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`.
|
||||
|
||||
41
docs/modules/verae-access-web/server.md
Normal file
41
docs/modules/verae-access-web/server.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# `verae-access-web/server`
|
||||
|
||||
**Package:** `verae-access-web`
|
||||
**Source:** `packages/verae-access-web/src/server.js`
|
||||
**Lines:** 39
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-access-web`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
_No named `export` / `module.exports` keys detected._
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `json` | `code, obj` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `node:http`
|
||||
- `./gate.js`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`createServer`, `async`, `writeHead`, `end`, `stringify`, `json`, `match`, `statement`, `push`, `parse`, `concat`, `toString`, `reload`, `listen`, `write`
|
||||
|
||||
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`.
|
||||
|
||||
41
docs/modules/verae-access-zapier/server.md
Normal file
41
docs/modules/verae-access-zapier/server.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# `verae-access-zapier/server`
|
||||
|
||||
**Package:** `verae-access-zapier`
|
||||
**Source:** `packages/verae-access-zapier/src/server.js`
|
||||
**Lines:** 56
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-access-zapier`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
_No named `export` / `module.exports` keys detected._
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `check` | `subject, principal` | — | `unknown` | see Call graph |
|
||||
| `json` | `code, obj` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `node:http`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`replace`, `check`, `fetch`, `stringify`, `json`, `createServer`, `async`, `writeHead`, `end`, `startsWith`, `push`, `concat`, `listen`, `write`
|
||||
|
||||
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:** `zappier-customer-service`
|
||||
**Source:** `packages/zappier-customer-service/src/nats-billing.js`
|
||||
**Lines:** 21
|
||||
**Lines:** 45
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -20,6 +20,7 @@ _None extracted._
|
|||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `authz` | `subject, payload` | — | `unknown` | see Call graph |
|
||||
| `billingRequest` | `subject, payload` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
|
@ -28,7 +29,7 @@ _No imports detected._
|
|||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`billingRequest`, `import`, `connect`, `split`, `request`, `encode`, `stringify`, `parse`, `decode`, `close`
|
||||
`authz`, `fetch`, `replace`, `stringify`, `json`, `billingRequest`, `import`, `connect`, `split`, `request`, `encode`, `parse`, `decode`, `close`
|
||||
|
||||
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-sales-pricing`
|
||||
**Source:** `packages/zappier-sales-pricing/src/nats-billing.js`
|
||||
**Lines:** 18
|
||||
**Lines:** 37
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -20,6 +20,7 @@ _None extracted._
|
|||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `authz` | `subject` | — | `unknown` | see Call graph |
|
||||
| `billingRequest` | `subject, payload` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
|
@ -28,7 +29,7 @@ _No imports detected._
|
|||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`billingRequest`, `import`, `connect`, `split`, `request`, `encode`, `stringify`, `parse`, `decode`, `close`
|
||||
`authz`, `fetch`, `replace`, `stringify`, `json`, `billingRequest`, `import`, `connect`, `split`, `request`, `encode`, `parse`, `decode`, `close`
|
||||
|
||||
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:** 588
|
||||
**Lines:** 591
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier`
|
||||
**Source:** `packages/zappier/src/billing-nats.ts`
|
||||
**Lines:** 74
|
||||
**Lines:** 130
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -10,12 +10,13 @@ Implementation module in `zappier`. The tables below are extracted from the sour
|
|||
|
||||
## Exports
|
||||
|
||||
`BILLING_SUBJECTS`, `BillingStatement`, `natsStatement`, `natsAdjust`, `natsPublish`
|
||||
`BILLING_SUBJECTS`, `AUTHZ_CHECK`, `AccessPlane`, `BillingStatement`, `natsStatement`, `natsAdjust`, `natsPublish`
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
| Kind | Name |
|
||||
|------|------|
|
||||
| type | `AccessPlane` |
|
||||
| type | `BillingStatement` |
|
||||
| type | `Nc` |
|
||||
|
||||
|
|
@ -26,15 +27,26 @@ Implementation module in `zappier`. The tables below are extracted from the sour
|
|||
| `nc` | `(none)` | — | `unknown` | see Call graph |
|
||||
| `encode` | `obj: unknown` | — | `unknown` | see Call graph |
|
||||
| `decode` | `buf: Uint8Array` | — | `unknown` | see Call graph |
|
||||
| `natsStatement` | `customerId: string` | — | `unknown` | see Call graph |
|
||||
| `authzAllow` | `plane: AccessPlane,
|
||||
subject: string,
|
||||
extra: { principal?: string; kind?: string } = {},` | — | `unknown` | see Call graph |
|
||||
| `natsStatement` | `customerId: string,
|
||||
plane: AccessPlane = 'web',` | — | `unknown` | see Call graph |
|
||||
| `natsAdjust` | `payload: {
|
||||
customerId: string;
|
||||
cents: number;
|
||||
reason: string;
|
||||
agent: string;
|
||||
kind?: string;
|
||||
}` | — | `unknown` | see Call graph |
|
||||
| `natsPublish` | `subject: string, payload: unknown` | — | `unknown` | see Call graph |
|
||||
customerId: string;
|
||||
cents: number;
|
||||
reason: string;
|
||||
agent: string;
|
||||
kind?: string;
|
||||
},
|
||||
plane: AccessPlane = 'staff',` | — | `unknown` | see Call graph |
|
||||
| `natsPublish` | `subject: string, payload: unknown, plane: AccessPlane = 'api'` | — | `unknown` | see Call graph |
|
||||
|
||||
## Methods (class / object)
|
||||
|
||||
| Name | Parameters |
|
||||
|------|------------|
|
||||
| `void` | `async (` |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
|
|
@ -42,7 +54,7 @@ _No imports detected._
|
|||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`unset`, `nc`, `async`, `import`, `connect`, `split`, `encode`, `stringify`, `decode`, `parse`, `natsStatement`, `request`, `natsAdjust`, `natsPublish`, `then`, `publish`
|
||||
`unset`, `nc`, `async`, `import`, `connect`, `split`, `encode`, `stringify`, `decode`, `parse`, `authzAllow`, `request`, `fetch`, `replace`, `json`, `natsStatement`, `natsAdjust`, `natsPublish`, `void`, `publish`
|
||||
|
||||
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/portal.ts`
|
||||
**Lines:** 321
|
||||
**Lines:** 328
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@ Markdown indexes: https://zapier.georgelambert.org/index-md.html
|
|||
repos/zappier-customer-service
|
||||
repos/zappier-sales-pricing
|
||||
repos/zappier-accounting-export
|
||||
repos/verae-access-authz
|
||||
repos/verae-access-web
|
||||
repos/verae-access-api
|
||||
repos/verae-access-leaf
|
||||
repos/verae-access-zapier
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
|
@ -157,3 +162,12 @@ Markdown indexes: https://zapier.georgelambert.org/index-md.html
|
|||
modules/zappier-sales-pricing/server
|
||||
modules/zappier-accounting-export/nats-billing
|
||||
modules/zappier-accounting-export/server
|
||||
modules/verae-access-authz/client
|
||||
modules/verae-access-authz/policy
|
||||
modules/verae-access-authz/server
|
||||
modules/verae-access-authz/subjects
|
||||
modules/verae-access-web/gate
|
||||
modules/verae-access-web/server
|
||||
modules/verae-access-api/server
|
||||
modules/verae-access-leaf/server
|
||||
modules/verae-access-zapier/server
|
||||
|
|
|
|||
41
docs/sphinx/modules/verae-access-api/server.md
Normal file
41
docs/sphinx/modules/verae-access-api/server.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# `verae-access-api/server`
|
||||
|
||||
**Package:** `verae-access-api`
|
||||
**Source:** `packages/verae-access-api/src/server.js`
|
||||
**Lines:** 60
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-access-api`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
_No named `export` / `module.exports` keys detected._
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `check` | `subject, principal, extra = {}` | — | `unknown` | see Call graph |
|
||||
| `json` | `code, obj` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `node:http`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`replace`, `check`, `fetch`, `stringify`, `json`, `createServer`, `async`, `writeHead`, `end`, `match`, `push`, `concat`, `listen`, `write`
|
||||
|
||||
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/verae-access-api/server.rst
Normal file
7
docs/sphinx/modules/verae-access-api/server.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
verae-access-api.server
|
||||
=======================
|
||||
|
||||
Generated API sheet for ``verae-access-api/server``.
|
||||
|
||||
.. include:: server.md
|
||||
:parser: myst_parser.sphinx_
|
||||
42
docs/sphinx/modules/verae-access-authz/client.md
Normal file
42
docs/sphinx/modules/verae-access-authz/client.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# `verae-access-authz/client`
|
||||
|
||||
**Package:** `verae-access-authz`
|
||||
**Source:** `packages/verae-access-authz/src/client.js`
|
||||
**Lines:** 39
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-access-authz`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
`authorizeThenRequest`, `authorizeThenPublish`
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `authorizeThenRequest` | `nc, sc, { plane, subject, principal, payload, timeout = 2000 }` | — | `unknown` | see Call graph |
|
||||
| | _Call verae.access.authz.check then the internal subject. Fail closed._ | | | |
|
||||
| `authorizeThenPublish` | `nc, sc, { plane, subject, principal, payload, timeout = 2000 }` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `./subjects.js`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`authorizeThenRequest`, `request`, `encode`, `stringify`, `parse`, `decode`, `authorizeThenPublish`, `publish`
|
||||
|
||||
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/verae-access-authz/client.rst
Normal file
7
docs/sphinx/modules/verae-access-authz/client.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
verae-access-authz.client
|
||||
=========================
|
||||
|
||||
Generated API sheet for ``verae-access-authz/client``.
|
||||
|
||||
.. include:: client.md
|
||||
:parser: myst_parser.sphinx_
|
||||
42
docs/sphinx/modules/verae-access-authz/policy.md
Normal file
42
docs/sphinx/modules/verae-access-authz/policy.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# `verae-access-authz/policy`
|
||||
|
||||
**Package:** `verae-access-authz`
|
||||
**Source:** `packages/verae-access-authz/src/policy.js`
|
||||
**Lines:** 99
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-access-authz`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
`POLICY`, `authorize`
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `prefixAllowed` | `allow, subject` | — | `unknown` | see Call graph |
|
||||
| `authorize` | `req` | — | `unknown` | see Call graph |
|
||||
| `deny` | `plane, subject, reason` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `./subjects.js`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`workers`, `freeze`, `web`, `prefixAllowed`, `some`, `endsWith`, `startsWith`, `authorize`, `parseAddress`, `deny`, `includes`, `mismatch`
|
||||
|
||||
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/verae-access-authz/policy.rst
Normal file
7
docs/sphinx/modules/verae-access-authz/policy.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
verae-access-authz.policy
|
||||
=========================
|
||||
|
||||
Generated API sheet for ``verae-access-authz/policy``.
|
||||
|
||||
.. include:: policy.md
|
||||
:parser: myst_parser.sphinx_
|
||||
44
docs/sphinx/modules/verae-access-authz/server.md
Normal file
44
docs/sphinx/modules/verae-access-authz/server.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# `verae-access-authz/server`
|
||||
|
||||
**Package:** `verae-access-authz`
|
||||
**Source:** `packages/verae-access-authz/src/server.js`
|
||||
**Lines:** 84
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-access-authz`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
_No named `export` / `module.exports` keys detected._
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `startNats` | `(none)` | — | `unknown` | see Call graph |
|
||||
| `readBody` | `req` | — | `unknown` | see Call graph |
|
||||
| `json` | `code, obj` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `node:http`
|
||||
- `./policy.js`
|
||||
- `./subjects.js`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`startNats`, `import`, `connect`, `split`, `subscribe`, `async`, `parse`, `decode`, `authorize`, `publish`, `encode`, `stringify`, `toISOString`, `respond`, `write`, `readBody`, `on`, `push`, `resolve`, `concat`, `toString`, `createServer`, `writeHead`, `end`, `json`, `listen`
|
||||
|
||||
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/verae-access-authz/server.rst
Normal file
7
docs/sphinx/modules/verae-access-authz/server.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
verae-access-authz.server
|
||||
=========================
|
||||
|
||||
Generated API sheet for ``verae-access-authz/server``.
|
||||
|
||||
.. include:: server.md
|
||||
:parser: myst_parser.sphinx_
|
||||
43
docs/sphinx/modules/verae-access-authz/subjects.md
Normal file
43
docs/sphinx/modules/verae-access-authz/subjects.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# `verae-access-authz/subjects`
|
||||
|
||||
**Package:** `verae-access-authz`
|
||||
**Source:** `packages/verae-access-authz/src/subjects.js`
|
||||
**Lines:** 37
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-access-authz`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
`PLANES`, `SUBJECTS`, `accessAddress`, `parseAddress`
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `accessAddress` | `plane, internalSubject` | — | `unknown` | see Call graph |
|
||||
| | _verae.billing.statement.get + plane web → verae.access.web.billing.statement.get_ | | | |
|
||||
| `parseAddress` | `subject, planeHint` | — | `unknown` | see Call graph |
|
||||
| | _Parse an access or internal subject into { plane, internal }._ | | | |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
_No imports detected._
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`ingress`, `internal`, `freeze`, `accessAddress`, `includes`, `startsWith`, `slice`, `parseAddress`, `match`
|
||||
|
||||
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/verae-access-authz/subjects.rst
Normal file
7
docs/sphinx/modules/verae-access-authz/subjects.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
verae-access-authz.subjects
|
||||
===========================
|
||||
|
||||
Generated API sheet for ``verae-access-authz/subjects``.
|
||||
|
||||
.. include:: subjects.md
|
||||
:parser: myst_parser.sphinx_
|
||||
44
docs/sphinx/modules/verae-access-leaf/server.md
Normal file
44
docs/sphinx/modules/verae-access-leaf/server.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# `verae-access-leaf/server`
|
||||
|
||||
**Package:** `verae-access-leaf`
|
||||
**Source:** `packages/verae-access-leaf/src/server.js`
|
||||
**Lines:** 95
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-access-leaf`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
_No named `export` / `module.exports` keys detected._
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `check` | `subject, principal` | — | `unknown` | see Call graph |
|
||||
| `authorizedToken` | `got` | — | `unknown` | see Call graph |
|
||||
| `forward` | `body` | — | `unknown` | see Call graph |
|
||||
| `startNats` | `(none)` | — | `unknown` | see Call graph |
|
||||
| `json` | `code, obj` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `node:http`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`subjects`, `replace`, `check`, `fetch`, `stringify`, `json`, `authorizedToken`, `forward`, `import`, `connect`, `split`, `publish`, `encode`, `flush`, `close`, `startNats`, `async`, `subscribe`, `parse`, `decode`, `respond`, `write`, `createServer`, `writeHead`, `end`, `push`, `concat`, `toString`, `listen`
|
||||
|
||||
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/verae-access-leaf/server.rst
Normal file
7
docs/sphinx/modules/verae-access-leaf/server.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
verae-access-leaf.server
|
||||
========================
|
||||
|
||||
Generated API sheet for ``verae-access-leaf/server``.
|
||||
|
||||
.. include:: server.md
|
||||
:parser: myst_parser.sphinx_
|
||||
42
docs/sphinx/modules/verae-access-web/gate.md
Normal file
42
docs/sphinx/modules/verae-access-web/gate.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# `verae-access-web/gate`
|
||||
|
||||
**Package:** `verae-access-web`
|
||||
**Source:** `packages/verae-access-web/src/gate.js`
|
||||
**Lines:** 51
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-access-web`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
`PLANE`, `AUTHZ_CHECK`, `check`, `statement`, `reload`
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `check` | `subject, { principal, kind, payload } = {}` | — | `unknown` | see Call graph |
|
||||
| `statement` | `customerId, principal` | — | `unknown` | see Call graph |
|
||||
| `reload` | `customerId, cents, principal` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
_No imports detected._
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`replace`, `check`, `fetch`, `stringify`, `json`, `statement`, `import`, `connect`, `split`, `request`, `encode`, `parse`, `decode`, `close`, `reload`
|
||||
|
||||
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/verae-access-web/gate.rst
Normal file
7
docs/sphinx/modules/verae-access-web/gate.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
verae-access-web.gate
|
||||
=====================
|
||||
|
||||
Generated API sheet for ``verae-access-web/gate``.
|
||||
|
||||
.. include:: gate.md
|
||||
:parser: myst_parser.sphinx_
|
||||
41
docs/sphinx/modules/verae-access-web/server.md
Normal file
41
docs/sphinx/modules/verae-access-web/server.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# `verae-access-web/server`
|
||||
|
||||
**Package:** `verae-access-web`
|
||||
**Source:** `packages/verae-access-web/src/server.js`
|
||||
**Lines:** 39
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-access-web`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
_No named `export` / `module.exports` keys detected._
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `json` | `code, obj` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `node:http`
|
||||
- `./gate.js`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`createServer`, `async`, `writeHead`, `end`, `stringify`, `json`, `match`, `statement`, `push`, `parse`, `concat`, `toString`, `reload`, `listen`, `write`
|
||||
|
||||
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/verae-access-web/server.rst
Normal file
7
docs/sphinx/modules/verae-access-web/server.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
verae-access-web.server
|
||||
=======================
|
||||
|
||||
Generated API sheet for ``verae-access-web/server``.
|
||||
|
||||
.. include:: server.md
|
||||
:parser: myst_parser.sphinx_
|
||||
41
docs/sphinx/modules/verae-access-zapier/server.md
Normal file
41
docs/sphinx/modules/verae-access-zapier/server.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# `verae-access-zapier/server`
|
||||
|
||||
**Package:** `verae-access-zapier`
|
||||
**Source:** `packages/verae-access-zapier/src/server.js`
|
||||
**Lines:** 56
|
||||
|
||||
## What this module is
|
||||
|
||||
Implementation module in `verae-access-zapier`. The tables below are extracted from the source (signatures + JSDoc).
|
||||
|
||||
## Exports
|
||||
|
||||
_No named `export` / `module.exports` keys detected._
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
_None extracted._
|
||||
|
||||
## Functions
|
||||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `check` | `subject, principal` | — | `unknown` | see Call graph |
|
||||
| `json` | `code, obj` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
- `node:http`
|
||||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`replace`, `check`, `fetch`, `stringify`, `json`, `createServer`, `async`, `writeHead`, `end`, `startsWith`, `push`, `concat`, `listen`, `write`
|
||||
|
||||
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/verae-access-zapier/server.rst
Normal file
7
docs/sphinx/modules/verae-access-zapier/server.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
verae-access-zapier.server
|
||||
==========================
|
||||
|
||||
Generated API sheet for ``verae-access-zapier/server``.
|
||||
|
||||
.. include:: server.md
|
||||
:parser: myst_parser.sphinx_
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier-customer-service`
|
||||
**Source:** `packages/zappier-customer-service/src/nats-billing.js`
|
||||
**Lines:** 21
|
||||
**Lines:** 45
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -20,6 +20,7 @@ _None extracted._
|
|||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `authz` | `subject, payload` | — | `unknown` | see Call graph |
|
||||
| `billingRequest` | `subject, payload` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
|
@ -28,7 +29,7 @@ _No imports detected._
|
|||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`billingRequest`, `import`, `connect`, `split`, `request`, `encode`, `stringify`, `parse`, `decode`, `close`
|
||||
`authz`, `fetch`, `replace`, `stringify`, `json`, `billingRequest`, `import`, `connect`, `split`, `request`, `encode`, `parse`, `decode`, `close`
|
||||
|
||||
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-sales-pricing`
|
||||
**Source:** `packages/zappier-sales-pricing/src/nats-billing.js`
|
||||
**Lines:** 18
|
||||
**Lines:** 37
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -20,6 +20,7 @@ _None extracted._
|
|||
|
||||
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||
|------|------------|---------------------|---------|-----------------------------|
|
||||
| `authz` | `subject` | — | `unknown` | see Call graph |
|
||||
| `billingRequest` | `subject, payload` | — | `unknown` | see Call graph |
|
||||
|
||||
## What it imports / requires
|
||||
|
|
@ -28,7 +29,7 @@ _No imports detected._
|
|||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`billingRequest`, `import`, `connect`, `split`, `request`, `encode`, `stringify`, `parse`, `decode`, `close`
|
||||
`authz`, `fetch`, `replace`, `stringify`, `json`, `billingRequest`, `import`, `connect`, `split`, `request`, `encode`, `parse`, `decode`, `close`
|
||||
|
||||
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:** 588
|
||||
**Lines:** 591
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Package:** `zappier`
|
||||
**Source:** `packages/zappier/src/billing-nats.ts`
|
||||
**Lines:** 74
|
||||
**Lines:** 130
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
@ -10,12 +10,13 @@ Implementation module in `zappier`. The tables below are extracted from the sour
|
|||
|
||||
## Exports
|
||||
|
||||
`BILLING_SUBJECTS`, `BillingStatement`, `natsStatement`, `natsAdjust`, `natsPublish`
|
||||
`BILLING_SUBJECTS`, `AUTHZ_CHECK`, `AccessPlane`, `BillingStatement`, `natsStatement`, `natsAdjust`, `natsPublish`
|
||||
|
||||
## Types / interfaces / classes
|
||||
|
||||
| Kind | Name |
|
||||
|------|------|
|
||||
| type | `AccessPlane` |
|
||||
| type | `BillingStatement` |
|
||||
| type | `Nc` |
|
||||
|
||||
|
|
@ -26,15 +27,26 @@ Implementation module in `zappier`. The tables below are extracted from the sour
|
|||
| `nc` | `(none)` | — | `unknown` | see Call graph |
|
||||
| `encode` | `obj: unknown` | — | `unknown` | see Call graph |
|
||||
| `decode` | `buf: Uint8Array` | — | `unknown` | see Call graph |
|
||||
| `natsStatement` | `customerId: string` | — | `unknown` | see Call graph |
|
||||
| `authzAllow` | `plane: AccessPlane,
|
||||
subject: string,
|
||||
extra: { principal?: string; kind?: string } = {},` | — | `unknown` | see Call graph |
|
||||
| `natsStatement` | `customerId: string,
|
||||
plane: AccessPlane = 'web',` | — | `unknown` | see Call graph |
|
||||
| `natsAdjust` | `payload: {
|
||||
customerId: string;
|
||||
cents: number;
|
||||
reason: string;
|
||||
agent: string;
|
||||
kind?: string;
|
||||
}` | — | `unknown` | see Call graph |
|
||||
| `natsPublish` | `subject: string, payload: unknown` | — | `unknown` | see Call graph |
|
||||
customerId: string;
|
||||
cents: number;
|
||||
reason: string;
|
||||
agent: string;
|
||||
kind?: string;
|
||||
},
|
||||
plane: AccessPlane = 'staff',` | — | `unknown` | see Call graph |
|
||||
| `natsPublish` | `subject: string, payload: unknown, plane: AccessPlane = 'api'` | — | `unknown` | see Call graph |
|
||||
|
||||
## Methods (class / object)
|
||||
|
||||
| Name | Parameters |
|
||||
|------|------------|
|
||||
| `void` | `async (` |
|
||||
|
||||
## What it imports / requires
|
||||
|
||||
|
|
@ -42,7 +54,7 @@ _No imports detected._
|
|||
|
||||
## Call graph (identifiers invoked)
|
||||
|
||||
`unset`, `nc`, `async`, `import`, `connect`, `split`, `encode`, `stringify`, `decode`, `parse`, `natsStatement`, `request`, `natsAdjust`, `natsPublish`, `then`, `publish`
|
||||
`unset`, `nc`, `async`, `import`, `connect`, `split`, `encode`, `stringify`, `decode`, `parse`, `authzAllow`, `request`, `fetch`, `replace`, `json`, `natsStatement`, `natsAdjust`, `natsPublish`, `void`, `publish`
|
||||
|
||||
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/portal.ts`
|
||||
**Lines:** 321
|
||||
**Lines:** 328
|
||||
|
||||
## What this module is
|
||||
|
||||
|
|
|
|||
11
docs/sphinx/repos/verae-access-api.md
Normal file
11
docs/sphinx/repos/verae-access-api.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# verae-access-api
|
||||
|
||||
**Direct customer API** plane (`x-api-key`). Separate from the Zapier Platform app.
|
||||
|
||||
**Forgejo:** https://git.georgelambert.org/marchon/verae-access-api
|
||||
|
||||
Address path: `verae.access.api.*` → `verae.access.authz.check` → `verae.billing.statement.get` / `verae.zapier.jobs.watch` (HTTPS to middleware, not a Zapier NATS client).
|
||||
|
||||
Cannot `balance.adjust` or `archive.put`.
|
||||
|
||||
Port `:3022`.
|
||||
34
docs/sphinx/repos/verae-access-authz.md
Normal file
34
docs/sphinx/repos/verae-access-authz.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# verae-access-authz
|
||||
|
||||
Authorization step on the NATS **address path**. Zapier is only **one** access plane. Direct web, customer API, and server-to-server leaf nodes are separate, and each is default-deny against internal subjects.
|
||||
|
||||
**Forgejo:** https://git.georgelambert.org/marchon/verae-access-authz
|
||||
**Catalog:** https://zapier.georgelambert.org/packages/verae-access-authz/README.pdf
|
||||
|
||||
## Address path
|
||||
|
||||
```text
|
||||
client
|
||||
→ verae.access.<plane>.<area>.<resource>.<action> (ingress, that plane only)
|
||||
→ verae.access.authz.check (allow / deny)
|
||||
→ verae.<area>.<resource>.<action> (internal bus)
|
||||
```
|
||||
|
||||
Internal subjects (`verae.billing.*`, `verae.archive.*`, `verae.zapier.jobs.*`) stay stable. Planes never publish them until `authz.check` returns `allow`.
|
||||
|
||||
| Plane | Who | May reach |
|
||||
|-------|-----|-----------|
|
||||
| `zapier` | Zapier Platform HTTPS | jobs.*, webhooks.deliver, billing.usage.recorded |
|
||||
| `web` | Customer browser portal | statement.get, balance.adjust kind=reload\|payment |
|
||||
| `api` | Customer `x-api-key` (not Zapier) | statement.get, usage.recorded, jobs.watch |
|
||||
| `leaf` | S2S NATS leaf / mTLS | archive.*, jobs.*, webhooks — **not billing** |
|
||||
| `staff` | CS / sales / admin | statement, balance.adjust (credits) |
|
||||
|
||||
A leaf node cannot credit an account. A Zapier hop cannot read a customer statement. A browser cannot `archive.put`.
|
||||
|
||||
```bash
|
||||
NATS_URL=nats://127.0.0.1:4222 PORT=3020 npm start
|
||||
curl -s http://127.0.0.1:3020/policy
|
||||
curl -s -X POST http://127.0.0.1:3020/check -H 'content-type: application/json' \
|
||||
-d '{"plane":"leaf","subject":"verae.billing.balance.adjust"}'
|
||||
```
|
||||
13
docs/sphinx/repos/verae-access-leaf.md
Normal file
13
docs/sphinx/repos/verae-access-leaf.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# verae-access-leaf
|
||||
|
||||
**Server-to-server leaf node** access plane. Remote machines connect as NATS leaf nodes (or POST `/forward` with a leaf token). They never get the core billing subjects.
|
||||
|
||||
**Forgejo:** https://git.georgelambert.org/marchon/verae-access-leaf
|
||||
|
||||
```text
|
||||
remote leaf → verae.access.leaf.in → authz.check → verae.archive.put | jobs.*
|
||||
```
|
||||
|
||||
Denied: `verae.billing.balance.adjust`, `verae.billing.statement.get`. A compromised WORM/leaf cannot credit customers.
|
||||
|
||||
`LEAF_TOKEN` required. Port `:3023`. Do not publish NATS `4222` to the internet; leaf nodes use a private leafnode port / tunnel.
|
||||
15
docs/sphinx/repos/verae-access-web.md
Normal file
15
docs/sphinx/repos/verae-access-web.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# verae-access-web
|
||||
|
||||
**Direct web** access plane. Customer browsers hit this process, not Zapier and not the NATS port.
|
||||
|
||||
**Forgejo:** https://git.georgelambert.org/marchon/verae-access-web
|
||||
|
||||
Every NATS hop is `verae.access.web.*` → `verae.access.authz.check` → internal `verae.billing.statement.get` (reload only, not CS credits).
|
||||
|
||||
| Route | Job |
|
||||
|-------|-----|
|
||||
| `GET /health` | `{ plane: "web" }` |
|
||||
| `GET /statement/:id` | Authz then statement |
|
||||
| `POST /reload` | Authz then `balance.adjust` kind=reload |
|
||||
|
||||
Port `:3021`.
|
||||
9
docs/sphinx/repos/verae-access-zapier.md
Normal file
9
docs/sphinx/repos/verae-access-zapier.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# verae-access-zapier
|
||||
|
||||
**Zapier is one access plane**, not the only one. This process is the Zapier HTTPS front door: timestamp/wait/hash into middleware. No customer portal, no admin, no leaf.
|
||||
|
||||
**Forgejo:** https://git.georgelambert.org/marchon/verae-access-zapier
|
||||
|
||||
Zapier cloud never opens NATS. This process may request `verae.access.authz.check` for `verae.zapier.jobs.watch` and `verae.billing.usage.recorded` only.
|
||||
|
||||
Port `:3024`.
|
||||
|
|
@ -30,7 +30,12 @@ Zapier cloud **never** connects to NATS. Internal services do: middleware worker
|
|||
| Module | Repo (Forgejo) | Runtime | Listens | Sends |
|
||||
|--------|----------------|---------|---------|-------|
|
||||
| zapier-platform-app | `verae-zapier-app` | Zapier cloud | User Zap steps | HTTPS to zappier-edge |
|
||||
| zappier-edge | `zappier-edge` | Public HTTPS :3000 | Zapier, portal, admin | HTTPS to middleware; NATS billing pub |
|
||||
| zappier-edge | `zappier-edge` | Public HTTPS :3000 | Zapier, portal, admin | HTTPS to middleware; NATS billing after authz |
|
||||
| access-authz | `verae-access-authz` | :3020 + NATS | `verae.access.authz.check` | allow/deny |
|
||||
| access-web | `verae-access-web` | :3021 | customer browser | billing statement/reload |
|
||||
| access-api | `verae-access-api` | :3022 | x-api-key | statement; HTTPS to middleware |
|
||||
| access-leaf | `verae-access-leaf` | :3023 | S2S leaf | archive/jobs only |
|
||||
| access-zapier | `verae-access-zapier` | :3024 | Zapier HTTPS | jobs.watch after authz |
|
||||
| account-balance | `zappier-account-balance` | :3010 + NATS | `verae.billing.*` | statement/adjust replies |
|
||||
| customer-service | `zappier-customer-service` | :3011 | CS staff HTTP | NATS balance.adjust / statement.get |
|
||||
| sales-pricing | `zappier-sales-pricing` | :3012 | Sales HTTP | NATS statement.get; HTTPS to edge for multipliers |
|
||||
|
|
@ -63,6 +68,11 @@ Zapier cloud **never** connects to NATS. Internal services do: middleware worker
|
|||
| `verae.billing.usage.recorded` | pub | meter events from zappier-edge |
|
||||
| `verae.billing.payment.recorded` | pub | portal reload / invoice paid |
|
||||
| `verae.billing.credit.applied` | pub | CS goodwill |
|
||||
| `verae.access.authz.check` | request-reply | `{ plane, subject, principal }` → `{ allow, reason }` |
|
||||
| `verae.access.web.billing.statement.get` | ingress | web plane only; mapped after authz |
|
||||
| `verae.access.api.*` | ingress | customer API plane |
|
||||
| `verae.access.zapier.*` | ingress | Zapier plane |
|
||||
| `verae.access.leaf.in` | ingress | S2S leaf; never billing |
|
||||
| `verae.archive.put` | JetStream | `sha256`, `tenantId`, `kind` (`publicMeta\|privateMeta\|file\|tree`), `record`, `traceId` |
|
||||
| `verae.archive.query` | pub to **all** archives | `correlationId`, `sha256`, `tenantId`, `kinds[]`, `traceId` |
|
||||
| `verae.archive.reply.<correlationId>` | replies | `archiveId`, `sha256`, `records[]`, `traceId` |
|
||||
|
|
|
|||
3
packages/docs-master/modules/verae-access-api/NATS.md
Normal file
3
packages/docs-master/modules/verae-access-api/NATS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# NATS — verae-access-api
|
||||
|
||||
Plane `api`. Statement and jobs.watch after authz. No credits, no archive.
|
||||
3
packages/docs-master/modules/verae-access-api/SUMMARY.md
Normal file
3
packages/docs-master/modules/verae-access-api/SUMMARY.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# verae-access-api
|
||||
|
||||
Direct customer API (`x-api-key`). Separate from Zapier Platform.
|
||||
3
packages/docs-master/modules/verae-access-authz/NATS.md
Normal file
3
packages/docs-master/modules/verae-access-authz/NATS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# NATS — verae-access-authz
|
||||
|
||||
`verae.access.authz.check` request-reply. Queue `access-authz`. Fail closed.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# verae-access-authz
|
||||
|
||||
Default-deny authorization step on the NATS address path. Planes: zapier, web, api, leaf, staff.
|
||||
3
packages/docs-master/modules/verae-access-leaf/NATS.md
Normal file
3
packages/docs-master/modules/verae-access-leaf/NATS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# NATS — verae-access-leaf
|
||||
|
||||
`verae.access.leaf.in` → authz.check → `verae.archive.*` / jobs.*. Denied: billing.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# verae-access-leaf
|
||||
|
||||
Server-to-server NATS leaf ingress. Archive and jobs only. Never billing.
|
||||
3
packages/docs-master/modules/verae-access-web/NATS.md
Normal file
3
packages/docs-master/modules/verae-access-web/NATS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# NATS — verae-access-web
|
||||
|
||||
Plane `web`. Authz then `verae.billing.statement.get` / `balance.adjust` kind=reload.
|
||||
3
packages/docs-master/modules/verae-access-web/SUMMARY.md
Normal file
3
packages/docs-master/modules/verae-access-web/SUMMARY.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# verae-access-web
|
||||
|
||||
Direct customer web plane. Not Zapier. Statement + reload only after authz.
|
||||
3
packages/docs-master/modules/verae-access-zapier/NATS.md
Normal file
3
packages/docs-master/modules/verae-access-zapier/NATS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# NATS — verae-access-zapier
|
||||
|
||||
Plane `zapier`. Authz then jobs.watch / usage.recorded. Zapier cloud never connects to NATS.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# verae-access-zapier
|
||||
|
||||
Zapier Platform HTTPS plane only. No portal, no admin, no leaf.
|
||||
|
|
@ -10,8 +10,12 @@ Verae Time proves a SHA-256 existed at a given time. Zapier lets customers regis
|
|||
|
||||
| Plane | What | Network |
|
||||
|-------|------|---------|
|
||||
| Zapier cloud | `verae-zapier-app`, `verae-activate` | HTTPS to zappier-edge |
|
||||
| Commercial edge | `zappier-edge` portal, admin, `x-api-key`, Stripe meter | Public HTTPS; NATS billing pubs/requests |
|
||||
| Zapier cloud | `verae-zapier-app` via **access-zapier** | HTTPS to `:3024` only (not NATS) |
|
||||
| Direct web | `verae-access-web` customer browser | HTTPS `:3021` |
|
||||
| Direct customer API | `verae-access-api` `x-api-key` | HTTPS `:3022` |
|
||||
| S2S leaf | `verae-access-leaf` | NATS `verae.access.leaf.in` / HTTPS `:3023` |
|
||||
| Authz | `verae-access-authz` | `verae.access.authz.check` before any internal subject |
|
||||
| Commercial edge | `zappier-edge` portal, admin, Stripe (legacy combined) | Public HTTPS; NATS billing after authz |
|
||||
| Account balance | `zappier-account-balance` prepaid SoT | NATS `verae.billing.*` + HTTP `:3010` |
|
||||
| CS / sales / accounting | `zappier-customer-service`, `zappier-sales-pricing`, `zappier-accounting-export` | Private HTTP; NATS statement/adjust |
|
||||
| Middleware HTTP | `/zapier/v1/*` job id + wait | Public HTTPS from edge only |
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@ Each runtime piece is its **own git repo** on Forgejo (`git.georgelambert.org`,
|
|||
| **verae-nats-process** | `packages/verae-nats-process` | **Template** for a new addressed NATS process |
|
||||
| **master-zapier-plan-draft** | workspace root | Combined snapshot (`main` and `master`) |
|
||||
| **zappier-edge** | `packages/zappier` | Metered HTTPS, portal, admin, Stripe; **proxies Verae calls to middleware** |
|
||||
| **verae-access-authz** | `packages/verae-access-authz` | NATS authorization step (`authz.check`) |
|
||||
| **verae-access-web** | `packages/verae-access-web` | Direct customer web plane |
|
||||
| **verae-access-api** | `packages/verae-access-api` | Direct customer API plane (not Zapier) |
|
||||
| **verae-access-leaf** | `packages/verae-access-leaf` | Server-to-server NATS leaf plane |
|
||||
| **verae-access-zapier** | `packages/verae-access-zapier` | Zapier Platform plane only |
|
||||
| **zappier-account-balance** | `packages/zappier-account-balance` | NATS source of truth for prepaid balances |
|
||||
| **zappier-customer-service** | `packages/zappier-customer-service` | CS goodwill credits onto prepaid balances |
|
||||
| **zappier-sales-pricing** | `packages/zappier-sales-pricing` | Sales per-customer tier / multiplier |
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ Today’s NS1 box (`NS1.GEORGELAMBERT.ORG`, `70.88.205.138`) already runs JetStr
|
|||
|
||||
| Allowed | Forbidden |
|
||||
|---------|-----------|
|
||||
| verae-middleware, fleet workers, WORM, tree nodes, account-balance, zappier-edge billing, CS/sales/accounting, `verae-nats-process` clones | Zapier cloud, customer browsers (they use HTTPS portal/CS/sales UIs) |
|
||||
| access-authz, access-leaf (after token), middleware, fleet workers, WORM, tree, account-balance, access planes, CS/sales | Zapier cloud, customer browsers (HTTPS to access-web / access-api / access-zapier only) |
|
||||
|
||||
## Address families already in use
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,14 @@ NATS **addresses** (subjects) are the extension point. A new search, store, or j
|
|||
## Pattern
|
||||
|
||||
```text
|
||||
verae.<area>.<resource>.<action>
|
||||
verae.access.<plane>.<area>.<resource>.<action> # ingress (one plane)
|
||||
verae.access.authz.check # authorization step
|
||||
verae.<area>.<resource>.<action> # internal bus
|
||||
verae.<area>.<resource>.reply.<correlationId>
|
||||
```
|
||||
|
||||
Planes: `zapier` | `web` | `api` | `leaf` | `staff`. Default deny. A leaf cannot `balance.adjust`; Zapier cannot `statement.get`; a browser cannot `archive.put`.
|
||||
|
||||
| Piece | Example | Meaning |
|
||||
|-------|---------|---------|
|
||||
| `verae` | — | Verae bus (not Zapier) |
|
||||
|
|
@ -26,7 +30,7 @@ verae.<area>.<resource>.reply.<correlationId>
|
|||
2. Rename `verae.example.process.in` / `.out` / `.reply.*` in `src/subjects.js`.
|
||||
3. Add a row to that repo’s `ROUTING.md` and to [INDEX.md](INDEX.md).
|
||||
4. Register the process in `verae-fleet` (`min`/`max`, machines, roles).
|
||||
5. If Zapier must call it, add **one HTTPS route** on middleware — Zapier still never sees NATS.
|
||||
5. If an access plane must call it, add the subject to **verae-access-authz** policy for that plane only. Zapier still never sees NATS.
|
||||
|
||||
Do **not** invent a public NATS URL for Zapier. Do **not** reuse `verae.archive.query` as a queue group.
|
||||
|
||||
|
|
|
|||
|
|
@ -8,23 +8,24 @@ SVG files in [`diagrams/`](diagrams/). The same shapes are repeated below in mer
|
|||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Z[Zapier cloud apps] -->|HTTPS x-api-key| E[zappier-edge]
|
||||
Cust[customer portal] -->|HTTPS statement| E
|
||||
CS[customer-service] -->|NATS statement/adjust| N[NATS 3-node cluster]
|
||||
SA[sales-pricing] -->|NATS statement| N
|
||||
AC[accounting-export] -->|NATS statement| N
|
||||
E -->|NATS billing| N
|
||||
BAL[account-balance] -->|reply verae.billing.*| N
|
||||
E -->|HTTPS metered| M[verae-middleware]
|
||||
M -->|HTTPS| C[Verae chain]
|
||||
M -->|JetStream| N
|
||||
Z[Zapier cloud] -->|HTTPS plane=zapier| AZ[access-zapier]
|
||||
Web[customer browser] -->|HTTPS plane=web| AW[access-web]
|
||||
API[customer API] -->|HTTPS plane=api| AA[access-api]
|
||||
Leaf[S2S leaf] -->|verae.access.leaf.in| AL[access-leaf]
|
||||
AZ --> Authz[access-authz]
|
||||
AW --> Authz
|
||||
AA --> Authz
|
||||
AL --> Authz
|
||||
CS[staff CS/sales] --> Authz
|
||||
Authz -->|allow| N[NATS cluster]
|
||||
BAL[account-balance] --> N
|
||||
AZ -->|HTTPS| M[verae-middleware]
|
||||
M --> N
|
||||
N --> P[job-poller]
|
||||
N --> W[webhook-deliver]
|
||||
N --> A[archive-aggregator]
|
||||
N --> R[WORM x N]
|
||||
N --> T[tree-node x N]
|
||||
P -->|HTTPS status| C
|
||||
W -->|HTTPS REST Hook| Z
|
||||
N --> R[WORM]
|
||||
N --> T[tree-node]
|
||||
P --> C[Verae chain]
|
||||
M --> C
|
||||
```
|
||||
|
||||
## 8.2 NATS.IO 3-server cluster
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ Operator console (loopback): http://127.0.0.1:3850/ · [CONSOLE.pdf](https://zap
|
|||
|
||||
Prefix: `https://git.georgelambert.org/marchon/`
|
||||
|
||||
overview · verae-nats-process · verae-ops · master-zapier-plan-draft · zappier-edge · verae-middleware · verae-zapier-app · verae-activate · verae-request-splitter · verae-archive-worm · verae-archive-aggregator · verae-tree-node · verae-fleet · verae-zapier-simulator · zapier-user-docs · zapier-docs-master
|
||||
overview · verae-nats-process · verae-ops · master-zapier-plan-draft · zappier-edge · verae-access-authz · verae-access-web · verae-access-api · verae-access-leaf · verae-access-zapier · verae-middleware · verae-zapier-app · verae-activate · verae-request-splitter · verae-archive-worm · verae-archive-aggregator · verae-tree-node · verae-fleet · verae-zapier-simulator · zapier-user-docs · zapier-docs-master
|
||||
|
||||
## Per-module contracts
|
||||
|
||||
|
|
|
|||
3
packages/verae-access-api/NATS.md
Normal file
3
packages/verae-access-api/NATS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# NATS — verae-access-api
|
||||
|
||||
Plane `api`. Ingress `verae.access.api.*`. Authz then statement.get / usage.recorded / jobs.watch. Never archive or credits.
|
||||
11
packages/verae-access-api/README.md
Normal file
11
packages/verae-access-api/README.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# verae-access-api
|
||||
|
||||
**Direct customer API** plane (`x-api-key`). Separate from the Zapier Platform app.
|
||||
|
||||
**Forgejo:** https://git.georgelambert.org/marchon/verae-access-api
|
||||
|
||||
Address path: `verae.access.api.*` → `verae.access.authz.check` → `verae.billing.statement.get` / `verae.zapier.jobs.watch` (HTTPS to middleware, not a Zapier NATS client).
|
||||
|
||||
Cannot `balance.adjust` or `archive.put`.
|
||||
|
||||
Port `:3022`.
|
||||
11
packages/verae-access-api/package.json
Normal file
11
packages/verae-access-api/package.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "verae-access-api",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Direct customer API access plane (x-api-key, not Zapier)",
|
||||
"scripts": {
|
||||
"start": "node src/server.js",
|
||||
"test": "node --test test/*.test.js"
|
||||
}
|
||||
}
|
||||
59
packages/verae-access-api/src/server.js
Normal file
59
packages/verae-access-api/src/server.js
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env node
|
||||
/** Direct customer API (x-api-key). Not Zapier Platform. */
|
||||
import http from 'node:http';
|
||||
|
||||
const PORT = Number(process.env.PORT || 3022);
|
||||
const AUTHZ = (process.env.AUTHZ_URL || 'http://127.0.0.1:3020').replace(/\/$/, '');
|
||||
const BOOKS = (process.env.ACCOUNT_BALANCE_URL || 'http://127.0.0.1:3010').replace(/\/$/, '');
|
||||
const MW = (process.env.ZAPPIER_UPSTREAM || 'http://127.0.0.1:3100').replace(/\/$/, '');
|
||||
const PLANE = 'api';
|
||||
|
||||
async function check(subject, principal, extra = {}) {
|
||||
const r = await fetch(`${AUTHZ}/check`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ plane: PLANE, subject, principal, ...extra }),
|
||||
});
|
||||
return r.json();
|
||||
}
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url || '/', `http://127.0.0.1:${PORT}`);
|
||||
const json = (code, obj) => {
|
||||
res.writeHead(code, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify(obj));
|
||||
};
|
||||
try {
|
||||
if (req.method === 'GET' && url.pathname === '/health') {
|
||||
return json(200, { ok: true, role: 'verae-access-api', plane: PLANE });
|
||||
}
|
||||
const key = req.headers['x-api-key'];
|
||||
const principal = typeof key === 'string' ? key : 'anonymous';
|
||||
const st = url.pathname.match(/^\/v1\/statement\/([^/]+)$/);
|
||||
if (req.method === 'GET' && st) {
|
||||
const gate = await check('verae.billing.statement.get', principal);
|
||||
if (!gate.allow) return json(403, gate);
|
||||
const r = await fetch(`${BOOKS}/statement/${st[1]}`);
|
||||
return json(r.status, { ...(await r.json()), plane: PLANE, source: 'account-balance' });
|
||||
}
|
||||
if (req.method === 'POST' && url.pathname === '/v1/timestamp') {
|
||||
const gate = await check('verae.zapier.jobs.watch', principal);
|
||||
if (!gate.allow) return json(403, gate);
|
||||
const chunks = [];
|
||||
for await (const c of req) chunks.push(c);
|
||||
const r = await fetch(`${MW}/zapier/v1/timestamp`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json', 'x-api-key': principal },
|
||||
body: Buffer.concat(chunks),
|
||||
});
|
||||
return json(r.status, { ...(await r.json().catch(() => ({}))), plane: PLANE });
|
||||
}
|
||||
json(404, { error: 'not found' });
|
||||
} catch (err) {
|
||||
json(502, { error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(PORT, '0.0.0.0', () => {
|
||||
process.stdout.write(`verae-access-api http://0.0.0.0:${PORT}/ plane=${PLANE}\n`);
|
||||
});
|
||||
37
packages/verae-access-api/test/health.test.js
Normal file
37
packages/verae-access-api/test/health.test.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawn } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const authzRoot = path.join(root, '..', 'verae-access-authz');
|
||||
|
||||
test('api plane health and authz deny on credits', async () => {
|
||||
const authzPort = 18024;
|
||||
const apiPort = 18025;
|
||||
const authz = spawn(process.execPath, ['src/server.js'], {
|
||||
cwd: authzRoot,
|
||||
env: { ...process.env, PORT: String(authzPort) },
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
const api = spawn(process.execPath, ['src/server.js'], {
|
||||
cwd: root,
|
||||
env: { ...process.env, PORT: String(apiPort), AUTHZ_URL: `http://127.0.0.1:${authzPort}` },
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
try {
|
||||
const h = await (await fetch(`http://127.0.0.1:${apiPort}/health`)).json();
|
||||
assert.equal(h.plane, 'api');
|
||||
const deny = await fetch(`http://127.0.0.1:${authzPort}/check`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ plane: 'api', subject: 'verae.billing.balance.adjust' }),
|
||||
});
|
||||
assert.equal(deny.status, 403);
|
||||
} finally {
|
||||
api.kill('SIGTERM');
|
||||
authz.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
11
packages/verae-access-authz/NATS.md
Normal file
11
packages/verae-access-authz/NATS.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# NATS — verae-access-authz
|
||||
|
||||
| Direction | Address | Kind |
|
||||
|-----------|---------|------|
|
||||
| IN | `verae.access.authz.check` | request-reply, queue `access-authz` |
|
||||
| OUT | `verae.access.authz.deny` | pub on deny (audit) |
|
||||
|
||||
Body in: `{ plane, subject, principal, kind, payload }`.
|
||||
Body out: `{ allow, plane, subject, reason }`.
|
||||
|
||||
Fail closed: if this process is down, access gateways must not forward.
|
||||
34
packages/verae-access-authz/README.md
Normal file
34
packages/verae-access-authz/README.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# verae-access-authz
|
||||
|
||||
Authorization step on the NATS **address path**. Zapier is only **one** access plane. Direct web, customer API, and server-to-server leaf nodes are separate, and each is default-deny against internal subjects.
|
||||
|
||||
**Forgejo:** https://git.georgelambert.org/marchon/verae-access-authz
|
||||
**Catalog:** https://zapier.georgelambert.org/packages/verae-access-authz/README.pdf
|
||||
|
||||
## Address path
|
||||
|
||||
```text
|
||||
client
|
||||
→ verae.access.<plane>.<area>.<resource>.<action> (ingress, that plane only)
|
||||
→ verae.access.authz.check (allow / deny)
|
||||
→ verae.<area>.<resource>.<action> (internal bus)
|
||||
```
|
||||
|
||||
Internal subjects (`verae.billing.*`, `verae.archive.*`, `verae.zapier.jobs.*`) stay stable. Planes never publish them until `authz.check` returns `allow`.
|
||||
|
||||
| Plane | Who | May reach |
|
||||
|-------|-----|-----------|
|
||||
| `zapier` | Zapier Platform HTTPS | jobs.*, webhooks.deliver, billing.usage.recorded |
|
||||
| `web` | Customer browser portal | statement.get, balance.adjust kind=reload\|payment |
|
||||
| `api` | Customer `x-api-key` (not Zapier) | statement.get, usage.recorded, jobs.watch |
|
||||
| `leaf` | S2S NATS leaf / mTLS | archive.*, jobs.*, webhooks — **not billing** |
|
||||
| `staff` | CS / sales / admin | statement, balance.adjust (credits) |
|
||||
|
||||
A leaf node cannot credit an account. A Zapier hop cannot read a customer statement. A browser cannot `archive.put`.
|
||||
|
||||
```bash
|
||||
NATS_URL=nats://127.0.0.1:4222 PORT=3020 npm start
|
||||
curl -s http://127.0.0.1:3020/policy
|
||||
curl -s -X POST http://127.0.0.1:3020/check -H 'content-type: application/json' \
|
||||
-d '{"plane":"leaf","subject":"verae.billing.balance.adjust"}'
|
||||
```
|
||||
46
packages/verae-access-authz/package-lock.json
generated
Normal file
46
packages/verae-access-authz/package-lock.json
generated
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"name": "verae-access-authz",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "verae-access-authz",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"nats": "^2.28.2"
|
||||
}
|
||||
},
|
||||
"node_modules/nats": {
|
||||
"version": "2.29.3",
|
||||
"resolved": "https://registry.npmjs.org/nats/-/nats-2.29.3.tgz",
|
||||
"integrity": "sha512-tOQCRCwC74DgBTk4pWZ9V45sk4d7peoE2njVprMRCBXrhJ5q5cYM7i6W+Uvw2qUrcfOSnuisrX7bEx3b3Wx4QA==",
|
||||
"deprecated": "Package moved. Use @nats-io/transport-node from https://github.com/nats-io/nats.js",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"nkeys.js": "1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nkeys.js": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/nkeys.js/-/nkeys.js-1.1.0.tgz",
|
||||
"integrity": "sha512-tB/a0shZL5UZWSwsoeyqfTszONTt4k2YS0tuQioMOD180+MbombYVgzDUYHlx+gejYK6rgf08n/2Df99WY0Sxg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tweetnacl": "1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
||||
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
|
||||
"license": "Unlicense"
|
||||
}
|
||||
}
|
||||
}
|
||||
14
packages/verae-access-authz/package.json
Normal file
14
packages/verae-access-authz/package.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "verae-access-authz",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "NATS authorization step for access planes (web, api, zapier, leaf, staff)",
|
||||
"scripts": {
|
||||
"start": "node src/server.js",
|
||||
"test": "node --test test/*.test.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"nats": "^2.28.2"
|
||||
}
|
||||
}
|
||||
38
packages/verae-access-authz/src/client.js
Normal file
38
packages/verae-access-authz/src/client.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/** Call verae.access.authz.check then the internal subject. Fail closed. */
|
||||
import { SUBJECTS } from './subjects.js';
|
||||
|
||||
export async function authorizeThenRequest(nc, sc, { plane, subject, principal, payload, timeout = 2000 }) {
|
||||
const kind = payload?.kind;
|
||||
const checkMsg = await nc.request(
|
||||
SUBJECTS.AUTHZ_CHECK,
|
||||
sc.encode(JSON.stringify({ plane, subject, principal, kind, payload })),
|
||||
{ timeout },
|
||||
);
|
||||
const decision = JSON.parse(sc.decode(checkMsg.data) || '{}');
|
||||
if (!decision.allow) {
|
||||
const err = new Error(decision.reason || 'denied');
|
||||
err.decision = decision;
|
||||
throw err;
|
||||
}
|
||||
const internal = decision.subject;
|
||||
const body = { ...payload, plane, principal, traceId: payload?.traceId };
|
||||
const m = await nc.request(internal, sc.encode(JSON.stringify(body)), { timeout });
|
||||
return { decision, body: JSON.parse(sc.decode(m.data) || '{}') };
|
||||
}
|
||||
|
||||
export async function authorizeThenPublish(nc, sc, { plane, subject, principal, payload, timeout = 2000 }) {
|
||||
const kind = payload?.kind;
|
||||
const checkMsg = await nc.request(
|
||||
SUBJECTS.AUTHZ_CHECK,
|
||||
sc.encode(JSON.stringify({ plane, subject, principal, kind, payload })),
|
||||
{ timeout },
|
||||
);
|
||||
const decision = JSON.parse(sc.decode(checkMsg.data) || '{}');
|
||||
if (!decision.allow) {
|
||||
const err = new Error(decision.reason || 'denied');
|
||||
err.decision = decision;
|
||||
throw err;
|
||||
}
|
||||
nc.publish(decision.subject, sc.encode(JSON.stringify({ ...payload, plane, principal })));
|
||||
return { decision };
|
||||
}
|
||||
98
packages/verae-access-authz/src/policy.js
Normal file
98
packages/verae-access-authz/src/policy.js
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import { PLANES, parseAddress } from './subjects.js';
|
||||
|
||||
/**
|
||||
* Default-deny. Each access plane may only touch listed internal prefixes.
|
||||
* Internal workers (poller, WORM, account-balance) are not planes; they
|
||||
* already sit on the private bus. This policy gates *ingress*.
|
||||
*/
|
||||
export const POLICY = Object.freeze({
|
||||
zapier: {
|
||||
title: 'Zapier Platform (HTTPS only; never a NATS client)',
|
||||
allow: [
|
||||
'verae.zapier.jobs.watch',
|
||||
'verae.zapier.jobs.events',
|
||||
'verae.zapier.webhooks.deliver',
|
||||
'verae.billing.usage.recorded',
|
||||
],
|
||||
},
|
||||
web: {
|
||||
title: 'Direct customer web (portal browser)',
|
||||
allow: [
|
||||
'verae.billing.statement.get',
|
||||
'verae.billing.balance.adjust',
|
||||
'verae.billing.payment.recorded',
|
||||
],
|
||||
adjustKinds: ['payment', 'reload'],
|
||||
},
|
||||
api: {
|
||||
title: 'Direct customer API (x-api-key, not Zapier)',
|
||||
allow: ['verae.billing.statement.get', 'verae.billing.usage.recorded', 'verae.zapier.jobs.watch'],
|
||||
},
|
||||
leaf: {
|
||||
title: 'Server-to-server NATS leaf / mTLS',
|
||||
allow: [
|
||||
'verae.archive.put',
|
||||
'verae.archive.query',
|
||||
'verae.archive.reply.',
|
||||
'verae.zapier.jobs.watch',
|
||||
'verae.zapier.jobs.events',
|
||||
'verae.zapier.webhooks.deliver',
|
||||
],
|
||||
},
|
||||
staff: {
|
||||
title: 'CS / sales / admin / accounting web',
|
||||
allow: [
|
||||
'verae.billing.statement.get',
|
||||
'verae.billing.balance.get',
|
||||
'verae.billing.balance.adjust',
|
||||
'verae.billing.credit.applied',
|
||||
'verae.billing.payment.recorded',
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
function prefixAllowed(allow, subject) {
|
||||
return allow.some((p) => (p.endsWith('.') ? subject.startsWith(p) : subject === p || subject.startsWith(`${p}.`)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ plane: string, subject: string, kind?: string, principal?: string }} req
|
||||
*/
|
||||
export function authorize(req) {
|
||||
const plane = String(req?.plane || '');
|
||||
const parsed = parseAddress(req?.subject, plane);
|
||||
if (parsed.authz) {
|
||||
return deny(plane, req?.subject, 'authz subjects are not forwardable');
|
||||
}
|
||||
if (!PLANES.includes(plane)) {
|
||||
return deny(plane, parsed.internal, `unknown access plane`);
|
||||
}
|
||||
if (parsed.plane && parsed.plane !== plane) {
|
||||
return deny(plane, parsed.internal, `plane mismatch (address is ${parsed.plane})`);
|
||||
}
|
||||
const internal = parsed.internal;
|
||||
if (!internal.startsWith('verae.') || internal.startsWith('verae.access.')) {
|
||||
return deny(plane, internal, 'not an internal verae.* subject');
|
||||
}
|
||||
const rule = POLICY[plane];
|
||||
if (!prefixAllowed(rule.allow, internal)) {
|
||||
return deny(plane, internal, `${plane} cannot reach ${internal}`);
|
||||
}
|
||||
if (internal === 'verae.billing.balance.adjust' && rule.adjustKinds) {
|
||||
const kind = req?.kind || req?.payload?.kind;
|
||||
if (kind && !rule.adjustKinds.includes(kind)) {
|
||||
return deny(plane, internal, `${plane} cannot adjust kind=${kind}`);
|
||||
}
|
||||
}
|
||||
return {
|
||||
allow: true,
|
||||
plane,
|
||||
subject: internal,
|
||||
principal: req?.principal || null,
|
||||
reason: 'ok',
|
||||
};
|
||||
}
|
||||
|
||||
function deny(plane, subject, reason) {
|
||||
return { allow: false, plane: plane || null, subject: subject || null, reason };
|
||||
}
|
||||
83
packages/verae-access-authz/src/server.js
Normal file
83
packages/verae-access-authz/src/server.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#!/usr/bin/env node
|
||||
import http from 'node:http';
|
||||
import { authorize, POLICY } from './policy.js';
|
||||
import { PLANES, SUBJECTS } from './subjects.js';
|
||||
|
||||
const PORT = Number(process.env.PORT || process.env.FLEET_HEALTH_PORT || 3020);
|
||||
const BIND = process.env.FLEET_HEALTH_BIND || '0.0.0.0';
|
||||
let natsOk = false;
|
||||
|
||||
async function startNats() {
|
||||
const url = process.env.NATS_URL;
|
||||
if (!url) return;
|
||||
const { connect, StringCodec } = await import('nats');
|
||||
const nc = await connect({ servers: url.split(','), name: 'verae-access-authz' });
|
||||
const sc = StringCodec();
|
||||
const sub = nc.subscribe(SUBJECTS.AUTHZ_CHECK, { queue: SUBJECTS.QUEUE });
|
||||
(async () => {
|
||||
for await (const m of sub) {
|
||||
let payload = {};
|
||||
try {
|
||||
payload = JSON.parse(sc.decode(m.data) || '{}');
|
||||
} catch {
|
||||
payload = {};
|
||||
}
|
||||
const out = authorize(payload);
|
||||
if (!out.allow && m.reply) {
|
||||
nc.publish(SUBJECTS.AUTHZ_DENY, sc.encode(JSON.stringify({ ...out, at: new Date().toISOString() })));
|
||||
}
|
||||
if (m.reply) m.respond(sc.encode(JSON.stringify(out)));
|
||||
}
|
||||
})();
|
||||
natsOk = true;
|
||||
process.stdout.write(`access-authz nats ${url}\n`);
|
||||
}
|
||||
|
||||
function readBody(req) {
|
||||
return new Promise((resolve) => {
|
||||
const chunks = [];
|
||||
req.on('data', (c) => chunks.push(c));
|
||||
req.on('end', () => {
|
||||
try {
|
||||
resolve(JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}'));
|
||||
} catch {
|
||||
resolve({});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url || '/', `http://127.0.0.1:${PORT}`);
|
||||
const json = (code, obj) => {
|
||||
res.writeHead(code, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify(obj));
|
||||
};
|
||||
try {
|
||||
if (req.method === 'GET' && url.pathname === '/health') {
|
||||
return json(200, {
|
||||
ok: true,
|
||||
role: 'verae-access-authz',
|
||||
nats: natsOk,
|
||||
planes: PLANES,
|
||||
subject: SUBJECTS.AUTHZ_CHECK,
|
||||
});
|
||||
}
|
||||
if (req.method === 'GET' && url.pathname === '/policy') {
|
||||
return json(200, { planes: PLANES, policy: POLICY, subjects: SUBJECTS });
|
||||
}
|
||||
if (req.method === 'POST' && url.pathname === '/check') {
|
||||
const body = await readBody(req);
|
||||
const out = authorize(body);
|
||||
return json(out.allow ? 200 : 403, out);
|
||||
}
|
||||
json(404, { error: 'not found' });
|
||||
} catch (err) {
|
||||
json(500, { error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(PORT, BIND, () => {
|
||||
process.stdout.write(`verae-access-authz http://${BIND}:${PORT}/\n`);
|
||||
});
|
||||
startNats().catch((err) => process.stderr.write(`nats optional: ${err.message}\n`));
|
||||
36
packages/verae-access-authz/src/subjects.js
Normal file
36
packages/verae-access-authz/src/subjects.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Access planes sit in front of internal verae.<area>.* addresses.
|
||||
* Pattern:
|
||||
* verae.access.<plane>.<area>.<resource>.<action> ingress (that plane only)
|
||||
* verae.access.authz.check authorization step
|
||||
* verae.<area>.<resource>.<action> internal (unchanged)
|
||||
*/
|
||||
export const PLANES = Object.freeze(['zapier', 'web', 'api', 'leaf', 'staff']);
|
||||
|
||||
export const SUBJECTS = Object.freeze({
|
||||
AUTHZ_CHECK: 'verae.access.authz.check',
|
||||
AUTHZ_DENY: 'verae.access.authz.deny',
|
||||
QUEUE: 'access-authz',
|
||||
ingress: (plane) => `verae.access.${plane}.in`,
|
||||
});
|
||||
|
||||
/** verae.billing.statement.get + plane web → verae.access.web.billing.statement.get */
|
||||
export function accessAddress(plane, internalSubject) {
|
||||
if (!PLANES.includes(plane)) throw new Error(`unknown plane ${plane}`);
|
||||
if (!internalSubject.startsWith('verae.') || internalSubject.startsWith('verae.access.')) {
|
||||
throw new Error(`not an internal verae.* subject: ${internalSubject}`);
|
||||
}
|
||||
return `verae.access.${plane}.${internalSubject.slice('verae.'.length)}`;
|
||||
}
|
||||
|
||||
/** Parse an access or internal subject into { plane, internal }. */
|
||||
export function parseAddress(subject, planeHint) {
|
||||
const s = String(subject || '');
|
||||
const m = s.match(/^verae\.access\.([a-z]+)\.(.+)$/);
|
||||
if (m) {
|
||||
const plane = m[1];
|
||||
if (plane === 'authz') return { plane: planeHint || null, internal: s, authz: true };
|
||||
return { plane, internal: `verae.${m[2]}`, authz: false };
|
||||
}
|
||||
return { plane: planeHint || null, internal: s, authz: false };
|
||||
}
|
||||
35
packages/verae-access-authz/test/health.test.js
Normal file
35
packages/verae-access-authz/test/health.test.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawn } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
|
||||
test('authz health policy and check', async () => {
|
||||
const port = 18020;
|
||||
const child = spawn(process.execPath, ['src/server.js'], {
|
||||
cwd: root,
|
||||
env: { ...process.env, PORT: String(port) },
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 400));
|
||||
try {
|
||||
const h = await (await fetch(`http://127.0.0.1:${port}/health`)).json();
|
||||
assert.equal(h.role, 'verae-access-authz');
|
||||
const ok = await fetch(`http://127.0.0.1:${port}/check`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ plane: 'web', subject: 'verae.billing.statement.get' }),
|
||||
});
|
||||
assert.equal(ok.status, 200);
|
||||
const no = await fetch(`http://127.0.0.1:${port}/check`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ plane: 'leaf', subject: 'verae.billing.balance.adjust' }),
|
||||
});
|
||||
assert.equal(no.status, 403);
|
||||
} finally {
|
||||
child.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
66
packages/verae-access-authz/test/policy.test.js
Normal file
66
packages/verae-access-authz/test/policy.test.js
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { authorize } from '../src/policy.js';
|
||||
import { accessAddress, parseAddress, PLANES } from '../src/subjects.js';
|
||||
|
||||
test('five access planes', () => {
|
||||
assert.deepEqual([...PLANES], ['zapier', 'web', 'api', 'leaf', 'staff']);
|
||||
});
|
||||
|
||||
test('web may read statement and reload, not archive or jobs', () => {
|
||||
assert.equal(authorize({ plane: 'web', subject: 'verae.billing.statement.get' }).allow, true);
|
||||
assert.equal(
|
||||
authorize({ plane: 'web', subject: 'verae.billing.balance.adjust', kind: 'reload' }).allow,
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
authorize({ plane: 'web', subject: 'verae.billing.balance.adjust', kind: 'credit' }).allow,
|
||||
false,
|
||||
);
|
||||
assert.equal(authorize({ plane: 'web', subject: 'verae.archive.put' }).allow, false);
|
||||
assert.equal(authorize({ plane: 'web', subject: 'verae.zapier.jobs.watch' }).allow, false);
|
||||
});
|
||||
|
||||
test('leaf may archive and jobs, not billing adjust', () => {
|
||||
assert.equal(authorize({ plane: 'leaf', subject: 'verae.archive.put' }).allow, true);
|
||||
assert.equal(authorize({ plane: 'leaf', subject: 'verae.archive.reply.abc' }).allow, true);
|
||||
assert.equal(authorize({ plane: 'leaf', subject: 'verae.zapier.jobs.watch' }).allow, true);
|
||||
assert.equal(authorize({ plane: 'leaf', subject: 'verae.billing.balance.adjust' }).allow, false);
|
||||
assert.equal(authorize({ plane: 'leaf', subject: 'verae.billing.statement.get' }).allow, false);
|
||||
});
|
||||
|
||||
test('zapier may meter and jobs, not customer statement or credits', () => {
|
||||
assert.equal(authorize({ plane: 'zapier', subject: 'verae.zapier.jobs.watch' }).allow, true);
|
||||
assert.equal(authorize({ plane: 'zapier', subject: 'verae.billing.usage.recorded' }).allow, true);
|
||||
assert.equal(authorize({ plane: 'zapier', subject: 'verae.billing.statement.get' }).allow, false);
|
||||
assert.equal(authorize({ plane: 'zapier', subject: 'verae.billing.balance.adjust' }).allow, false);
|
||||
assert.equal(authorize({ plane: 'zapier', subject: 'verae.archive.put' }).allow, false);
|
||||
});
|
||||
|
||||
test('api may statement and usage, not credits or archive', () => {
|
||||
assert.equal(authorize({ plane: 'api', subject: 'verae.billing.statement.get' }).allow, true);
|
||||
assert.equal(authorize({ plane: 'api', subject: 'verae.billing.usage.recorded' }).allow, true);
|
||||
assert.equal(authorize({ plane: 'api', subject: 'verae.billing.balance.adjust' }).allow, false);
|
||||
assert.equal(authorize({ plane: 'api', subject: 'verae.archive.query' }).allow, false);
|
||||
});
|
||||
|
||||
test('staff may credit and statement, not archive', () => {
|
||||
assert.equal(authorize({ plane: 'staff', subject: 'verae.billing.balance.adjust', kind: 'credit' }).allow, true);
|
||||
assert.equal(authorize({ plane: 'staff', subject: 'verae.billing.statement.get' }).allow, true);
|
||||
assert.equal(authorize({ plane: 'staff', subject: 'verae.archive.put' }).allow, false);
|
||||
});
|
||||
|
||||
test('access-prefixed address is mapped to internal', () => {
|
||||
const addr = accessAddress('leaf', 'verae.archive.put');
|
||||
assert.equal(addr, 'verae.access.leaf.archive.put');
|
||||
const parsed = parseAddress(addr);
|
||||
assert.equal(parsed.plane, 'leaf');
|
||||
assert.equal(parsed.internal, 'verae.archive.put');
|
||||
assert.equal(authorize({ plane: 'leaf', subject: addr }).allow, true);
|
||||
assert.equal(authorize({ plane: 'web', subject: addr }).allow, false);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
9
packages/verae-access-leaf/NATS.md
Normal file
9
packages/verae-access-leaf/NATS.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# NATS — verae-access-leaf
|
||||
|
||||
| Direction | Address | Kind |
|
||||
|-----------|---------|------|
|
||||
| IN | `verae.access.leaf.in` | queue `access-leaf` `{ token, target, payload, principal }` |
|
||||
| OUT | `verae.access.authz.check` | request-reply |
|
||||
| OUT | allowed `target` only | pub after allow |
|
||||
|
||||
Do not export `verae.billing.*` to the leaf account.
|
||||
13
packages/verae-access-leaf/README.md
Normal file
13
packages/verae-access-leaf/README.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# verae-access-leaf
|
||||
|
||||
**Server-to-server leaf node** access plane. Remote machines connect as NATS leaf nodes (or POST `/forward` with a leaf token). They never get the core billing subjects.
|
||||
|
||||
**Forgejo:** https://git.georgelambert.org/marchon/verae-access-leaf
|
||||
|
||||
```text
|
||||
remote leaf → verae.access.leaf.in → authz.check → verae.archive.put | jobs.*
|
||||
```
|
||||
|
||||
Denied: `verae.billing.balance.adjust`, `verae.billing.statement.get`. A compromised WORM/leaf cannot credit customers.
|
||||
|
||||
`LEAF_TOKEN` required. Port `:3023`. Do not publish NATS `4222` to the internet; leaf nodes use a private leafnode port / tunnel.
|
||||
46
packages/verae-access-leaf/package-lock.json
generated
Normal file
46
packages/verae-access-leaf/package-lock.json
generated
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"name": "verae-access-leaf",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "verae-access-leaf",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"nats": "^2.28.2"
|
||||
}
|
||||
},
|
||||
"node_modules/nats": {
|
||||
"version": "2.29.3",
|
||||
"resolved": "https://registry.npmjs.org/nats/-/nats-2.29.3.tgz",
|
||||
"integrity": "sha512-tOQCRCwC74DgBTk4pWZ9V45sk4d7peoE2njVprMRCBXrhJ5q5cYM7i6W+Uvw2qUrcfOSnuisrX7bEx3b3Wx4QA==",
|
||||
"deprecated": "Package moved. Use @nats-io/transport-node from https://github.com/nats-io/nats.js",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"nkeys.js": "1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nkeys.js": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/nkeys.js/-/nkeys.js-1.1.0.tgz",
|
||||
"integrity": "sha512-tB/a0shZL5UZWSwsoeyqfTszONTt4k2YS0tuQioMOD180+MbombYVgzDUYHlx+gejYK6rgf08n/2Df99WY0Sxg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tweetnacl": "1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
||||
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
|
||||
"license": "Unlicense"
|
||||
}
|
||||
}
|
||||
}
|
||||
14
packages/verae-access-leaf/package.json
Normal file
14
packages/verae-access-leaf/package.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "verae-access-leaf",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Server-to-server NATS leaf-node access plane",
|
||||
"scripts": {
|
||||
"start": "node src/server.js",
|
||||
"test": "node --test test/*.test.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"nats": "^2.28.2"
|
||||
}
|
||||
}
|
||||
94
packages/verae-access-leaf/src/server.js
Normal file
94
packages/verae-access-leaf/src/server.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Server-to-server / NATS leaf-node ingress.
|
||||
* Remote servers publish verae.access.leaf.in; this process authz-checks
|
||||
* then forwards only allowed internal subjects (archive + jobs, never billing).
|
||||
*/
|
||||
import http from 'node:http';
|
||||
|
||||
const PORT = Number(process.env.PORT || 3023);
|
||||
const AUTHZ = (process.env.AUTHZ_URL || 'http://127.0.0.1:3020').replace(/\/$/, '');
|
||||
const TOKEN = process.env.LEAF_TOKEN || 'leaf-dev-token';
|
||||
const PLANE = 'leaf';
|
||||
const IN = 'verae.access.leaf.in';
|
||||
|
||||
async function check(subject, principal) {
|
||||
const r = await fetch(`${AUTHZ}/check`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ plane: PLANE, subject, principal }),
|
||||
});
|
||||
return r.json();
|
||||
}
|
||||
|
||||
function authorizedToken(got) {
|
||||
return got && got === TOKEN;
|
||||
}
|
||||
|
||||
async function forward(body) {
|
||||
if (!authorizedToken(body.token)) {
|
||||
return { status: 401, body: { allow: false, reason: 'bad leaf token' } };
|
||||
}
|
||||
const target = body.target || body.subject;
|
||||
const gate = await check(target, body.principal || 'leaf');
|
||||
if (!gate.allow) return { status: 403, body: gate };
|
||||
const url = process.env.NATS_URL;
|
||||
if (url) {
|
||||
const { connect, StringCodec } = await import('nats');
|
||||
const nc = await connect({ servers: url.split(','), name: 'verae-access-leaf' });
|
||||
const sc = StringCodec();
|
||||
nc.publish(gate.subject, sc.encode(JSON.stringify({ ...(body.payload || {}), plane: PLANE })));
|
||||
await nc.flush();
|
||||
await nc.close();
|
||||
}
|
||||
return { status: 200, body: { forwarded: gate.subject, plane: PLANE, nats: Boolean(url) } };
|
||||
}
|
||||
|
||||
async function startNats() {
|
||||
const url = process.env.NATS_URL;
|
||||
if (!url) return;
|
||||
const { connect, StringCodec } = await import('nats');
|
||||
const nc = await connect({ servers: url.split(','), name: 'verae-access-leaf' });
|
||||
const sc = StringCodec();
|
||||
(async () => {
|
||||
for await (const m of nc.subscribe(IN, { queue: 'access-leaf' })) {
|
||||
let payload = {};
|
||||
try {
|
||||
payload = JSON.parse(sc.decode(m.data) || '{}');
|
||||
} catch {
|
||||
payload = {};
|
||||
}
|
||||
const out = await forward(payload);
|
||||
if (m.reply) m.respond(sc.encode(JSON.stringify(out.body)));
|
||||
}
|
||||
})();
|
||||
process.stdout.write(`access-leaf nats ${IN}\n`);
|
||||
}
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url || '/', `http://127.0.0.1:${PORT}`);
|
||||
const json = (code, obj) => {
|
||||
res.writeHead(code, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify(obj));
|
||||
};
|
||||
try {
|
||||
if (req.method === 'GET' && url.pathname === '/health') {
|
||||
return json(200, { ok: true, role: 'verae-access-leaf', plane: PLANE, ingress: IN });
|
||||
}
|
||||
if (req.method === 'POST' && url.pathname === '/forward') {
|
||||
const chunks = [];
|
||||
for await (const c of req) chunks.push(c);
|
||||
const body = JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}');
|
||||
const out = await forward(body);
|
||||
return json(out.status, out.body);
|
||||
}
|
||||
json(404, { error: 'not found' });
|
||||
} catch (err) {
|
||||
json(502, { error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(PORT, '0.0.0.0', () => {
|
||||
process.stdout.write(`verae-access-leaf http://0.0.0.0:${PORT}/ plane=${PLANE}\n`);
|
||||
});
|
||||
startNats().catch((err) => process.stderr.write(`nats optional: ${err.message}\n`));
|
||||
63
packages/verae-access-leaf/test/health.test.js
Normal file
63
packages/verae-access-leaf/test/health.test.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawn } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const authzRoot = path.join(root, '..', 'verae-access-authz');
|
||||
|
||||
test('leaf forwards archive.put and refuses billing adjust', async () => {
|
||||
const authzPort = 18026;
|
||||
const leafPort = 18027;
|
||||
const authz = spawn(process.execPath, ['src/server.js'], {
|
||||
cwd: authzRoot,
|
||||
env: { ...process.env, PORT: String(authzPort) },
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
const leaf = spawn(process.execPath, ['src/server.js'], {
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
PORT: String(leafPort),
|
||||
AUTHZ_URL: `http://127.0.0.1:${authzPort}`,
|
||||
LEAF_TOKEN: 'leaf-dev-token',
|
||||
},
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
try {
|
||||
const h = await (await fetch(`http://127.0.0.1:${leafPort}/health`)).json();
|
||||
assert.equal(h.plane, 'leaf');
|
||||
const ok = await fetch(`http://127.0.0.1:${leafPort}/forward`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
token: 'leaf-dev-token',
|
||||
target: 'verae.archive.put',
|
||||
principal: 'ns2',
|
||||
payload: { sha256: 'abc' },
|
||||
}),
|
||||
});
|
||||
assert.equal(ok.status, 200);
|
||||
const no = await fetch(`http://127.0.0.1:${leafPort}/forward`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
token: 'leaf-dev-token',
|
||||
target: 'verae.billing.balance.adjust',
|
||||
payload: { customerId: 'c1', cents: 9999 },
|
||||
}),
|
||||
});
|
||||
assert.equal(no.status, 403);
|
||||
const bad = await fetch(`http://127.0.0.1:${leafPort}/forward`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ token: 'wrong', target: 'verae.archive.put' }),
|
||||
});
|
||||
assert.equal(bad.status, 401);
|
||||
} finally {
|
||||
leaf.kill('SIGTERM');
|
||||
authz.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
3
packages/verae-access-web/NATS.md
Normal file
3
packages/verae-access-web/NATS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# NATS — verae-access-web
|
||||
|
||||
Plane `web`. Ingress `verae.access.web.in` / `verae.access.web.billing.*`. Must pass `verae.access.authz.check`. Internal: `verae.billing.statement.get`, `verae.billing.balance.adjust` (reload/payment only).
|
||||
15
packages/verae-access-web/README.md
Normal file
15
packages/verae-access-web/README.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# verae-access-web
|
||||
|
||||
**Direct web** access plane. Customer browsers hit this process, not Zapier and not the NATS port.
|
||||
|
||||
**Forgejo:** https://git.georgelambert.org/marchon/verae-access-web
|
||||
|
||||
Every NATS hop is `verae.access.web.*` → `verae.access.authz.check` → internal `verae.billing.statement.get` (reload only, not CS credits).
|
||||
|
||||
| Route | Job |
|
||||
|-------|-----|
|
||||
| `GET /health` | `{ plane: "web" }` |
|
||||
| `GET /statement/:id` | Authz then statement |
|
||||
| `POST /reload` | Authz then `balance.adjust` kind=reload |
|
||||
|
||||
Port `:3021`.
|
||||
46
packages/verae-access-web/package-lock.json
generated
Normal file
46
packages/verae-access-web/package-lock.json
generated
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"name": "verae-access-web",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "verae-access-web",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"nats": "^2.28.2"
|
||||
}
|
||||
},
|
||||
"node_modules/nats": {
|
||||
"version": "2.29.3",
|
||||
"resolved": "https://registry.npmjs.org/nats/-/nats-2.29.3.tgz",
|
||||
"integrity": "sha512-tOQCRCwC74DgBTk4pWZ9V45sk4d7peoE2njVprMRCBXrhJ5q5cYM7i6W+Uvw2qUrcfOSnuisrX7bEx3b3Wx4QA==",
|
||||
"deprecated": "Package moved. Use @nats-io/transport-node from https://github.com/nats-io/nats.js",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"nkeys.js": "1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nkeys.js": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/nkeys.js/-/nkeys.js-1.1.0.tgz",
|
||||
"integrity": "sha512-tB/a0shZL5UZWSwsoeyqfTszONTt4k2YS0tuQioMOD180+MbombYVgzDUYHlx+gejYK6rgf08n/2Df99WY0Sxg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tweetnacl": "1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
||||
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
|
||||
"license": "Unlicense"
|
||||
}
|
||||
}
|
||||
}
|
||||
14
packages/verae-access-web/package.json
Normal file
14
packages/verae-access-web/package.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "verae-access-web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Direct customer web access plane (not Zapier)",
|
||||
"scripts": {
|
||||
"start": "node src/server.js",
|
||||
"test": "node --test test/*.test.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"nats": "^2.28.2"
|
||||
}
|
||||
}
|
||||
50
packages/verae-access-web/src/gate.js
Normal file
50
packages/verae-access-web/src/gate.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
const AUTHZ = (process.env.AUTHZ_URL || 'http://127.0.0.1:3020').replace(/\/$/, '');
|
||||
const BOOKS = (process.env.ACCOUNT_BALANCE_URL || 'http://127.0.0.1:3010').replace(/\/$/, '');
|
||||
|
||||
export const PLANE = 'web';
|
||||
export const AUTHZ_CHECK = 'verae.access.authz.check';
|
||||
|
||||
export async function check(subject, { principal, kind, payload } = {}) {
|
||||
const r = await fetch(`${AUTHZ}/check`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ plane: PLANE, subject, principal, kind, payload }),
|
||||
});
|
||||
return { status: r.status, body: await r.json().catch(() => ({ allow: false })) };
|
||||
}
|
||||
|
||||
export async function statement(customerId, principal) {
|
||||
const gate = await check('verae.billing.statement.get', { principal });
|
||||
if (!gate.body.allow) return { status: 403, body: gate.body };
|
||||
const url = process.env.NATS_URL;
|
||||
if (url) {
|
||||
try {
|
||||
const { connect, StringCodec } = await import('nats');
|
||||
const nc = await connect({ servers: url.split(','), name: 'verae-access-web' });
|
||||
const sc = StringCodec();
|
||||
const m = await nc.request(
|
||||
'verae.billing.statement.get',
|
||||
sc.encode(JSON.stringify({ customerId, plane: PLANE, principal })),
|
||||
{ timeout: 2000 },
|
||||
);
|
||||
const out = JSON.parse(sc.decode(m.data) || '{}');
|
||||
await nc.close();
|
||||
return { status: 200, body: { ...out, source: 'nats', plane: PLANE } };
|
||||
} catch {
|
||||
/* HTTP fallback */
|
||||
}
|
||||
}
|
||||
const r = await fetch(`${BOOKS}/statement/${customerId}`);
|
||||
return { status: r.status, body: { ...(await r.json().catch(() => ({}))), source: 'account-balance', plane: PLANE } };
|
||||
}
|
||||
|
||||
export async function reload(customerId, cents, principal) {
|
||||
const gate = await check('verae.billing.balance.adjust', { principal, kind: 'reload' });
|
||||
if (!gate.body.allow) return { status: 403, body: gate.body };
|
||||
const r = await fetch(`${BOOKS}/adjust`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ customerId, cents, reason: 'reload', agent: principal || 'web', kind: 'reload' }),
|
||||
});
|
||||
return { status: r.status, body: { ...(await r.json().catch(() => ({}))), plane: PLANE } };
|
||||
}
|
||||
38
packages/verae-access-web/src/server.js
Normal file
38
packages/verae-access-web/src/server.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#!/usr/bin/env node
|
||||
/** Direct customer web access. Not Zapier. NATS only after authz. */
|
||||
import http from 'node:http';
|
||||
import { PLANE, statement, reload } from './gate.js';
|
||||
|
||||
const PORT = Number(process.env.PORT || 3021);
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url || '/', `http://127.0.0.1:${PORT}`);
|
||||
const json = (code, obj) => {
|
||||
res.writeHead(code, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify(obj));
|
||||
};
|
||||
try {
|
||||
if (req.method === 'GET' && url.pathname === '/health') {
|
||||
return json(200, { ok: true, role: 'verae-access-web', plane: PLANE });
|
||||
}
|
||||
const st = url.pathname.match(/^\/statement\/([^/]+)$/);
|
||||
if (req.method === 'GET' && st) {
|
||||
const out = await statement(st[1], st[1]);
|
||||
return json(out.status, out.body);
|
||||
}
|
||||
if (req.method === 'POST' && url.pathname === '/reload') {
|
||||
const chunks = [];
|
||||
for await (const c of req) chunks.push(c);
|
||||
const body = JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}');
|
||||
const out = await reload(body.customerId, body.cents, body.customerId);
|
||||
return json(out.status, out.body);
|
||||
}
|
||||
json(404, { error: 'not found' });
|
||||
} catch (err) {
|
||||
json(502, { error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(PORT, '0.0.0.0', () => {
|
||||
process.stdout.write(`verae-access-web http://0.0.0.0:${PORT}/ plane=${PLANE}\n`);
|
||||
});
|
||||
52
packages/verae-access-web/test/health.test.js
Normal file
52
packages/verae-access-web/test/health.test.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawn } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const authzRoot = path.join(root, '..', 'verae-access-authz');
|
||||
const booksRoot = path.join(root, '..', 'zappier-account-balance');
|
||||
|
||||
test('web plane can read statement after authz, cannot skip authz', async () => {
|
||||
const authzPort = 18021;
|
||||
const booksPort = 18022;
|
||||
const webPort = 18023;
|
||||
const authz = spawn(process.execPath, ['src/server.js'], {
|
||||
cwd: authzRoot,
|
||||
env: { ...process.env, PORT: String(authzPort) },
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
const books = spawn(process.execPath, ['src/server.js'], {
|
||||
cwd: booksRoot,
|
||||
env: { ...process.env, PORT: String(booksPort) },
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
const web = spawn(process.execPath, ['src/server.js'], {
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
PORT: String(webPort),
|
||||
AUTHZ_URL: `http://127.0.0.1:${authzPort}`,
|
||||
ACCOUNT_BALANCE_URL: `http://127.0.0.1:${booksPort}`,
|
||||
},
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 600));
|
||||
try {
|
||||
await fetch(`http://127.0.0.1:${booksPort}/adjust`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ customerId: 'c-web', cents: 400, kind: 'reload' }),
|
||||
});
|
||||
const h = await (await fetch(`http://127.0.0.1:${webPort}/health`)).json();
|
||||
assert.equal(h.plane, 'web');
|
||||
const st = await (await fetch(`http://127.0.0.1:${webPort}/statement/c-web`)).json();
|
||||
assert.equal(st.prepaidCents, 400);
|
||||
assert.equal(st.plane, 'web');
|
||||
} finally {
|
||||
web.kill('SIGTERM');
|
||||
books.kill('SIGTERM');
|
||||
authz.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
3
packages/verae-access-zapier/NATS.md
Normal file
3
packages/verae-access-zapier/NATS.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# NATS — verae-access-zapier
|
||||
|
||||
Plane `zapier`. Authz then `verae.zapier.jobs.watch` / `verae.billing.usage.recorded`. Denied: statement.get, balance.adjust, archive.*.
|
||||
9
packages/verae-access-zapier/README.md
Normal file
9
packages/verae-access-zapier/README.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# verae-access-zapier
|
||||
|
||||
**Zapier is one access plane**, not the only one. This process is the Zapier HTTPS front door: timestamp/wait/hash into middleware. No customer portal, no admin, no leaf.
|
||||
|
||||
**Forgejo:** https://git.georgelambert.org/marchon/verae-access-zapier
|
||||
|
||||
Zapier cloud never opens NATS. This process may request `verae.access.authz.check` for `verae.zapier.jobs.watch` and `verae.billing.usage.recorded` only.
|
||||
|
||||
Port `:3024`.
|
||||
11
packages/verae-access-zapier/package.json
Normal file
11
packages/verae-access-zapier/package.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "verae-access-zapier",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Zapier Platform access plane (HTTPS only, no portal)",
|
||||
"scripts": {
|
||||
"start": "node src/server.js",
|
||||
"test": "node --test test/*.test.js"
|
||||
}
|
||||
}
|
||||
55
packages/verae-access-zapier/src/server.js
Normal file
55
packages/verae-access-zapier/src/server.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Zapier Platform access plane only. No portal, no customer API keys, no leaf.
|
||||
* Zapier cloud still never connects to NATS; this process does after authz.
|
||||
*/
|
||||
import http from 'node:http';
|
||||
|
||||
const PORT = Number(process.env.PORT || 3024);
|
||||
const AUTHZ = (process.env.AUTHZ_URL || 'http://127.0.0.1:3020').replace(/\/$/, '');
|
||||
const MW = (process.env.ZAPPIER_UPSTREAM || 'http://127.0.0.1:3100').replace(/\/$/, '');
|
||||
const PLANE = 'zapier';
|
||||
|
||||
async function check(subject, principal) {
|
||||
const r = await fetch(`${AUTHZ}/check`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ plane: PLANE, subject, principal }),
|
||||
});
|
||||
return r.json();
|
||||
}
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url || '/', `http://127.0.0.1:${PORT}`);
|
||||
const json = (code, obj) => {
|
||||
res.writeHead(code, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify(obj));
|
||||
};
|
||||
try {
|
||||
if (req.method === 'GET' && url.pathname === '/health') {
|
||||
return json(200, { ok: true, role: 'verae-access-zapier', plane: PLANE });
|
||||
}
|
||||
if (url.pathname.startsWith('/portal') || url.pathname.startsWith('/admin')) {
|
||||
return json(404, { error: 'zapier plane has no portal/admin', plane: PLANE });
|
||||
}
|
||||
if (req.method === 'POST' && (url.pathname === '/v1/timestamp' || url.pathname === '/zapier/v1/timestamp')) {
|
||||
const gate = await check('verae.zapier.jobs.watch', 'zapier-app');
|
||||
if (!gate.allow) return json(403, gate);
|
||||
const chunks = [];
|
||||
for await (const c of req) chunks.push(c);
|
||||
const r = await fetch(`${MW}/zapier/v1/timestamp`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: Buffer.concat(chunks),
|
||||
});
|
||||
return json(r.status, { ...(await r.json().catch(() => ({}))), plane: PLANE });
|
||||
}
|
||||
json(404, { error: 'not found', plane: PLANE });
|
||||
} catch (err) {
|
||||
json(502, { error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(PORT, '0.0.0.0', () => {
|
||||
process.stdout.write(`verae-access-zapier http://0.0.0.0:${PORT}/ plane=${PLANE}\n`);
|
||||
});
|
||||
39
packages/verae-access-zapier/test/health.test.js
Normal file
39
packages/verae-access-zapier/test/health.test.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawn } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const authzRoot = path.join(root, '..', 'verae-access-authz');
|
||||
|
||||
test('zapier plane has no portal and cannot read statements', async () => {
|
||||
const authzPort = 18028;
|
||||
const zPort = 18029;
|
||||
const authz = spawn(process.execPath, ['src/server.js'], {
|
||||
cwd: authzRoot,
|
||||
env: { ...process.env, PORT: String(authzPort) },
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
const z = spawn(process.execPath, ['src/server.js'], {
|
||||
cwd: root,
|
||||
env: { ...process.env, PORT: String(zPort), AUTHZ_URL: `http://127.0.0.1:${authzPort}` },
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
try {
|
||||
const h = await (await fetch(`http://127.0.0.1:${zPort}/health`)).json();
|
||||
assert.equal(h.plane, 'zapier');
|
||||
const portal = await fetch(`http://127.0.0.1:${zPort}/portal`);
|
||||
assert.equal(portal.status, 404);
|
||||
const deny = await fetch(`http://127.0.0.1:${authzPort}/check`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ plane: 'zapier', subject: 'verae.billing.statement.get' }),
|
||||
});
|
||||
assert.equal(deny.status, 403);
|
||||
} finally {
|
||||
z.kill('SIGTERM');
|
||||
authz.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
|
|
@ -15,6 +15,11 @@
|
|||
"services": {
|
||||
"nats": { "min": 1, "max": 1, "keepFloor": false, "enabled": true, "managed": false },
|
||||
"zappier-edge": { "min": 1, "max": 1, "keepFloor": true, "enabled": true },
|
||||
"access-authz": { "min": 1, "max": 1, "keepFloor": true, "enabled": true },
|
||||
"access-web": { "min": 1, "max": 1, "keepFloor": true, "enabled": true },
|
||||
"access-api": { "min": 1, "max": 1, "keepFloor": true, "enabled": true },
|
||||
"access-leaf": { "min": 1, "max": 1, "keepFloor": true, "enabled": true },
|
||||
"access-zapier": { "min": 1, "max": 1, "keepFloor": true, "enabled": true },
|
||||
"account-balance": { "min": 1, "max": 1, "keepFloor": true, "enabled": true },
|
||||
"customer-service": { "min": 1, "max": 1, "keepFloor": true, "enabled": true },
|
||||
"sales-pricing": { "min": 1, "max": 1, "keepFloor": true, "enabled": true },
|
||||
|
|
|
|||
27
packages/verae-fleet/services/access-api.json
Normal file
27
packages/verae-fleet/services/access-api.json
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"id": "access-api",
|
||||
"title": "Direct customer API access",
|
||||
"kind": "http",
|
||||
"package": "verae-access-api",
|
||||
"role": "access-api",
|
||||
"managed": true,
|
||||
"runtime": "HTTP :3022 plane=api",
|
||||
"health": { "type": "http", "path": "/health", "timeoutMs": 2000 },
|
||||
"ports": { "healthBase": 3022 },
|
||||
"spawn": {
|
||||
"cwd": "../verae-access-api",
|
||||
"command": "node",
|
||||
"args": ["src/server.js"]
|
||||
},
|
||||
"env": {
|
||||
"PORT": "3022",
|
||||
"AUTHZ_URL": "http://127.0.0.1:3020",
|
||||
"ACCOUNT_BALANCE_URL": "http://127.0.0.1:3010",
|
||||
"ZAPPIER_UPSTREAM": "http://127.0.0.1:3100",
|
||||
"NATS_URL": "nats://127.0.0.1:4222"
|
||||
},
|
||||
"nats": {
|
||||
"in": [],
|
||||
"out": ["verae.access.authz.check", "verae.billing.statement.get", "verae.zapier.jobs.watch"]
|
||||
}
|
||||
}
|
||||
24
packages/verae-fleet/services/access-authz.json
Normal file
24
packages/verae-fleet/services/access-authz.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"id": "access-authz",
|
||||
"title": "Access-plane authorization",
|
||||
"kind": "http",
|
||||
"package": "verae-access-authz",
|
||||
"role": "access-authz",
|
||||
"managed": true,
|
||||
"runtime": "HTTP :3020 + NATS verae.access.authz.check",
|
||||
"health": { "type": "http", "path": "/health", "timeoutMs": 2000 },
|
||||
"ports": { "healthBase": 3020 },
|
||||
"spawn": {
|
||||
"cwd": "../verae-access-authz",
|
||||
"command": "node",
|
||||
"args": ["src/server.js"]
|
||||
},
|
||||
"env": {
|
||||
"PORT": "3020",
|
||||
"NATS_URL": "nats://127.0.0.1:4222"
|
||||
},
|
||||
"nats": {
|
||||
"in": ["verae.access.authz.check"],
|
||||
"out": ["verae.access.authz.deny"]
|
||||
}
|
||||
}
|
||||
26
packages/verae-fleet/services/access-leaf.json
Normal file
26
packages/verae-fleet/services/access-leaf.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"id": "access-leaf",
|
||||
"title": "Server-to-server NATS leaf access",
|
||||
"kind": "http",
|
||||
"package": "verae-access-leaf",
|
||||
"role": "access-leaf",
|
||||
"managed": true,
|
||||
"runtime": "HTTP :3023 + NATS verae.access.leaf.in",
|
||||
"health": { "type": "http", "path": "/health", "timeoutMs": 2000 },
|
||||
"ports": { "healthBase": 3023 },
|
||||
"spawn": {
|
||||
"cwd": "../verae-access-leaf",
|
||||
"command": "node",
|
||||
"args": ["src/server.js"]
|
||||
},
|
||||
"env": {
|
||||
"PORT": "3023",
|
||||
"AUTHZ_URL": "http://127.0.0.1:3020",
|
||||
"LEAF_TOKEN": "leaf-dev-token",
|
||||
"NATS_URL": "nats://127.0.0.1:4222"
|
||||
},
|
||||
"nats": {
|
||||
"in": ["verae.access.leaf.in"],
|
||||
"out": ["verae.access.authz.check", "verae.archive.put", "verae.archive.query"]
|
||||
}
|
||||
}
|
||||
26
packages/verae-fleet/services/access-web.json
Normal file
26
packages/verae-fleet/services/access-web.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"id": "access-web",
|
||||
"title": "Direct customer web access",
|
||||
"kind": "http",
|
||||
"package": "verae-access-web",
|
||||
"role": "access-web",
|
||||
"managed": true,
|
||||
"runtime": "HTTP :3021 plane=web",
|
||||
"health": { "type": "http", "path": "/health", "timeoutMs": 2000 },
|
||||
"ports": { "healthBase": 3021 },
|
||||
"spawn": {
|
||||
"cwd": "../verae-access-web",
|
||||
"command": "node",
|
||||
"args": ["src/server.js"]
|
||||
},
|
||||
"env": {
|
||||
"PORT": "3021",
|
||||
"AUTHZ_URL": "http://127.0.0.1:3020",
|
||||
"ACCOUNT_BALANCE_URL": "http://127.0.0.1:3010",
|
||||
"NATS_URL": "nats://127.0.0.1:4222"
|
||||
},
|
||||
"nats": {
|
||||
"in": [],
|
||||
"out": ["verae.access.authz.check", "verae.billing.statement.get", "verae.billing.balance.adjust"]
|
||||
}
|
||||
}
|
||||
26
packages/verae-fleet/services/access-zapier.json
Normal file
26
packages/verae-fleet/services/access-zapier.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"id": "access-zapier",
|
||||
"title": "Zapier Platform access plane",
|
||||
"kind": "http",
|
||||
"package": "verae-access-zapier",
|
||||
"role": "access-zapier",
|
||||
"managed": true,
|
||||
"runtime": "HTTP :3024 plane=zapier (no portal)",
|
||||
"health": { "type": "http", "path": "/health", "timeoutMs": 2000 },
|
||||
"ports": { "healthBase": 3024 },
|
||||
"spawn": {
|
||||
"cwd": "../verae-access-zapier",
|
||||
"command": "node",
|
||||
"args": ["src/server.js"]
|
||||
},
|
||||
"env": {
|
||||
"PORT": "3024",
|
||||
"AUTHZ_URL": "http://127.0.0.1:3020",
|
||||
"ZAPPIER_UPSTREAM": "http://127.0.0.1:3100",
|
||||
"NATS_URL": "nats://127.0.0.1:4222"
|
||||
},
|
||||
"nats": {
|
||||
"in": [],
|
||||
"out": ["verae.access.authz.check", "verae.zapier.jobs.watch"]
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue