@stratum-hq/hono 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @stratum-hq/hono
2
2
 
3
- [Hono](https://hono.dev) middleware for [Stratum](https://github.com/stratum-hq/Stratum) — extracts tenant identity from a request and sets up AsyncLocalStorage context for downstream handlers.
3
+ [Hono](https://hono.dev) middleware for [Stratum](https://github.com/stratum-hq/Stratum). It extracts tenant identity from a request and sets up AsyncLocalStorage context for downstream handlers.
4
+
5
+ Read the documentation at [docs.stratum-hq.org/packages/hono](https://docs.stratum-hq.org/packages/hono/).
4
6
 
5
7
  ## Installation
6
8
 
@@ -47,9 +49,12 @@ app.get("/users", (c) => {
47
49
 
48
50
  ### Client-controlled sources
49
51
 
50
- `pathParam` and `header` both take the tenant ID from the request as the client sent it, so any client can name another tenant. Use them only when something you control authorizes the caller for that tenant (for example a later middleware that compares it with a claim of the verified token). Header mode additionally requires `trustTenantHeader: true`, which is appropriate only when a gateway you control sets the header, removes any client copy, and is the only way to reach the server:
52
+ `pathParam` and `header` both take the tenant ID from the request as the client sent it, so any client can name another tenant. Use them only when something you control authorizes the caller for that tenant (for example a later middleware that compares it with a claim of the verified token). Path parameter mode therefore requires `trustPathParam: true`. Header mode requires `trustTenantHeader: true`, which is appropriate only when a gateway you control sets the header, removes any client copy, and is the only way to reach the server:
51
53
 
52
54
  ```typescript
55
+ // Your application authorizes the caller for the tenant in the path.
56
+ app.use("/tenants/:tenantId/*", stratumMiddleware({ pathParam: "tenantId", trustPathParam: true }));
57
+
53
58
  app.use("*", stratumMiddleware({ header: "x-tenant-id", trustTenantHeader: true }));
54
59
  ```
55
60
 
@@ -60,18 +65,19 @@ app.use("*", stratumMiddleware({ header: "x-tenant-id", trustTenantHeader: true
60
65
  | Option | Behavior |
61
66
  |--------|----------|
62
67
  | `jwtClaim` | Read the claim from Hono's `jwtPayload` context variable |
63
- | `pathParam` | Read a URL path parameter (`c.req.param(name)`). Client-controlled: authorize the caller for that tenant separately |
68
+ | `pathParam` | Read a URL path parameter (`c.req.param(name)`). Requires `trustPathParam: true`. Client-controlled: authorize the caller for that tenant separately |
64
69
  | `header` | Read a request header (default: `x-tenant-id`). Requires `trustTenantHeader: true` |
70
+ | `trustPathParam` | Allow path parameter mode. Without it, `stratumMiddleware` throws at construction when `pathParam` is set and `jwtClaim` is not (default: `false`) |
65
71
  | `trustTenantHeader` | Allow header mode. Without it, and without `jwtClaim` or `pathParam`, `stratumMiddleware` throws at construction (default: `false`) |
66
72
  | `resolve` | Optional callback `(tenantId) => TenantContext` to populate ancestry, config, and permissions |
67
73
 
68
- If no tenant ID is found, the middleware responds with `400 { error: "Missing tenant ID" }`. If `resolve` rejects with a tenant error from `@stratum-hq/core`, for example from `StratumClient.resolveTenant`, the middleware responds with 404 `TENANT_NOT_FOUND`, 403 `TENANT_SUSPENDED`, 410 `TENANT_ARCHIVED`, or 403 `FORBIDDEN`. A control plane timeout gets 504 `CONTROL_PLANE_TIMEOUT`. An `UnauthorizedError` for the SDK's own API key gets 500 `CONTROL_PLANE_AUTH_FAILED` and a `console.error` line. Other errors go to the Hono error handler. Without a `resolve` callback the context is a placeholder (empty config/permissions) — provide `resolve` for real tenant data.
74
+ If no tenant ID is found, the middleware responds with `400 { error: "Missing tenant ID" }`. If `resolve` rejects with a tenant error from `@stratum-hq/core`, for example from `StratumClient.resolveTenant`, the middleware responds with 404 `TENANT_NOT_FOUND`, 403 `TENANT_SUSPENDED`, 410 `TENANT_ARCHIVED`, or 403 `FORBIDDEN`. A control plane timeout gets 504 `CONTROL_PLANE_TIMEOUT`. An `UnauthorizedError` for the SDK's own API key gets 500 `CONTROL_PLANE_AUTH_FAILED` and a `console.error` line. Other errors go to the Hono error handler. Without a `resolve` callback the context is a placeholder (empty config/permissions); provide `resolve` for real tenant data.
69
75
 
70
76
  ## Features
71
77
 
72
78
  - Tenant extraction from a verified JWT claim, a path parameter, or a trusted-gateway header
73
79
  - Binds tenant context via `runWithTenantContext` so downstream handlers can call `getTenantContext()`
74
- - Lightweight — structural types only, no heavy dependencies
80
+ - Lightweight: structural types only, no heavy dependencies
75
81
 
76
82
  ## Links
77
83
 
@@ -5,8 +5,19 @@ export interface StratumMiddlewareOptions {
5
5
  header?: string;
6
6
  /** JWT claim name to extract tenant ID from */
7
7
  jwtClaim?: string;
8
- /** URL path parameter name to extract tenant ID from */
8
+ /**
9
+ * URL path parameter name to extract tenant ID from. The client chooses the
10
+ * path, so this also requires `trustPathParam: true`.
11
+ */
9
12
  pathParam?: string;
13
+ /**
14
+ * Allow reading the tenant ID from the `pathParam` URL path parameter. A
15
+ * client can put any tenant ID in the path, so this must be enabled
16
+ * explicitly, and only when your application separately authorizes the
17
+ * caller for that tenant. Without it, `stratumMiddleware` throws at
18
+ * construction when `pathParam` is the tenant source. Default: false.
19
+ */
20
+ trustPathParam?: boolean;
10
21
  /**
11
22
  * Allow reading the tenant ID from a request header. A client can set any
12
23
  * header, so this must be enabled explicitly, and only when a gateway you
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAG7D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAG9D,MAAM,WAAW,wBAAwB;IACvC,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,GAAG,qBAAqB,CAAC;CACxF;AAkBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE,wBAA6B,GACrC,iBAAiB,CAmDnB"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAG7D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAG9D,MAAM,WAAW,wBAAwB;IACvC,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,GAAG,qBAAqB,CAAC;CACxF;AAkBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE,wBAA6B,GACrC,iBAAiB,CA0DnB"}
@@ -20,6 +20,10 @@ function extractFromPathParam(c, param) {
20
20
  * AsyncLocalStorage context via `runWithTenantContext()`.
21
21
  */
22
22
  export function stratumMiddleware(options = {}) {
23
+ if (!options.jwtClaim && options.pathParam && options.trustPathParam !== true) {
24
+ throw new Error("[stratum] stratumMiddleware would read the tenant ID from an unverified URL path parameter. " +
25
+ "Use jwtClaim with a verified JWT, or set trustPathParam: true if your application authorizes the caller for that tenant.");
26
+ }
23
27
  if (!options.jwtClaim && !options.pathParam && options.trustTenantHeader !== true) {
24
28
  throw new Error("[stratum] stratumMiddleware would read the tenant ID from an unverified request header. " +
25
29
  "Use jwtClaim with a verified JWT, or set trustTenantHeader: true if a trusted gateway sets the header.");
@@ -45,7 +49,7 @@ export function stratumMiddleware(options = {}) {
45
49
  ctx = options.resolve
46
50
  ? await options.resolve(tenantId)
47
51
  : /**
48
- * @warning Placeholder context — ancestry_path, resolved_config, and
52
+ * @warning Placeholder context: ancestry_path, resolved_config, and
49
53
  * resolved_permissions are stub values. Provide a `resolve` callback
50
54
  * to populate real tenant data.
51
55
  */
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@stratum-hq/hono",
3
- "version": "1.2.0",
4
- "description": "Stratum Hono integration — tenant extraction middleware with ALS context",
3
+ "version": "1.3.1",
4
+ "description": "Hono multi-tenancy middleware: tenant resolution with AsyncLocalStorage context",
5
5
  "keywords": [
6
6
  "multi-tenancy",
7
+ "multitenancy",
7
8
  "multi-tenant",
8
9
  "saas",
9
10
  "tenant",
11
+ "tenant-isolation",
10
12
  "hono",
13
+ "asynclocalstorage",
11
14
  "middleware",
12
15
  "typescript",
13
16
  "stratum"
@@ -51,7 +54,7 @@
51
54
  },
52
55
  "license": "MIT",
53
56
  "author": "Christian Crank",
54
- "homepage": "https://github.com/stratum-hq/Stratum/tree/main/packages/hono#readme",
57
+ "homepage": "https://docs.stratum-hq.org/packages/hono/",
55
58
  "bugs": "https://github.com/stratum-hq/Stratum/issues",
56
59
  "engines": {
57
60
  "node": ">=20.0.0"