@zudojs/tenancy 1.3.3 → 1.4.0
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 +10 -0
- package/dist/context/contextManager.core.d.ts +0 -1
- package/dist/context/contextManager.core.js +0 -1
- package/dist/context/contextStorage.core.d.ts +0 -1
- package/dist/context/contextStorage.core.js +0 -1
- package/dist/context/index.d.ts +0 -1
- package/dist/context/index.js +0 -1
- package/dist/http/httpHelpers.d.ts +30 -7
- package/dist/http/httpHelpers.js +33 -12
- package/dist/http/httpResolverContext.d.ts +0 -1
- package/dist/http/httpResolverContext.js +0 -1
- package/dist/http/httpSupport/httpRequest.helper.d.ts +0 -1
- package/dist/http/httpSupport/httpRequest.helper.js +0 -1
- package/dist/http/httpSupport/index.d.ts +0 -1
- package/dist/http/httpSupport/index.js +0 -1
- package/dist/http/httpSupport/tenancyMiddleware.lookup.d.ts +0 -1
- package/dist/http/httpSupport/tenancyMiddleware.lookup.js +0 -1
- package/dist/http/httpSupport/tenancyMiddleware.resolver.d.ts +0 -1
- package/dist/http/httpSupport/tenancyMiddleware.resolver.js +0 -1
- package/dist/http/httpTypes.d.ts +0 -1
- package/dist/http/httpTypes.js +0 -1
- package/dist/http/index.d.ts +2 -2
- package/dist/http/index.js +1 -2
- package/dist/http/tenancyMiddleware.core.d.ts +4 -2
- package/dist/http/tenancyMiddleware.core.js +2 -3
- package/dist/http/tenancyMiddleware.guard.d.ts +0 -1
- package/dist/http/tenancyMiddleware.guard.js +2 -3
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/repository/index.d.ts +0 -1
- package/dist/repository/index.js +0 -1
- package/dist/repository/repository.core.d.ts +0 -1
- package/dist/repository/repository.core.js +0 -1
- package/dist/repository/tenantManager.core.d.ts +0 -1
- package/dist/repository/tenantManager.core.js +0 -1
- package/dist/resolver/index.d.ts +0 -1
- package/dist/resolver/index.js +0 -1
- package/dist/resolver/resolverChain.core.d.ts +0 -1
- package/dist/resolver/resolverChain.core.js +0 -1
- package/dist/resolver/resolvers/domainResolver.core.d.ts +0 -1
- package/dist/resolver/resolvers/domainResolver.core.js +0 -1
- package/dist/resolver/resolvers/headerResolver.core.d.ts +0 -1
- package/dist/resolver/resolvers/headerResolver.core.js +0 -1
- package/dist/resolver/resolvers/index.d.ts +0 -1
- package/dist/resolver/resolvers/index.js +0 -1
- package/dist/resolver/resolvers/jwtResolver.core.d.ts +0 -1
- package/dist/resolver/resolvers/jwtResolver.core.js +0 -1
- package/dist/resolver/resolvers/pathResolver.core.d.ts +0 -1
- package/dist/resolver/resolvers/pathResolver.core.js +0 -1
- package/dist/resolver/resolvers/subdomainResolver.core.d.ts +0 -1
- package/dist/resolver/resolvers/subdomainResolver.core.js +0 -1
- package/dist/security/guard.core.d.ts +0 -1
- package/dist/security/guard.core.js +0 -1
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/tenantCache.helper.d.ts +49 -0
- package/dist/security/tenantCache.helper.js +54 -0
- package/dist/tenancyErrors/index.d.ts +0 -1
- package/dist/tenancyErrors/index.js +0 -1
- package/dist/tenancyErrors/tenancyError.base.d.ts +0 -1
- package/dist/tenancyErrors/tenancyError.base.js +0 -1
- package/dist/tenancyErrors/tenancyError.types.d.ts +0 -1
- package/dist/tenancyErrors/tenancyError.types.js +0 -1
- package/dist/tenancyTypes/index.d.ts +0 -1
- package/dist/tenancyTypes/index.js +0 -1
- package/dist/tenancyTypes/repositoryTypes.d.ts +0 -1
- package/dist/tenancyTypes/repositoryTypes.js +0 -1
- package/dist/tenancyTypes/resolverTypes.d.ts +0 -1
- package/dist/tenancyTypes/resolverTypes.js +0 -1
- package/dist/tenancyTypes/tenantIdentity.d.ts +0 -1
- package/dist/tenancyTypes/tenantIdentity.js +0 -1
- package/dist/tenancyTypes/tenantInterface.d.ts +0 -1
- package/dist/tenancyTypes/tenantInterface.js +0 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -1
- package/dist/utils/utils.helper.d.ts +0 -1
- package/dist/utils/utils.helper.js +0 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -149,8 +149,18 @@ explicitly on routes where something else ties the tenant to the principal.
|
|
|
149
149
|
- Tenant ids are normalized (NFKC, trimmed, lowercased) and constrained to
|
|
150
150
|
`^[a-z0-9][a-z0-9_-]*$`, so an id cannot forge a separator in a cache key,
|
|
151
151
|
a schema name, or a path. `tenantKey` escapes its segments as well.
|
|
152
|
+
- For `@zudojs/cache`, use `createTenantCacheScope(tenantId, key)` rather than
|
|
153
|
+
`createTenantCacheKey`: the cache refuses `:` inside a key part (that is how
|
|
154
|
+
it stops a raw key from forging a namespace), so `tenant:acme:totals` throws
|
|
155
|
+
`ERR_INVALID_INPUT` there. The scope helper returns
|
|
156
|
+
`{ namespace: "tenant.acme", key: "totals" }`, and the cache's namespace is
|
|
157
|
+
the isolation boundary:
|
|
158
|
+
`await cache.get(scope.key, { namespace: scope.namespace })`.
|
|
152
159
|
- Non-active tenants are refused during resolution. Pass `allowInactive` on
|
|
153
160
|
routes that exist to serve suspended tenants.
|
|
161
|
+
- Every refusal body is `{ "error": message, "code": ERR_TENANT_* }`, the
|
|
162
|
+
shape `@zudojs/http` uses, so a client can tell `ERR_TENANT_NOT_FOUND` from
|
|
163
|
+
an application 404. The codes are exported as `TENANCY_RESPONSE_CODE`.
|
|
154
164
|
- An unknown tenant and a non-active one get the same `404 Tenant not found`,
|
|
155
165
|
and the guard middleware answers `403 Tenant is not available` without the
|
|
156
166
|
id or status, so a caller cannot enumerate tenants or learn which are
|
package/dist/context/index.d.ts
CHANGED
|
@@ -5,4 +5,3 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { createTenantContextStorage, getDefaultStorage, resetDefaultStorage, type TenantContextStorage, } from "./contextStorage.core.js";
|
|
7
7
|
export { createContextManager, type ContextManagerOptions, } from "./contextManager.core.js";
|
|
8
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/context/index.js
CHANGED
|
@@ -9,28 +9,51 @@
|
|
|
9
9
|
* @module http/httpHelpers
|
|
10
10
|
*/
|
|
11
11
|
import { type GuardResponse } from "@zudojs/middleware";
|
|
12
|
+
/**
|
|
13
|
+
* Machine-readable `code` carried by every refusal body the tenancy
|
|
14
|
+
* middleware produces, alongside `error`. They are tenant-specific on
|
|
15
|
+
* purpose: a client (or a test) can tell "no tenant" from an application's
|
|
16
|
+
* own `404`, which `{ "error": "Tenant not found" }` alone could not.
|
|
17
|
+
*/
|
|
18
|
+
export declare const TENANCY_RESPONSE_CODE: Readonly<{
|
|
19
|
+
/** The request names no tenant, or an unknown or non-active one. */
|
|
20
|
+
readonly NOT_FOUND: "ERR_TENANT_NOT_FOUND";
|
|
21
|
+
/** A tenant was resolved but the route requires one it lacks. */
|
|
22
|
+
readonly REQUIRED: "ERR_TENANT_REQUIRED";
|
|
23
|
+
/** The route forbids tenant context, or the resolution is not trusted. */
|
|
24
|
+
readonly FORBIDDEN: "ERR_TENANT_FORBIDDEN";
|
|
25
|
+
/** The tenant exists but is not active (guard middleware). */
|
|
26
|
+
readonly UNAVAILABLE: "ERR_TENANT_UNAVAILABLE";
|
|
27
|
+
/** Two request sources named different tenants. */
|
|
28
|
+
readonly RESOLUTION_CONFLICT: "ERR_TENANT_RESOLUTION_CONFLICT";
|
|
29
|
+
/** A resolver rejected a credential or could not run. */
|
|
30
|
+
readonly RESOLUTION_FAILED: "ERR_TENANT_RESOLUTION_FAILED";
|
|
31
|
+
}>;
|
|
32
|
+
/** One of the {@link TENANCY_RESPONSE_CODE} values. */
|
|
33
|
+
export type TenancyResponseCode = (typeof TENANCY_RESPONSE_CODE)[keyof typeof TENANCY_RESPONSE_CODE];
|
|
12
34
|
/**
|
|
13
35
|
* Create a JSON response with a caller-built body.
|
|
14
36
|
*/
|
|
15
37
|
export declare function createJsonResponse(status: number, body: unknown): GuardResponse;
|
|
16
38
|
/**
|
|
17
|
-
* Create a JSON error response: `{ "error": message }
|
|
39
|
+
* Create a JSON error response: `{ "error": message, "code": code }`, the
|
|
40
|
+
* shape `@zudojs/http` uses for thrown errors. `code` is omitted only when
|
|
41
|
+
* not given.
|
|
18
42
|
*/
|
|
19
|
-
export declare function createJsonErrorResponse(status: number, message: string): GuardResponse;
|
|
43
|
+
export declare function createJsonErrorResponse(status: number, message: string, code?: string): GuardResponse;
|
|
20
44
|
/**
|
|
21
45
|
* Create a 400 Bad Request response.
|
|
22
46
|
*/
|
|
23
|
-
export declare function createBadRequest(message: string): GuardResponse;
|
|
47
|
+
export declare function createBadRequest(message: string, code?: string): GuardResponse;
|
|
24
48
|
/**
|
|
25
49
|
* Create a 401 Unauthorized response.
|
|
26
50
|
*/
|
|
27
|
-
export declare function createUnauthorized(message: string): GuardResponse;
|
|
51
|
+
export declare function createUnauthorized(message: string, code?: string): GuardResponse;
|
|
28
52
|
/**
|
|
29
53
|
* Create a 403 Forbidden response.
|
|
30
54
|
*/
|
|
31
|
-
export declare function createForbidden(message: string): GuardResponse;
|
|
55
|
+
export declare function createForbidden(message: string, code?: string): GuardResponse;
|
|
32
56
|
/**
|
|
33
57
|
* Create a 404 Not Found response.
|
|
34
58
|
*/
|
|
35
|
-
export declare function createNotFound(message: string): GuardResponse;
|
|
36
|
-
//# sourceMappingURL=httpHelpers.d.ts.map
|
|
59
|
+
export declare function createNotFound(message: string, code?: string): GuardResponse;
|
package/dist/http/httpHelpers.js
CHANGED
|
@@ -10,6 +10,26 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { createGuardResponse } from "@zudojs/middleware";
|
|
12
12
|
const JSON_HEADERS = { "content-type": "application/json" };
|
|
13
|
+
/**
|
|
14
|
+
* Machine-readable `code` carried by every refusal body the tenancy
|
|
15
|
+
* middleware produces, alongside `error`. They are tenant-specific on
|
|
16
|
+
* purpose: a client (or a test) can tell "no tenant" from an application's
|
|
17
|
+
* own `404`, which `{ "error": "Tenant not found" }` alone could not.
|
|
18
|
+
*/
|
|
19
|
+
export const TENANCY_RESPONSE_CODE = Object.freeze({
|
|
20
|
+
/** The request names no tenant, or an unknown or non-active one. */
|
|
21
|
+
NOT_FOUND: "ERR_TENANT_NOT_FOUND",
|
|
22
|
+
/** A tenant was resolved but the route requires one it lacks. */
|
|
23
|
+
REQUIRED: "ERR_TENANT_REQUIRED",
|
|
24
|
+
/** The route forbids tenant context, or the resolution is not trusted. */
|
|
25
|
+
FORBIDDEN: "ERR_TENANT_FORBIDDEN",
|
|
26
|
+
/** The tenant exists but is not active (guard middleware). */
|
|
27
|
+
UNAVAILABLE: "ERR_TENANT_UNAVAILABLE",
|
|
28
|
+
/** Two request sources named different tenants. */
|
|
29
|
+
RESOLUTION_CONFLICT: "ERR_TENANT_RESOLUTION_CONFLICT",
|
|
30
|
+
/** A resolver rejected a credential or could not run. */
|
|
31
|
+
RESOLUTION_FAILED: "ERR_TENANT_RESOLUTION_FAILED",
|
|
32
|
+
});
|
|
13
33
|
/**
|
|
14
34
|
* Create a JSON response with a caller-built body.
|
|
15
35
|
*/
|
|
@@ -17,33 +37,34 @@ export function createJsonResponse(status, body) {
|
|
|
17
37
|
return createGuardResponse({ status, body, headers: JSON_HEADERS });
|
|
18
38
|
}
|
|
19
39
|
/**
|
|
20
|
-
* Create a JSON error response: `{ "error": message }
|
|
40
|
+
* Create a JSON error response: `{ "error": message, "code": code }`, the
|
|
41
|
+
* shape `@zudojs/http` uses for thrown errors. `code` is omitted only when
|
|
42
|
+
* not given.
|
|
21
43
|
*/
|
|
22
|
-
export function createJsonErrorResponse(status, message) {
|
|
23
|
-
return createJsonResponse(status, { error: message });
|
|
44
|
+
export function createJsonErrorResponse(status, message, code) {
|
|
45
|
+
return createJsonResponse(status, code === undefined ? { error: message } : { error: message, code });
|
|
24
46
|
}
|
|
25
47
|
/**
|
|
26
48
|
* Create a 400 Bad Request response.
|
|
27
49
|
*/
|
|
28
|
-
export function createBadRequest(message) {
|
|
29
|
-
return createJsonErrorResponse(400, message);
|
|
50
|
+
export function createBadRequest(message, code = TENANCY_RESPONSE_CODE.RESOLUTION_FAILED) {
|
|
51
|
+
return createJsonErrorResponse(400, message, code);
|
|
30
52
|
}
|
|
31
53
|
/**
|
|
32
54
|
* Create a 401 Unauthorized response.
|
|
33
55
|
*/
|
|
34
|
-
export function createUnauthorized(message) {
|
|
35
|
-
return createJsonErrorResponse(401, message);
|
|
56
|
+
export function createUnauthorized(message, code = TENANCY_RESPONSE_CODE.REQUIRED) {
|
|
57
|
+
return createJsonErrorResponse(401, message, code);
|
|
36
58
|
}
|
|
37
59
|
/**
|
|
38
60
|
* Create a 403 Forbidden response.
|
|
39
61
|
*/
|
|
40
|
-
export function createForbidden(message) {
|
|
41
|
-
return createJsonErrorResponse(403, message);
|
|
62
|
+
export function createForbidden(message, code = TENANCY_RESPONSE_CODE.FORBIDDEN) {
|
|
63
|
+
return createJsonErrorResponse(403, message, code);
|
|
42
64
|
}
|
|
43
65
|
/**
|
|
44
66
|
* Create a 404 Not Found response.
|
|
45
67
|
*/
|
|
46
|
-
export function createNotFound(message) {
|
|
47
|
-
return createJsonErrorResponse(404, message);
|
|
68
|
+
export function createNotFound(message, code = TENANCY_RESPONSE_CODE.NOT_FOUND) {
|
|
69
|
+
return createJsonErrorResponse(404, message, code);
|
|
48
70
|
}
|
|
49
|
-
//# sourceMappingURL=httpHelpers.js.map
|
|
@@ -42,4 +42,3 @@ export declare const TENANT_CLAIMS_STATE_KEY = "tenancy:claims";
|
|
|
42
42
|
* @returns An accessor object every shipped resolver understands.
|
|
43
43
|
*/
|
|
44
44
|
export declare function createHttpResolverContext<Context extends HttpMiddlewareContext = HttpMiddlewareContext>(context: Context, getClaims?: TenantClaimsReader<Context>): HttpResolverContext;
|
|
45
|
-
//# sourceMappingURL=httpResolverContext.d.ts.map
|
|
@@ -14,4 +14,3 @@ import type { HttpRequestContext } from "../httpTypes.js";
|
|
|
14
14
|
* then a plain object — each case-insensitive.
|
|
15
15
|
*/
|
|
16
16
|
export declare function readRequestHeader(request: HttpRequestContext, name: string): string | undefined;
|
|
17
|
-
//# sourceMappingURL=httpRequest.helper.d.ts.map
|
|
@@ -16,4 +16,3 @@ import type { TenantRepository } from "../../tenancyTypes/repositoryTypes.js";
|
|
|
16
16
|
* every tenant whose id differs from its slug was unreachable.
|
|
17
17
|
*/
|
|
18
18
|
export declare function loadResolvedTenant(repository: TenantRepository, resolution: TenantResolution, slugLookup: boolean): Promise<Tenant | undefined>;
|
|
19
|
-
//# sourceMappingURL=tenancyMiddleware.lookup.d.ts.map
|
|
@@ -27,4 +27,3 @@ export type TenantResolverSource<Context> = TenantResolver<Context> | TenantReso
|
|
|
27
27
|
* @returns A resolver that yields the winning `TenantResolution`.
|
|
28
28
|
*/
|
|
29
29
|
export declare function toTenantResolver<Context>(source: TenantResolverSource<Context>): TenantResolver<Context>;
|
|
30
|
-
//# sourceMappingURL=tenancyMiddleware.resolver.d.ts.map
|
package/dist/http/httpTypes.d.ts
CHANGED
package/dist/http/httpTypes.js
CHANGED
package/dist/http/index.d.ts
CHANGED
|
@@ -9,6 +9,6 @@ export { TENANT_CLAIMS_STATE_KEY, createHttpResolverContext, } from "./httpResol
|
|
|
9
9
|
export type { HttpResolverContext, TenantClaims, TenantClaimsReader, } from "./httpResolverContext.js";
|
|
10
10
|
export { readRequestHeader } from "./httpSupport/index.js";
|
|
11
11
|
export type { TenantResolverSource } from "./httpSupport/index.js";
|
|
12
|
-
export { createBadRequest, createForbidden, createJsonErrorResponse, createJsonResponse, createNotFound, createUnauthorized, } from "./httpHelpers.js";
|
|
12
|
+
export { createBadRequest, createForbidden, createJsonErrorResponse, createJsonResponse, createNotFound, createUnauthorized, TENANCY_RESPONSE_CODE, } from "./httpHelpers.js";
|
|
13
|
+
export type { TenancyResponseCode } from "./httpHelpers.js";
|
|
13
14
|
export type * from "./httpTypes.js";
|
|
14
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/http/index.js
CHANGED
|
@@ -5,5 +5,4 @@ export { TENANT_CONTEXT_STATE_KEY, TENANT_STATE_KEY, createRequireTenantMiddlewa
|
|
|
5
5
|
export { createTenantGuardMiddleware, createTenantPropagationMiddleware, } from "./tenancyMiddleware.guard.js";
|
|
6
6
|
export { TENANT_CLAIMS_STATE_KEY, createHttpResolverContext, } from "./httpResolverContext.js";
|
|
7
7
|
export { readRequestHeader } from "./httpSupport/index.js";
|
|
8
|
-
export { createBadRequest, createForbidden, createJsonErrorResponse, createJsonResponse, createNotFound, createUnauthorized, } from "./httpHelpers.js";
|
|
9
|
-
//# sourceMappingURL=index.js.map
|
|
8
|
+
export { createBadRequest, createForbidden, createJsonErrorResponse, createJsonResponse, createNotFound, createUnauthorized, TENANCY_RESPONSE_CODE, } from "./httpHelpers.js";
|
|
@@ -74,7 +74,10 @@ export interface ResolveTenantMiddlewareOptions<Context extends HttpMiddlewareCo
|
|
|
74
74
|
* `tenancy:claims` state key. May be typed with `@zudojs/http`'s context.
|
|
75
75
|
*/
|
|
76
76
|
readonly getClaims?: TenantClaimsReader<Context>;
|
|
77
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* Custom body for the 404 answered when no usable tenant resolves. The
|
|
79
|
+
* default is `{ error: "Tenant not found", code: "ERR_TENANT_NOT_FOUND" }`.
|
|
80
|
+
*/
|
|
78
81
|
readonly notFoundResponse?: (resolution: TenantResolution | undefined) => unknown;
|
|
79
82
|
}
|
|
80
83
|
/** Options for the require tenant middleware. */
|
|
@@ -98,4 +101,3 @@ export declare function createResolveTenantMiddleware<Context extends HttpMiddle
|
|
|
98
101
|
* Must run after `createResolveTenantMiddleware`.
|
|
99
102
|
*/
|
|
100
103
|
export declare function createRequireTenantMiddleware(options?: RequireTenantMiddlewareOptions): HttpMiddleware;
|
|
101
|
-
//# sourceMappingURL=tenancyMiddleware.core.d.ts.map
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Composes with the @zudojs/http pipeline structurally; no dependency on it.
|
|
9
9
|
*/
|
|
10
10
|
import { createHttpResolverContext } from "./httpResolverContext.js";
|
|
11
|
-
import { createBadRequest, createForbidden, createJsonResponse, createNotFound, createUnauthorized, } from "./httpHelpers.js";
|
|
11
|
+
import { createBadRequest, createForbidden, createJsonResponse, createNotFound, createUnauthorized, TENANCY_RESPONSE_CODE, } from "./httpHelpers.js";
|
|
12
12
|
import { meetsTrustLevel } from "../security/guard.core.js";
|
|
13
13
|
import { loadResolvedTenant, toTenantResolver, } from "./httpSupport/index.js";
|
|
14
14
|
import { TenantResolutionConflictError, TenantResolutionError, } from "../tenancyErrors/tenancyError.types.js";
|
|
@@ -45,7 +45,7 @@ export function createResolveTenantMiddleware(options) {
|
|
|
45
45
|
// carrying the framework's own error text; both cases are a refusal
|
|
46
46
|
// the caller caused, and neither may fall through to `next()`.
|
|
47
47
|
if (error instanceof TenantResolutionConflictError) {
|
|
48
|
-
return createForbidden("Tenant could not be established: request sources name different tenants");
|
|
48
|
+
return createForbidden("Tenant could not be established: request sources name different tenants", TENANCY_RESPONSE_CODE.RESOLUTION_CONFLICT);
|
|
49
49
|
}
|
|
50
50
|
if (error instanceof TenantResolutionError) {
|
|
51
51
|
return createBadRequest("Tenant could not be resolved for this request");
|
|
@@ -101,4 +101,3 @@ export function createRequireTenantMiddleware(options) {
|
|
|
101
101
|
return next();
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
|
-
//# sourceMappingURL=tenancyMiddleware.core.js.map
|
|
@@ -32,4 +32,3 @@ export declare function createTenantGuardMiddleware(options?: TenantGuardMiddlew
|
|
|
32
32
|
* into AsyncLocalStorage for downstream handlers.
|
|
33
33
|
*/
|
|
34
34
|
export declare function createTenantPropagationMiddleware(storage: TenantContextStorage): HttpMiddleware;
|
|
35
|
-
//# sourceMappingURL=tenancyMiddleware.guard.d.ts.map
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module http/tenancyMiddleware.guard
|
|
5
5
|
*/
|
|
6
|
-
import { createForbidden } from "./httpHelpers.js";
|
|
6
|
+
import { createForbidden, TENANCY_RESPONSE_CODE } from "./httpHelpers.js";
|
|
7
7
|
import { TENANT_STATE_KEY, TENANT_CONTEXT_STATE_KEY, } from "./tenancyMiddleware.core.js";
|
|
8
8
|
// ─── Middleware Factories ──────────────────────────────────────────────────
|
|
9
9
|
/**
|
|
@@ -24,7 +24,7 @@ export function createTenantGuardMiddleware(options = {}) {
|
|
|
24
24
|
// One answer for "gone" and "not active", naming neither the tenant nor
|
|
25
25
|
// its status: the body goes to the client, which must not learn either.
|
|
26
26
|
if (!current || current.status !== "active") {
|
|
27
|
-
return createForbidden("Tenant is not available");
|
|
27
|
+
return createForbidden("Tenant is not available", TENANCY_RESPONSE_CODE.UNAVAILABLE);
|
|
28
28
|
}
|
|
29
29
|
return next();
|
|
30
30
|
};
|
|
@@ -47,4 +47,3 @@ export function createTenantPropagationMiddleware(storage) {
|
|
|
47
47
|
return next();
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
-
//# sourceMappingURL=tenancyMiddleware.guard.js.map
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -6,4 +6,3 @@
|
|
|
6
6
|
export { createMemoryTenantRepository, createDomainRegistry, } from "./repository.core.js";
|
|
7
7
|
export type { MemoryTenantRepository, TenantWithDomains, } from "./repository.core.js";
|
|
8
8
|
export { DEFAULT_TENANT_CACHE_TTL_MS, createTenantManager, type TenantManagerOptions, } from "./tenantManager.core.js";
|
|
9
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/repository/index.js
CHANGED
package/dist/resolver/index.d.ts
CHANGED
package/dist/resolver/index.js
CHANGED
|
@@ -46,4 +46,3 @@ export type ResolverChainContext<Resolvers extends readonly TenantResolver<never
|
|
|
46
46
|
export declare function createResolverChain<const Resolvers extends readonly TenantResolver<never>[]>(resolvers: Resolvers, options?: ResolverChainOptions): TenantResolverChain<ResolverChainContext<Resolvers>>;
|
|
47
47
|
export declare function createResolverChain<Context = unknown>(resolvers: readonly TenantResolver<Context>[], options?: ResolverChainOptions): TenantResolverChain<Context>;
|
|
48
48
|
export {};
|
|
49
|
-
//# sourceMappingURL=resolverChain.core.d.ts.map
|
|
@@ -26,4 +26,3 @@ export interface HeaderResolverOptions {
|
|
|
26
26
|
* Create a tenant resolver that reads from an HTTP header.
|
|
27
27
|
*/
|
|
28
28
|
export declare function createHeaderResolver(options?: HeaderResolverOptions): TenantResolver<HeaderContext>;
|
|
29
|
-
//# sourceMappingURL=headerResolver.core.d.ts.map
|
|
@@ -13,4 +13,3 @@ export { createPathResolver } from "./pathResolver.core.js";
|
|
|
13
13
|
export type { PathContext, PathResolverOptions } from "./pathResolver.core.js";
|
|
14
14
|
export { createJwtResolver } from "./jwtResolver.core.js";
|
|
15
15
|
export type { JwtContext, JwtResolverOptions } from "./jwtResolver.core.js";
|
|
16
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -8,4 +8,3 @@ export { createSubdomainResolver } from "./subdomainResolver.core.js";
|
|
|
8
8
|
export { createDomainResolver } from "./domainResolver.core.js";
|
|
9
9
|
export { createPathResolver } from "./pathResolver.core.js";
|
|
10
10
|
export { createJwtResolver } from "./jwtResolver.core.js";
|
|
11
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -36,4 +36,3 @@ export declare function hostnameOf(host: string): string | undefined;
|
|
|
36
36
|
* Example: "acme.example.com" → tenant "acme"
|
|
37
37
|
*/
|
|
38
38
|
export declare function createSubdomainResolver(options?: SubdomainResolverOptions): TenantResolver<SubdomainContext>;
|
|
39
|
-
//# sourceMappingURL=subdomainResolver.core.d.ts.map
|
package/dist/security/index.d.ts
CHANGED
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* @module security
|
|
5
5
|
*/
|
|
6
6
|
export { getDefaultTrust, meetsTrustLevel, assertTenantUsable, assertTenantOwnership, assertTrustLevel, assertSameTenant, tenantKey, createTenantCacheKey, } from "./guard.core.js";
|
|
7
|
-
|
|
7
|
+
export { createTenantCacheNamespace, createTenantCacheScope, TENANT_CACHE_PART_PATTERN, type TenantCacheScope, } from "./tenantCache.helper.js";
|
package/dist/security/index.js
CHANGED
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* @module security
|
|
5
5
|
*/
|
|
6
6
|
export { getDefaultTrust, meetsTrustLevel, assertTenantUsable, assertTenantOwnership, assertTrustLevel, assertSameTenant, tenantKey, createTenantCacheKey, } from "./guard.core.js";
|
|
7
|
-
|
|
7
|
+
export { createTenantCacheNamespace, createTenantCacheScope, TENANT_CACHE_PART_PATTERN, } from "./tenantCache.helper.js";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant scoping for `@zudojs/cache`.
|
|
3
|
+
*
|
|
4
|
+
* @module security/tenantCache
|
|
5
|
+
*
|
|
6
|
+
* `createTenantCacheKey` builds `tenant:<id>:<key>`, which `@zudojs/cache`
|
|
7
|
+
* refuses: its key parts may not contain the `:` separator, precisely so a
|
|
8
|
+
* raw key cannot forge a namespace. The cache's own tenant boundary is the
|
|
9
|
+
* `namespace` option, so the compatible shape is a namespace derived from
|
|
10
|
+
* the tenant id plus the caller's key, passed separately:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const scope = createTenantCacheScope(tenantId, "dashboard.totals");
|
|
14
|
+
* await cache.get(scope.key, { namespace: scope.namespace });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import type { TenantId } from "../tenancyTypes/tenantIdentity.js";
|
|
18
|
+
/**
|
|
19
|
+
* The alphabet `@zudojs/cache` accepts for a key part
|
|
20
|
+
* (`CACHE_KEY_PATTERN` there). Mirrored rather than imported: tenancy sits
|
|
21
|
+
* below cache in the dependency tiers.
|
|
22
|
+
*/
|
|
23
|
+
export declare const TENANT_CACHE_PART_PATTERN: RegExp;
|
|
24
|
+
/** A tenant-scoped cache address: pass both halves to `@zudojs/cache`. */
|
|
25
|
+
export interface TenantCacheScope {
|
|
26
|
+
/** Cache namespace for the tenant, e.g. `tenant.kola-motors`. */
|
|
27
|
+
readonly namespace: string;
|
|
28
|
+
/** The caller's key, unchanged. */
|
|
29
|
+
readonly key: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Cache namespace for a tenant: `tenant.<tenantId>`.
|
|
33
|
+
*
|
|
34
|
+
* Tenant ids are already constrained to `^[a-z0-9][a-z0-9_-]*$`, so the
|
|
35
|
+
* result always satisfies the cache's part alphabet.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createTenantCacheNamespace(tenantId: TenantId): string;
|
|
38
|
+
/**
|
|
39
|
+
* Build a tenant-scoped cache address that `@zudojs/cache` accepts.
|
|
40
|
+
*
|
|
41
|
+
* Isolation comes from the namespace, not from string concatenation, so two
|
|
42
|
+
* tenants can never share a fully-qualified key whatever `key` is.
|
|
43
|
+
*
|
|
44
|
+
* @param tenantId - The owning tenant.
|
|
45
|
+
* @param key - The resource key within that tenant. Must match
|
|
46
|
+
* {@link TENANT_CACHE_PART_PATTERN}; in particular it may not contain `:`.
|
|
47
|
+
* @throws {ValidationError} when `key` would fail the cache's validation.
|
|
48
|
+
*/
|
|
49
|
+
export declare function createTenantCacheScope(tenantId: TenantId, key: string): TenantCacheScope;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant scoping for `@zudojs/cache`.
|
|
3
|
+
*
|
|
4
|
+
* @module security/tenantCache
|
|
5
|
+
*
|
|
6
|
+
* `createTenantCacheKey` builds `tenant:<id>:<key>`, which `@zudojs/cache`
|
|
7
|
+
* refuses: its key parts may not contain the `:` separator, precisely so a
|
|
8
|
+
* raw key cannot forge a namespace. The cache's own tenant boundary is the
|
|
9
|
+
* `namespace` option, so the compatible shape is a namespace derived from
|
|
10
|
+
* the tenant id plus the caller's key, passed separately:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const scope = createTenantCacheScope(tenantId, "dashboard.totals");
|
|
14
|
+
* await cache.get(scope.key, { namespace: scope.namespace });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import { ValidationError } from "@zudojs/errors";
|
|
18
|
+
/**
|
|
19
|
+
* The alphabet `@zudojs/cache` accepts for a key part
|
|
20
|
+
* (`CACHE_KEY_PATTERN` there). Mirrored rather than imported: tenancy sits
|
|
21
|
+
* below cache in the dependency tiers.
|
|
22
|
+
*/
|
|
23
|
+
export const TENANT_CACHE_PART_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
24
|
+
/** Prefix that keeps tenant namespaces apart from any other namespace. */
|
|
25
|
+
const NAMESPACE_PREFIX = "tenant.";
|
|
26
|
+
/**
|
|
27
|
+
* Cache namespace for a tenant: `tenant.<tenantId>`.
|
|
28
|
+
*
|
|
29
|
+
* Tenant ids are already constrained to `^[a-z0-9][a-z0-9_-]*$`, so the
|
|
30
|
+
* result always satisfies the cache's part alphabet.
|
|
31
|
+
*/
|
|
32
|
+
export function createTenantCacheNamespace(tenantId) {
|
|
33
|
+
return `${NAMESPACE_PREFIX}${tenantId}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Build a tenant-scoped cache address that `@zudojs/cache` accepts.
|
|
37
|
+
*
|
|
38
|
+
* Isolation comes from the namespace, not from string concatenation, so two
|
|
39
|
+
* tenants can never share a fully-qualified key whatever `key` is.
|
|
40
|
+
*
|
|
41
|
+
* @param tenantId - The owning tenant.
|
|
42
|
+
* @param key - The resource key within that tenant. Must match
|
|
43
|
+
* {@link TENANT_CACHE_PART_PATTERN}; in particular it may not contain `:`.
|
|
44
|
+
* @throws {ValidationError} when `key` would fail the cache's validation.
|
|
45
|
+
*/
|
|
46
|
+
export function createTenantCacheScope(tenantId, key) {
|
|
47
|
+
if (typeof key !== "string" || !TENANT_CACHE_PART_PATTERN.test(key)) {
|
|
48
|
+
throw new ValidationError(`Tenant cache key ${JSON.stringify(key)} must match ${String(TENANT_CACHE_PART_PATTERN)}; use "." or "-" instead of ":" to separate segments.`);
|
|
49
|
+
}
|
|
50
|
+
return Object.freeze({
|
|
51
|
+
namespace: createTenantCacheNamespace(tenantId),
|
|
52
|
+
key,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -5,4 +5,3 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { TenantError } from "./tenancyError.base.js";
|
|
7
7
|
export { InvalidTenantIdError, TenantNotFoundError, TenantContextMissingError, TenantResolutionError, TenantResolutionConflictError, TenantUnavailableError, TenantAccessDeniedError, TenantTrustLevelError, TenantAlreadyExistsError, TenantIsolationError, } from "./tenancyError.types.js";
|
|
8
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -5,4 +5,3 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { TenantError } from "./tenancyError.base.js";
|
|
7
7
|
export { InvalidTenantIdError, TenantNotFoundError, TenantContextMissingError, TenantResolutionError, TenantResolutionConflictError, TenantUnavailableError, TenantAccessDeniedError, TenantTrustLevelError, TenantAlreadyExistsError, TenantIsolationError, } from "./tenancyError.types.js";
|
|
8
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -8,4 +8,3 @@ export { type Tenant, type TenantResolutionSource, type TenantTrustLevel, type T
|
|
|
8
8
|
export { type TenantResolution, type TenantResolutionResult, type TenantResolver, type ResolverChainOptions, } from "./resolverTypes.js";
|
|
9
9
|
export { type TenantRepository, type TenantCache, } from "./repositoryTypes.js";
|
|
10
10
|
export { type TenantDomain } from "./repositoryTypes.js";
|
|
11
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -48,4 +48,3 @@ export declare function tryCreateTenantId(value: unknown): TenantId | undefined;
|
|
|
48
48
|
export declare function isValidTenantId(value: unknown): value is TenantId;
|
|
49
49
|
/** Tenant lifecycle status. */
|
|
50
50
|
export type TenantStatus = "provisioning" | "active" | "inactive" | "suspended" | "deleting" | "deleted";
|
|
51
|
-
//# sourceMappingURL=tenantIdentity.d.ts.map
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/tenancy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Multi-tenant context and isolation with tenant resolution, AsyncLocalStorage propagation, resolver chains, trust levels, and guard middleware.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"!dist/.tsbuildinfo"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@zudojs/errors": "1.
|
|
29
|
-
"@zudojs/constants": "1.
|
|
30
|
-
"@zudojs/middleware": "1.1.
|
|
28
|
+
"@zudojs/errors": "1.4.0",
|
|
29
|
+
"@zudojs/constants": "1.2.0",
|
|
30
|
+
"@zudojs/middleware": "1.1.3"
|
|
31
31
|
},
|
|
32
32
|
"engines": {
|
|
33
33
|
"node": ">=24.0.0"
|