@plantops/contracts 0.1.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 +11 -0
- package/dist/audit.d.ts +149 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +87 -0
- package/dist/bindings.d.ts +125 -0
- package/dist/bindings.d.ts.map +1 -0
- package/dist/bindings.js +40 -0
- package/dist/clients.d.ts +240 -0
- package/dist/clients.d.ts.map +1 -0
- package/dist/clients.js +49 -0
- package/dist/constants.d.ts +91 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +92 -0
- package/dist/entitlements.d.ts +135 -0
- package/dist/entitlements.d.ts.map +1 -0
- package/dist/entitlements.js +43 -0
- package/dist/errors.d.ts +68 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +83 -0
- package/dist/grants.d.ts +70 -0
- package/dist/grants.d.ts.map +1 -0
- package/dist/grants.js +13 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/jwt.d.ts +155 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +49 -0
- package/dist/lib/contracts.d.ts +2 -0
- package/dist/lib/contracts.d.ts.map +1 -0
- package/dist/lib/contracts.js +3 -0
- package/dist/manifest.d.ts +136 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +22 -0
- package/dist/nav.d.ts +53 -0
- package/dist/nav.d.ts.map +1 -0
- package/dist/nav.js +25 -0
- package/dist/pagination.d.ts +28 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +27 -0
- package/dist/registry.d.ts +183 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +24 -0
- package/dist/roles.d.ts +215 -0
- package/dist/roles.d.ts.map +1 -0
- package/dist/roles.js +45 -0
- package/dist/scopes.d.ts +220 -0
- package/dist/scopes.d.ts.map +1 -0
- package/dist/scopes.js +108 -0
- package/dist/service-accounts.d.ts +81 -0
- package/dist/service-accounts.d.ts.map +1 -0
- package/dist/service-accounts.js +35 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/dist/type-assertions.d.ts +18 -0
- package/dist/type-assertions.d.ts.map +1 -0
- package/dist/type-assertions.js +8 -0
- package/dist/users.d.ts +364 -0
- package/dist/users.d.ts.map +1 -0
- package/dist/users.js +134 -0
- package/package.json +39 -0
package/dist/clients.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client contract — the tenant-provisioning surface (Doc 06 §5, Doc 02 §3).
|
|
3
|
+
*
|
|
4
|
+
* Three things live here, and they are three because provisioning a tenant is
|
|
5
|
+
* three separable decisions: **who the tenant is** ({@link ClientDTO}), **what
|
|
6
|
+
* they are allowed to run** ({@link ClientApplicationDTO}), and **who
|
|
7
|
+
* administers them on day one** ({@link ClientAdminDTO}). Doc 02 §3 lists them
|
|
8
|
+
* in that order and the API follows it, because each step is only meaningful
|
|
9
|
+
* once the one before it has happened.
|
|
10
|
+
*
|
|
11
|
+
* ## These are tenant rows described by a platform API
|
|
12
|
+
*
|
|
13
|
+
* Unlike {@link ApplicationDTO} and friends, everything here carries a
|
|
14
|
+
* `client_id` and lives behind RLS (Doc 07 §6). The surface is nonetheless
|
|
15
|
+
* platform-tier: a tenant does not create itself, and the endpoints are gated on
|
|
16
|
+
* `iam.platform.*`. That split is why the DTOs name the client explicitly rather
|
|
17
|
+
* than leaving it implied by the caller's token, the way the client-admin
|
|
18
|
+
* surfaces of Doc 06 §6–9 do.
|
|
19
|
+
*
|
|
20
|
+
* Field naming is snake_case, matching every other published shape in this
|
|
21
|
+
* package.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* A tenant's lifecycle states (Doc 01 §3.4).
|
|
25
|
+
*
|
|
26
|
+
* Two, and neither is `deleted`: a tenant is suspended, never removed. Doc 06 §5
|
|
27
|
+
* makes suspension the off switch — a suspended client's users cannot log in,
|
|
28
|
+
* which migration 0012's `auth_begin_session` enforces by re-checking
|
|
29
|
+
* `client.status` at the moment a session is created — while every scope node,
|
|
30
|
+
* role, binding and audit row it owns stays exactly where it was. Deleting a
|
|
31
|
+
* client would take an organisation's entire access history with it, and
|
|
32
|
+
* `on delete restrict` on every child table (migration 0003) makes sure nobody
|
|
33
|
+
* does it by accident.
|
|
34
|
+
*
|
|
35
|
+
* Spelled here rather than imported from `@plantops/db`, which contracts must
|
|
36
|
+
* not depend on — it has zero dependencies by design (Doc 08 §3). The Postgres
|
|
37
|
+
* enum is the same two values, and `libs/db`'s `entities.spec.ts` asserts the
|
|
38
|
+
* two spellings against each other so they cannot drift into a status the API
|
|
39
|
+
* accepts and the column rejects. Same arrangement as
|
|
40
|
+
* {@link ServiceAccountStatus}.
|
|
41
|
+
*/
|
|
42
|
+
export const ClientStatus = {
|
|
43
|
+
ACTIVE: 'active',
|
|
44
|
+
SUSPENDED: 'suspended',
|
|
45
|
+
};
|
|
46
|
+
export const CLIENT_STATUS_VALUES = [
|
|
47
|
+
ClientStatus.ACTIVE,
|
|
48
|
+
ClientStatus.SUSPENDED,
|
|
49
|
+
];
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants every project agrees on. Values that IAM and consuming
|
|
3
|
+
* modules must not disagree about live here (Doc 08 §3), never duplicated in a
|
|
4
|
+
* service.
|
|
5
|
+
*
|
|
6
|
+
* These are *defaults and invariants*, not deployment configuration: anything an
|
|
7
|
+
* operator may retune per environment is validated in `@plantops/config`, which
|
|
8
|
+
* uses these values as its defaults.
|
|
9
|
+
*/
|
|
10
|
+
/** `iss` claim on every token the IAM signs (Doc 03 §2). */
|
|
11
|
+
export declare const IAM_ISSUER = "plantops-iam";
|
|
12
|
+
/** Base path for the IAM surface (Doc 06 §1). */
|
|
13
|
+
export declare const IAM_ROUTE_PREFIX = "/iam";
|
|
14
|
+
/** Base path for the authentication surface (Doc 06 §1). */
|
|
15
|
+
export declare const AUTH_ROUTE_PREFIX = "/auth";
|
|
16
|
+
/**
|
|
17
|
+
* Leeway applied to `exp`/`iat`/`nbf` checks in the IAM *and* in every consuming
|
|
18
|
+
* module, absorbing clock drift between signer and verifiers (Doc 03 §6).
|
|
19
|
+
* Shared so verifiers cannot drift apart.
|
|
20
|
+
*/
|
|
21
|
+
export declare const CLOCK_SKEW_LEEWAY_SECONDS = 60;
|
|
22
|
+
/**
|
|
23
|
+
* The one signature algorithm the IAM issues and accepts (Doc 03 §1).
|
|
24
|
+
*
|
|
25
|
+
* Pinned as a constant because verification must never take the algorithm from
|
|
26
|
+
* the token being verified. A verifier that honours the incoming `alg` header
|
|
27
|
+
* accepts `alg: "none"`, and accepts an HS256 token forged with the *public*
|
|
28
|
+
* key as the HMAC secret — the two classic JWT breaks, both of which look like
|
|
29
|
+
* ordinary success. Comparing the header against this value and rejecting
|
|
30
|
+
* anything else closes them.
|
|
31
|
+
*/
|
|
32
|
+
export declare const JWT_SIGNING_ALGORITHM = "RS256";
|
|
33
|
+
/** Smallest RSA modulus the IAM will sign or verify with. */
|
|
34
|
+
export declare const JWT_MIN_RSA_KEY_BITS = 2048;
|
|
35
|
+
/**
|
|
36
|
+
* `max-age` on the JWKS response (Doc 03 §1 step 2).
|
|
37
|
+
*
|
|
38
|
+
* Rotation says to wait for JWKS propagation — "allow at least one cache TTL" —
|
|
39
|
+
* before switching signers. That step is only actionable if the TTL is a known
|
|
40
|
+
* number, so the endpoint publishes this one and the rotation tool reads the
|
|
41
|
+
* same constant when it computes the earliest safe activation time. Changing it
|
|
42
|
+
* changes both ends together.
|
|
43
|
+
*/
|
|
44
|
+
export declare const JWKS_CACHE_MAX_AGE_SECONDS = 300;
|
|
45
|
+
/** Human access-token lifetime — 15 min (Doc 03 §1). */
|
|
46
|
+
export declare const ACCESS_TOKEN_TTL_SECONDS = 900;
|
|
47
|
+
/** Refresh-token lifetime — 7 days, inside the 7–30 day band (Doc 03 §1). */
|
|
48
|
+
export declare const REFRESH_TOKEN_TTL_SECONDS = 604800;
|
|
49
|
+
/**
|
|
50
|
+
* Service-account access-token lifetime — 5 min. Doc 03 §5 caps these at ≤5 min
|
|
51
|
+
* because an ephemeral `sid` cannot be revoked mid-token; the TTL *is* the
|
|
52
|
+
* revocation window.
|
|
53
|
+
*/
|
|
54
|
+
export declare const SERVICE_ACCESS_TOKEN_TTL_SECONDS = 300;
|
|
55
|
+
export declare const SERVICE_ACCESS_TOKEN_MAX_TTL_SECONDS = 300;
|
|
56
|
+
/**
|
|
57
|
+
* Window during which the immediately-previous refresh token replays
|
|
58
|
+
* idempotently instead of being treated as compromise, so two legitimate
|
|
59
|
+
* clients refreshing at once do not kill a live session (Doc 03 §4: 10–30 s).
|
|
60
|
+
*/
|
|
61
|
+
export declare const REFRESH_REUSE_GRACE_SECONDS = 15;
|
|
62
|
+
export declare const REFRESH_REUSE_GRACE_MIN_SECONDS = 10;
|
|
63
|
+
export declare const REFRESH_REUSE_GRACE_MAX_SECONDS = 30;
|
|
64
|
+
/**
|
|
65
|
+
* Grants-cache TTL — the safety net behind invalidation, and the staleness bound
|
|
66
|
+
* on `role_binding.expires_at` (Doc 01 §4.5, Doc 04 §6: ≤10 min).
|
|
67
|
+
*/
|
|
68
|
+
export declare const GRANTS_CACHE_TTL_SECONDS = 600;
|
|
69
|
+
export declare const GRANTS_CACHE_MAX_TTL_SECONDS = 600;
|
|
70
|
+
/**
|
|
71
|
+
* ltree labels are `n_` + the node's UUID hex — id-derived, never name-derived,
|
|
72
|
+
* so a rename never rewrites a path (Doc 01 §3.5).
|
|
73
|
+
*/
|
|
74
|
+
export declare const SCOPE_PATH_LABEL_PREFIX = "n_";
|
|
75
|
+
/** Permission namespaces for the two administrative tiers (Doc 02 §1). */
|
|
76
|
+
export declare const PLATFORM_PERMISSION_NAMESPACE = "iam.platform";
|
|
77
|
+
export declare const CLIENT_PERMISSION_NAMESPACE = "iam.client";
|
|
78
|
+
/** Redis pub/sub channel carrying grant-invalidation events (Doc 04 §7). */
|
|
79
|
+
export declare const PERMS_INVALIDATED_CHANNEL = "perms.invalidated";
|
|
80
|
+
/**
|
|
81
|
+
* Key namespace for the revoked-`sid` cache (Doc 03 §6).
|
|
82
|
+
*
|
|
83
|
+
* Shared rather than private to the IAM because the IAM *writes* these keys and
|
|
84
|
+
* every consuming module *reads* them: a module that guesses a different prefix
|
|
85
|
+
* finds no revocations and honours force-logout never. One entry per revoked
|
|
86
|
+
* session, expiring on its own — see {@link revokedSessionKey}.
|
|
87
|
+
*/
|
|
88
|
+
export declare const REVOKED_SESSION_KEY_PREFIX = "revoked-sid:";
|
|
89
|
+
/** The revoked-`sid` cache key for a session id. */
|
|
90
|
+
export declare function revokedSessionKey(sessionId: string): string;
|
|
91
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,4DAA4D;AAC5D,eAAO,MAAM,UAAU,iBAAiB,CAAC;AAEzC,iDAAiD;AACjD,eAAO,MAAM,gBAAgB,SAAS,CAAC;AAEvC,4DAA4D;AAC5D,eAAO,MAAM,iBAAiB,UAAU,CAAC;AAEzC;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,UAAU,CAAC;AAE7C,6DAA6D;AAC7D,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,wDAAwD;AACxD,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C,6EAA6E;AAC7E,eAAO,MAAM,yBAAyB,SAAU,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,MAAM,CAAC;AACpD,eAAO,MAAM,oCAAoC,MAAM,CAAC;AAExD;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAC9C,eAAO,MAAM,+BAA+B,KAAK,CAAC;AAClD,eAAO,MAAM,+BAA+B,KAAK,CAAC;AAElD;;;GAGG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAC5C,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAEhD;;;GAGG;AACH,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,0EAA0E;AAC1E,eAAO,MAAM,6BAA6B,iBAAiB,CAAC;AAC5D,eAAO,MAAM,2BAA2B,eAAe,CAAC;AAExD,4EAA4E;AAC5E,eAAO,MAAM,yBAAyB,sBAAsB,CAAC;AAE7D;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,iBAAiB,CAAC;AAEzD,oDAAoD;AACpD,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3D"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants every project agrees on. Values that IAM and consuming
|
|
3
|
+
* modules must not disagree about live here (Doc 08 §3), never duplicated in a
|
|
4
|
+
* service.
|
|
5
|
+
*
|
|
6
|
+
* These are *defaults and invariants*, not deployment configuration: anything an
|
|
7
|
+
* operator may retune per environment is validated in `@plantops/config`, which
|
|
8
|
+
* uses these values as its defaults.
|
|
9
|
+
*/
|
|
10
|
+
/** `iss` claim on every token the IAM signs (Doc 03 §2). */
|
|
11
|
+
export const IAM_ISSUER = 'plantops-iam';
|
|
12
|
+
/** Base path for the IAM surface (Doc 06 §1). */
|
|
13
|
+
export const IAM_ROUTE_PREFIX = '/iam';
|
|
14
|
+
/** Base path for the authentication surface (Doc 06 §1). */
|
|
15
|
+
export const AUTH_ROUTE_PREFIX = '/auth';
|
|
16
|
+
/**
|
|
17
|
+
* Leeway applied to `exp`/`iat`/`nbf` checks in the IAM *and* in every consuming
|
|
18
|
+
* module, absorbing clock drift between signer and verifiers (Doc 03 §6).
|
|
19
|
+
* Shared so verifiers cannot drift apart.
|
|
20
|
+
*/
|
|
21
|
+
export const CLOCK_SKEW_LEEWAY_SECONDS = 60;
|
|
22
|
+
/**
|
|
23
|
+
* The one signature algorithm the IAM issues and accepts (Doc 03 §1).
|
|
24
|
+
*
|
|
25
|
+
* Pinned as a constant because verification must never take the algorithm from
|
|
26
|
+
* the token being verified. A verifier that honours the incoming `alg` header
|
|
27
|
+
* accepts `alg: "none"`, and accepts an HS256 token forged with the *public*
|
|
28
|
+
* key as the HMAC secret — the two classic JWT breaks, both of which look like
|
|
29
|
+
* ordinary success. Comparing the header against this value and rejecting
|
|
30
|
+
* anything else closes them.
|
|
31
|
+
*/
|
|
32
|
+
export const JWT_SIGNING_ALGORITHM = 'RS256';
|
|
33
|
+
/** Smallest RSA modulus the IAM will sign or verify with. */
|
|
34
|
+
export const JWT_MIN_RSA_KEY_BITS = 2048;
|
|
35
|
+
/**
|
|
36
|
+
* `max-age` on the JWKS response (Doc 03 §1 step 2).
|
|
37
|
+
*
|
|
38
|
+
* Rotation says to wait for JWKS propagation — "allow at least one cache TTL" —
|
|
39
|
+
* before switching signers. That step is only actionable if the TTL is a known
|
|
40
|
+
* number, so the endpoint publishes this one and the rotation tool reads the
|
|
41
|
+
* same constant when it computes the earliest safe activation time. Changing it
|
|
42
|
+
* changes both ends together.
|
|
43
|
+
*/
|
|
44
|
+
export const JWKS_CACHE_MAX_AGE_SECONDS = 300;
|
|
45
|
+
/** Human access-token lifetime — 15 min (Doc 03 §1). */
|
|
46
|
+
export const ACCESS_TOKEN_TTL_SECONDS = 900;
|
|
47
|
+
/** Refresh-token lifetime — 7 days, inside the 7–30 day band (Doc 03 §1). */
|
|
48
|
+
export const REFRESH_TOKEN_TTL_SECONDS = 604_800;
|
|
49
|
+
/**
|
|
50
|
+
* Service-account access-token lifetime — 5 min. Doc 03 §5 caps these at ≤5 min
|
|
51
|
+
* because an ephemeral `sid` cannot be revoked mid-token; the TTL *is* the
|
|
52
|
+
* revocation window.
|
|
53
|
+
*/
|
|
54
|
+
export const SERVICE_ACCESS_TOKEN_TTL_SECONDS = 300;
|
|
55
|
+
export const SERVICE_ACCESS_TOKEN_MAX_TTL_SECONDS = 300;
|
|
56
|
+
/**
|
|
57
|
+
* Window during which the immediately-previous refresh token replays
|
|
58
|
+
* idempotently instead of being treated as compromise, so two legitimate
|
|
59
|
+
* clients refreshing at once do not kill a live session (Doc 03 §4: 10–30 s).
|
|
60
|
+
*/
|
|
61
|
+
export const REFRESH_REUSE_GRACE_SECONDS = 15;
|
|
62
|
+
export const REFRESH_REUSE_GRACE_MIN_SECONDS = 10;
|
|
63
|
+
export const REFRESH_REUSE_GRACE_MAX_SECONDS = 30;
|
|
64
|
+
/**
|
|
65
|
+
* Grants-cache TTL — the safety net behind invalidation, and the staleness bound
|
|
66
|
+
* on `role_binding.expires_at` (Doc 01 §4.5, Doc 04 §6: ≤10 min).
|
|
67
|
+
*/
|
|
68
|
+
export const GRANTS_CACHE_TTL_SECONDS = 600;
|
|
69
|
+
export const GRANTS_CACHE_MAX_TTL_SECONDS = 600;
|
|
70
|
+
/**
|
|
71
|
+
* ltree labels are `n_` + the node's UUID hex — id-derived, never name-derived,
|
|
72
|
+
* so a rename never rewrites a path (Doc 01 §3.5).
|
|
73
|
+
*/
|
|
74
|
+
export const SCOPE_PATH_LABEL_PREFIX = 'n_';
|
|
75
|
+
/** Permission namespaces for the two administrative tiers (Doc 02 §1). */
|
|
76
|
+
export const PLATFORM_PERMISSION_NAMESPACE = 'iam.platform';
|
|
77
|
+
export const CLIENT_PERMISSION_NAMESPACE = 'iam.client';
|
|
78
|
+
/** Redis pub/sub channel carrying grant-invalidation events (Doc 04 §7). */
|
|
79
|
+
export const PERMS_INVALIDATED_CHANNEL = 'perms.invalidated';
|
|
80
|
+
/**
|
|
81
|
+
* Key namespace for the revoked-`sid` cache (Doc 03 §6).
|
|
82
|
+
*
|
|
83
|
+
* Shared rather than private to the IAM because the IAM *writes* these keys and
|
|
84
|
+
* every consuming module *reads* them: a module that guesses a different prefix
|
|
85
|
+
* finds no revocations and honours force-logout never. One entry per revoked
|
|
86
|
+
* session, expiring on its own — see {@link revokedSessionKey}.
|
|
87
|
+
*/
|
|
88
|
+
export const REVOKED_SESSION_KEY_PREFIX = 'revoked-sid:';
|
|
89
|
+
/** The revoked-`sid` cache key for a session id. */
|
|
90
|
+
export function revokedSessionKey(sessionId) {
|
|
91
|
+
return `${REVOKED_SESSION_KEY_PREFIX}${sessionId}`;
|
|
92
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entitlement contract — what a tenant is entitled to, readable by the tenant
|
|
3
|
+
* (`GET /iam/entitlements`).
|
|
4
|
+
*
|
|
5
|
+
* ## Why this exists next to `/iam/licence`
|
|
6
|
+
*
|
|
7
|
+
* `LicenceView` answers one question — when does this deployment's term run
|
|
8
|
+
* out — and answers it for a console header. It carries no ceilings and no
|
|
9
|
+
* per-application view, so a consuming module that needed to know "is the
|
|
10
|
+
* postmortem module enabled for this client, until when, and how many seats"
|
|
11
|
+
* had two options, both bad: embed `iam.platform.*` credentials in every
|
|
12
|
+
* consumer instance to call `GET /iam/clients/:id/applications`, or go
|
|
13
|
+
* without. This is the third option.
|
|
14
|
+
*
|
|
15
|
+
* `/iam/licence` stays exactly as it is. It is a shipped contract with a
|
|
16
|
+
* consumer, and this is a superset rather than a replacement.
|
|
17
|
+
*
|
|
18
|
+
* ## The IAM reports; it does not enforce
|
|
19
|
+
*
|
|
20
|
+
* Nothing here is an authorization decision. An application whose term has
|
|
21
|
+
* passed still resolves grants and still appears in navigation — see
|
|
22
|
+
* `apps/iam-api/src/licence/licence.guard.ts`, which exempts
|
|
23
|
+
* `/iam/permissions/resolve` and `/iam/introspect` from expiry blocking by
|
|
24
|
+
* name, on the grounds that an IAM which stops issuing tokens is a plant that
|
|
25
|
+
* stops running.
|
|
26
|
+
*
|
|
27
|
+
* So {@link ApplicationEntitlement.expired} is information a consumer acts on,
|
|
28
|
+
* and a consumer that ignores it has bought nothing. Doc 12 states the
|
|
29
|
+
* obligation normatively.
|
|
30
|
+
*
|
|
31
|
+
* ## This is not the grants cache
|
|
32
|
+
*
|
|
33
|
+
* Resolved grants invalidate on a `(client, subject)` version counter, exactly,
|
|
34
|
+
* on write. Entitlement state cannot: **nothing writes at the moment a term
|
|
35
|
+
* expires**, so no counter moves and no invalidation fires. It goes stale on a
|
|
36
|
+
* clock instead, which is what {@link EntitlementsView.as_of} is for.
|
|
37
|
+
*
|
|
38
|
+
* Caching this against the grants version counter produces a value that is
|
|
39
|
+
* wrong forever rather than briefly. Doc 12 forbids it in those words.
|
|
40
|
+
*
|
|
41
|
+
* Field naming is snake_case, matching every other published shape here.
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* The tenant-wide ceiling and how much of it is spent.
|
|
45
|
+
*
|
|
46
|
+
* `null` on any ceiling means unlimited — the reading every pre-licence
|
|
47
|
+
* deployment relies on (migration 0020). The counts are live at the moment of
|
|
48
|
+
* the read, not cached: they sit beside a write that is about to be allowed or
|
|
49
|
+
* refused, and a stale "you are under your limit" is worse than counting.
|
|
50
|
+
*/
|
|
51
|
+
export interface TenantEntitlement {
|
|
52
|
+
/**
|
|
53
|
+
* The **effective** term: whichever of the signed licence and the client
|
|
54
|
+
* row's subscription column expires first. `null` when nothing expires.
|
|
55
|
+
*/
|
|
56
|
+
expires_at: string | null;
|
|
57
|
+
/** True once `expires_at` has passed. Administrative writes are blocked. */
|
|
58
|
+
expired: boolean;
|
|
59
|
+
max_users: number | null;
|
|
60
|
+
max_sites: number | null;
|
|
61
|
+
/** Users in `active` status. The number `max_users` is compared against. */
|
|
62
|
+
active_users: number;
|
|
63
|
+
/**
|
|
64
|
+
* Scope nodes flagged `billable_site`.
|
|
65
|
+
*
|
|
66
|
+
* A platform-set flag, deliberately not derived from `scope_node.kind`:
|
|
67
|
+
* kind is tenant-supplied text (ADR 0002 §6), and a meter the customer can
|
|
68
|
+
* rename away is not a meter. See migration 0020.
|
|
69
|
+
*/
|
|
70
|
+
billable_sites: number;
|
|
71
|
+
}
|
|
72
|
+
/** One enabled application's own entitlement (`client_application`). */
|
|
73
|
+
export interface ApplicationEntitlement {
|
|
74
|
+
/** The application's stable key, e.g. `gatepass`. */
|
|
75
|
+
key: string;
|
|
76
|
+
name: string;
|
|
77
|
+
/**
|
|
78
|
+
* The tenant's switch for this application. A disabled application resolves
|
|
79
|
+
* no grants at all — this one *is* enforced, in `resolver.service.ts`.
|
|
80
|
+
*/
|
|
81
|
+
enabled: boolean;
|
|
82
|
+
/** This application's own term, independent of the tenant's. */
|
|
83
|
+
expires_at: string | null;
|
|
84
|
+
/**
|
|
85
|
+
* True once this application's own term has passed.
|
|
86
|
+
*
|
|
87
|
+
* **Nothing in the IAM acts on this.** Grants still resolve and navigation
|
|
88
|
+
* still renders; the consuming module is what must refuse. See the header.
|
|
89
|
+
*/
|
|
90
|
+
expired: boolean;
|
|
91
|
+
max_users: number | null;
|
|
92
|
+
max_sites: number | null;
|
|
93
|
+
}
|
|
94
|
+
/** `GET /iam/entitlements` (Doc 12). */
|
|
95
|
+
export interface EntitlementsView {
|
|
96
|
+
/**
|
|
97
|
+
* When this snapshot was taken.
|
|
98
|
+
*
|
|
99
|
+
* Consumers bound their cache staleness against this rather than against
|
|
100
|
+
* their own clock, because the interesting skew is between the reader and
|
|
101
|
+
* the IAM, not between the reader and real time.
|
|
102
|
+
*/
|
|
103
|
+
as_of: string;
|
|
104
|
+
tenant: TenantEntitlement;
|
|
105
|
+
/**
|
|
106
|
+
* Every application enabled for this client, plus any that were enabled and
|
|
107
|
+
* later switched off — a disabled row is preserved, not deleted (Doc 02 §7),
|
|
108
|
+
* and a consumer that vanished from this list could not tell "never had it"
|
|
109
|
+
* from "lost it".
|
|
110
|
+
*/
|
|
111
|
+
applications: ApplicationEntitlement[];
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* `GET /iam/licence` (Doc 11 §10) — the effective term, and nothing else.
|
|
115
|
+
*
|
|
116
|
+
* Predates {@link EntitlementsView} and is kept beside it rather than folded
|
|
117
|
+
* into it: the console header that renders a thirty-day warning wants one date
|
|
118
|
+
* and should not pay for a per-application read to get it.
|
|
119
|
+
*
|
|
120
|
+
* The term reported is the effective one — whichever of the signed licence and
|
|
121
|
+
* the client row's subscription column expires first. Unlike the per-module
|
|
122
|
+
* terms in {@link ApplicationEntitlement}, this one **is** enforced: once past,
|
|
123
|
+
* administrative writes are refused, while `/auth/*`,
|
|
124
|
+
* `/iam/permissions/resolve` and `/iam/introspect` keep working.
|
|
125
|
+
*/
|
|
126
|
+
export interface LicenceView {
|
|
127
|
+
/** Whether any term applies at all — a licence file, a subscription, or both. */
|
|
128
|
+
configured: boolean;
|
|
129
|
+
expired: boolean;
|
|
130
|
+
/** ISO instant of the effective term; `null` when nothing expires. */
|
|
131
|
+
expires_at: string | null;
|
|
132
|
+
/** Whole days to the effective term; negative once past. */
|
|
133
|
+
days_remaining: number | null;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=entitlements.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entitlements.d.ts","sourceRoot":"","sources":["../src/entitlements.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wEAAwE;AACxE,MAAM,WAAW,sBAAsB;IACrC,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,gEAAgE;IAChE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,wCAAwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,iBAAiB,CAAC;IAC1B;;;;;OAKG;IACH,YAAY,EAAE,sBAAsB,EAAE,CAAC;CACxC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,WAAW;IAC1B,iFAAiF;IACjF,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,sEAAsE;IACtE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,4DAA4D;IAC5D,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entitlement contract — what a tenant is entitled to, readable by the tenant
|
|
3
|
+
* (`GET /iam/entitlements`).
|
|
4
|
+
*
|
|
5
|
+
* ## Why this exists next to `/iam/licence`
|
|
6
|
+
*
|
|
7
|
+
* `LicenceView` answers one question — when does this deployment's term run
|
|
8
|
+
* out — and answers it for a console header. It carries no ceilings and no
|
|
9
|
+
* per-application view, so a consuming module that needed to know "is the
|
|
10
|
+
* postmortem module enabled for this client, until when, and how many seats"
|
|
11
|
+
* had two options, both bad: embed `iam.platform.*` credentials in every
|
|
12
|
+
* consumer instance to call `GET /iam/clients/:id/applications`, or go
|
|
13
|
+
* without. This is the third option.
|
|
14
|
+
*
|
|
15
|
+
* `/iam/licence` stays exactly as it is. It is a shipped contract with a
|
|
16
|
+
* consumer, and this is a superset rather than a replacement.
|
|
17
|
+
*
|
|
18
|
+
* ## The IAM reports; it does not enforce
|
|
19
|
+
*
|
|
20
|
+
* Nothing here is an authorization decision. An application whose term has
|
|
21
|
+
* passed still resolves grants and still appears in navigation — see
|
|
22
|
+
* `apps/iam-api/src/licence/licence.guard.ts`, which exempts
|
|
23
|
+
* `/iam/permissions/resolve` and `/iam/introspect` from expiry blocking by
|
|
24
|
+
* name, on the grounds that an IAM which stops issuing tokens is a plant that
|
|
25
|
+
* stops running.
|
|
26
|
+
*
|
|
27
|
+
* So {@link ApplicationEntitlement.expired} is information a consumer acts on,
|
|
28
|
+
* and a consumer that ignores it has bought nothing. Doc 12 states the
|
|
29
|
+
* obligation normatively.
|
|
30
|
+
*
|
|
31
|
+
* ## This is not the grants cache
|
|
32
|
+
*
|
|
33
|
+
* Resolved grants invalidate on a `(client, subject)` version counter, exactly,
|
|
34
|
+
* on write. Entitlement state cannot: **nothing writes at the moment a term
|
|
35
|
+
* expires**, so no counter moves and no invalidation fires. It goes stale on a
|
|
36
|
+
* clock instead, which is what {@link EntitlementsView.as_of} is for.
|
|
37
|
+
*
|
|
38
|
+
* Caching this against the grants version counter produces a value that is
|
|
39
|
+
* wrong forever rather than briefly. Doc 12 forbids it in those words.
|
|
40
|
+
*
|
|
41
|
+
* Field naming is snake_case, matching every other published shape here.
|
|
42
|
+
*/
|
|
43
|
+
export {};
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error contract — the envelope and the code table from Doc 06 §2.
|
|
3
|
+
*
|
|
4
|
+
* The table there is authoritative and closed: adding a code is a spec change,
|
|
5
|
+
* not an implementation detail, because consumers (`iam-client`, admin-web,
|
|
6
|
+
* every future module) branch on these values.
|
|
7
|
+
*/
|
|
8
|
+
export declare const IamErrorCode: {
|
|
9
|
+
/** 400 — request failed validation; see `details` for the offending fields. */
|
|
10
|
+
readonly VALIDATION_FAILED: "VALIDATION_FAILED";
|
|
11
|
+
/** 401 — no (or unverifiable) bearer token. */
|
|
12
|
+
readonly AUTH_REQUIRED: "AUTH_REQUIRED";
|
|
13
|
+
/** 401 — login rejected. Generic by design: never hints at user existence. */
|
|
14
|
+
readonly INVALID_CREDENTIALS: "INVALID_CREDENTIALS";
|
|
15
|
+
/** 403 — subject does not hold the required permission. */
|
|
16
|
+
readonly PERMISSION_DENIED: "PERMISSION_DENIED";
|
|
17
|
+
/** 403 — permission held, but not at a scope node covering the target. */
|
|
18
|
+
readonly SCOPE_DENIED: "SCOPE_DENIED";
|
|
19
|
+
/** 404 — target absent (within the caller's tenant). */
|
|
20
|
+
readonly NOT_FOUND: "NOT_FOUND";
|
|
21
|
+
/** 409 — duplicate key or cross-tenant violation. */
|
|
22
|
+
readonly CONFLICT: "CONFLICT";
|
|
23
|
+
/** 423 — account is locked (Doc 03 §8). */
|
|
24
|
+
readonly ACCOUNT_LOCKED: "ACCOUNT_LOCKED";
|
|
25
|
+
/** 429 — throttled. */
|
|
26
|
+
readonly RATE_LIMITED: "RATE_LIMITED";
|
|
27
|
+
/**
|
|
28
|
+
* 500 — the request failed for a reason the server did not anticipate.
|
|
29
|
+
*
|
|
30
|
+
* The one code a caller can never act on, and the one every caller must be
|
|
31
|
+
* able to parse: without it, an unhandled exception escapes as some other
|
|
32
|
+
* body shape and `isIamErrorResponse` rejects the server's own error. The
|
|
33
|
+
* `message` is always generic — an exception's text can carry a query, a
|
|
34
|
+
* connection string, or a row — and `requestId` is what correlates it with
|
|
35
|
+
* the logged stack (Doc 06 §2, added Session 6).
|
|
36
|
+
*/
|
|
37
|
+
readonly INTERNAL_ERROR: "INTERNAL_ERROR";
|
|
38
|
+
};
|
|
39
|
+
export type IamErrorCode = (typeof IamErrorCode)[keyof typeof IamErrorCode];
|
|
40
|
+
/** Every code, in Doc 06 §2 table order. */
|
|
41
|
+
export declare const IAM_ERROR_CODES: readonly ["VALIDATION_FAILED", "AUTH_REQUIRED", "INVALID_CREDENTIALS", "PERMISSION_DENIED", "SCOPE_DENIED", "NOT_FOUND", "CONFLICT", "ACCOUNT_LOCKED", "RATE_LIMITED", "INTERNAL_ERROR"];
|
|
42
|
+
/** code → HTTP status, per the Doc 06 §2 table. */
|
|
43
|
+
export declare const IAM_ERROR_HTTP_STATUS: Readonly<Record<IamErrorCode, number>>;
|
|
44
|
+
/** One field-level complaint accompanying `VALIDATION_FAILED`. */
|
|
45
|
+
export interface IamErrorDetail {
|
|
46
|
+
field: string;
|
|
47
|
+
message: string;
|
|
48
|
+
}
|
|
49
|
+
export interface IamError {
|
|
50
|
+
code: IamErrorCode;
|
|
51
|
+
/**
|
|
52
|
+
* Human-readable message. Denials must stay non-committal: a 403 never
|
|
53
|
+
* reveals whether the target exists in another tenant (Doc 06 §2).
|
|
54
|
+
*/
|
|
55
|
+
message: string;
|
|
56
|
+
/** Correlates the response with server logs and audit records. */
|
|
57
|
+
requestId: string;
|
|
58
|
+
details?: IamErrorDetail[];
|
|
59
|
+
}
|
|
60
|
+
/** The single response shape for every error the IAM returns (Doc 06 §2). */
|
|
61
|
+
export interface IamErrorResponse {
|
|
62
|
+
error: IamError;
|
|
63
|
+
}
|
|
64
|
+
export declare function isIamErrorCode(value: unknown): value is IamErrorCode;
|
|
65
|
+
export declare function isIamErrorResponse(value: unknown): value is IamErrorResponse;
|
|
66
|
+
/** HTTP status for a code — the mapping consumers and the filter share. */
|
|
67
|
+
export declare function httpStatusForIamError(code: IamErrorCode): number;
|
|
68
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,YAAY;IACvB,+EAA+E;;IAE/E,+CAA+C;;IAE/C,8EAA8E;;IAE9E,2DAA2D;;IAE3D,0EAA0E;;IAE1E,wDAAwD;;IAExD,qDAAqD;;IAErD,2CAA2C;;IAE3C,uBAAuB;;IAEvB;;;;;;;;;OASG;;CAEK,CAAC;AACX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AAE5E,4CAA4C;AAC5C,eAAO,MAAM,eAAe,0LAWgB,CAAC;AAE7C,mDAAmD;AACnD,eAAO,MAAM,qBAAqB,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAYrE,CAAC;AAEL,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,YAAY,CAAC;IACnB;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,QAAQ,CAAC;CACjB;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAKpE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,gBAAgB,CAY5E;AAED,2EAA2E;AAC3E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAEhE"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error contract — the envelope and the code table from Doc 06 §2.
|
|
3
|
+
*
|
|
4
|
+
* The table there is authoritative and closed: adding a code is a spec change,
|
|
5
|
+
* not an implementation detail, because consumers (`iam-client`, admin-web,
|
|
6
|
+
* every future module) branch on these values.
|
|
7
|
+
*/
|
|
8
|
+
export const IamErrorCode = {
|
|
9
|
+
/** 400 — request failed validation; see `details` for the offending fields. */
|
|
10
|
+
VALIDATION_FAILED: 'VALIDATION_FAILED',
|
|
11
|
+
/** 401 — no (or unverifiable) bearer token. */
|
|
12
|
+
AUTH_REQUIRED: 'AUTH_REQUIRED',
|
|
13
|
+
/** 401 — login rejected. Generic by design: never hints at user existence. */
|
|
14
|
+
INVALID_CREDENTIALS: 'INVALID_CREDENTIALS',
|
|
15
|
+
/** 403 — subject does not hold the required permission. */
|
|
16
|
+
PERMISSION_DENIED: 'PERMISSION_DENIED',
|
|
17
|
+
/** 403 — permission held, but not at a scope node covering the target. */
|
|
18
|
+
SCOPE_DENIED: 'SCOPE_DENIED',
|
|
19
|
+
/** 404 — target absent (within the caller's tenant). */
|
|
20
|
+
NOT_FOUND: 'NOT_FOUND',
|
|
21
|
+
/** 409 — duplicate key or cross-tenant violation. */
|
|
22
|
+
CONFLICT: 'CONFLICT',
|
|
23
|
+
/** 423 — account is locked (Doc 03 §8). */
|
|
24
|
+
ACCOUNT_LOCKED: 'ACCOUNT_LOCKED',
|
|
25
|
+
/** 429 — throttled. */
|
|
26
|
+
RATE_LIMITED: 'RATE_LIMITED',
|
|
27
|
+
/**
|
|
28
|
+
* 500 — the request failed for a reason the server did not anticipate.
|
|
29
|
+
*
|
|
30
|
+
* The one code a caller can never act on, and the one every caller must be
|
|
31
|
+
* able to parse: without it, an unhandled exception escapes as some other
|
|
32
|
+
* body shape and `isIamErrorResponse` rejects the server's own error. The
|
|
33
|
+
* `message` is always generic — an exception's text can carry a query, a
|
|
34
|
+
* connection string, or a row — and `requestId` is what correlates it with
|
|
35
|
+
* the logged stack (Doc 06 §2, added Session 6).
|
|
36
|
+
*/
|
|
37
|
+
INTERNAL_ERROR: 'INTERNAL_ERROR',
|
|
38
|
+
};
|
|
39
|
+
/** Every code, in Doc 06 §2 table order. */
|
|
40
|
+
export const IAM_ERROR_CODES = [
|
|
41
|
+
IamErrorCode.VALIDATION_FAILED,
|
|
42
|
+
IamErrorCode.AUTH_REQUIRED,
|
|
43
|
+
IamErrorCode.INVALID_CREDENTIALS,
|
|
44
|
+
IamErrorCode.PERMISSION_DENIED,
|
|
45
|
+
IamErrorCode.SCOPE_DENIED,
|
|
46
|
+
IamErrorCode.NOT_FOUND,
|
|
47
|
+
IamErrorCode.CONFLICT,
|
|
48
|
+
IamErrorCode.ACCOUNT_LOCKED,
|
|
49
|
+
IamErrorCode.RATE_LIMITED,
|
|
50
|
+
IamErrorCode.INTERNAL_ERROR,
|
|
51
|
+
];
|
|
52
|
+
/** code → HTTP status, per the Doc 06 §2 table. */
|
|
53
|
+
export const IAM_ERROR_HTTP_STATUS = Object.freeze({
|
|
54
|
+
[IamErrorCode.VALIDATION_FAILED]: 400,
|
|
55
|
+
[IamErrorCode.AUTH_REQUIRED]: 401,
|
|
56
|
+
[IamErrorCode.INVALID_CREDENTIALS]: 401,
|
|
57
|
+
[IamErrorCode.PERMISSION_DENIED]: 403,
|
|
58
|
+
[IamErrorCode.SCOPE_DENIED]: 403,
|
|
59
|
+
[IamErrorCode.NOT_FOUND]: 404,
|
|
60
|
+
[IamErrorCode.CONFLICT]: 409,
|
|
61
|
+
[IamErrorCode.ACCOUNT_LOCKED]: 423,
|
|
62
|
+
[IamErrorCode.RATE_LIMITED]: 429,
|
|
63
|
+
[IamErrorCode.INTERNAL_ERROR]: 500,
|
|
64
|
+
});
|
|
65
|
+
export function isIamErrorCode(value) {
|
|
66
|
+
return (typeof value === 'string' &&
|
|
67
|
+
IAM_ERROR_CODES.includes(value));
|
|
68
|
+
}
|
|
69
|
+
export function isIamErrorResponse(value) {
|
|
70
|
+
if (typeof value !== 'object' || value === null || !('error' in value)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
const { error } = value;
|
|
74
|
+
return (typeof error === 'object' &&
|
|
75
|
+
error !== null &&
|
|
76
|
+
isIamErrorCode(error.code) &&
|
|
77
|
+
typeof error.message === 'string' &&
|
|
78
|
+
typeof error.requestId === 'string');
|
|
79
|
+
}
|
|
80
|
+
/** HTTP status for a code — the mapping consumers and the filter share. */
|
|
81
|
+
export function httpStatusForIamError(code) {
|
|
82
|
+
return IAM_ERROR_HTTP_STATUS[code];
|
|
83
|
+
}
|
package/dist/grants.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authorization contract — the resolved WHO × WHAT × WHERE answer every module
|
|
3
|
+
* consumes (Doc 04 §4–6).
|
|
4
|
+
*/
|
|
5
|
+
import { SubjectType } from './jwt.js';
|
|
6
|
+
/**
|
|
7
|
+
* A namespaced permission key, `app.resource.action` (Doc 01 §3.2).
|
|
8
|
+
*
|
|
9
|
+
* Deliberately a plain `string`: permission keys are **data**, created at
|
|
10
|
+
* runtime through the registry. The IAM must never enumerate them in code
|
|
11
|
+
* (Doc 02 §8) — a union type here would be exactly that.
|
|
12
|
+
*/
|
|
13
|
+
export type PermissionKey = string;
|
|
14
|
+
/**
|
|
15
|
+
* A materialized `ltree` path identifying a scope-tree node,
|
|
16
|
+
* `n_<hex>.n_<hex>.…` (Doc 01 §3.5). Coverage is the prefix test `<@`.
|
|
17
|
+
*/
|
|
18
|
+
export type ScopePath = string;
|
|
19
|
+
/**
|
|
20
|
+
* A subject's complete grant set (Doc 04 §4.1).
|
|
21
|
+
*
|
|
22
|
+
* `scopes[permKey]` is the **minimal covering set** of paths for that
|
|
23
|
+
* permission: if an ancestor path is present, its descendants are dropped
|
|
24
|
+
* because `<@` already covers them. Consumers may rely on that minimization.
|
|
25
|
+
*/
|
|
26
|
+
export interface ResolvedGrants {
|
|
27
|
+
/** Flat list of held permission keys, for quick "has permission" checks. */
|
|
28
|
+
permissions: PermissionKey[];
|
|
29
|
+
/** permission key → minimal set of covering scope paths. */
|
|
30
|
+
scopes: Record<PermissionKey, ScopePath[]>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The cached form of {@link ResolvedGrants} (Doc 04 §6). `v` is the
|
|
34
|
+
* `(client, subject)` version counter; a mismatch against the authoritative
|
|
35
|
+
* counter is treated as a cache miss, which is how role-level changes
|
|
36
|
+
* invalidate without enumerating subjects (Doc 04 §7).
|
|
37
|
+
*/
|
|
38
|
+
export interface CachedGrants extends ResolvedGrants {
|
|
39
|
+
v: number;
|
|
40
|
+
}
|
|
41
|
+
/** `POST /iam/permissions/check` body (Doc 06 §11). */
|
|
42
|
+
export interface PermissionCheckRequest {
|
|
43
|
+
permission: PermissionKey;
|
|
44
|
+
scopeNodeId: string;
|
|
45
|
+
}
|
|
46
|
+
export interface PermissionCheckResponse {
|
|
47
|
+
allowed: boolean;
|
|
48
|
+
}
|
|
49
|
+
/** Optional narrowing of `/iam/permissions/resolve` to one application's slice. */
|
|
50
|
+
export interface ResolveQuery {
|
|
51
|
+
applicationId?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Payload of the `perms.invalidated` event (Doc 04 §7). Either a specific
|
|
55
|
+
* subject or a role whose bound subjects are affected.
|
|
56
|
+
*/
|
|
57
|
+
export interface PermsInvalidatedEvent {
|
|
58
|
+
clientId: string;
|
|
59
|
+
subjectId?: string;
|
|
60
|
+
subjectType?: SubjectType;
|
|
61
|
+
roleId?: string;
|
|
62
|
+
}
|
|
63
|
+
/** Redis key prefix for cached grants (Doc 04 §6). */
|
|
64
|
+
export declare const GRANTS_CACHE_KEY_PREFIX = "perms";
|
|
65
|
+
/**
|
|
66
|
+
* `perms:{clientId}:{subjectType}:{subjectId}` — the cache key shape from
|
|
67
|
+
* Doc 04 §6. A function so IAM and every cache holder derive it identically.
|
|
68
|
+
*/
|
|
69
|
+
export declare function grantsCacheKey(clientId: string, subjectType: SubjectType, subjectId: string): string;
|
|
70
|
+
//# sourceMappingURL=grants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grants.d.ts","sourceRoot":"","sources":["../src/grants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC;AAEnC;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC,aAAa,EAAE,SAAS,EAAE,CAAC,CAAC;CAC5C;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD,CAAC,EAAE,MAAM,CAAC;CACX;AAED,uDAAuD;AACvD,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,aAAa,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,mFAAmF;AACnF,MAAM,WAAW,YAAY;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,sDAAsD;AACtD,eAAO,MAAM,uBAAuB,UAAU,CAAC;AAE/C;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,WAAW,EACxB,SAAS,EAAE,MAAM,GAChB,MAAM,CAER"}
|