@plantops/iam-client 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/auth.d.ts +135 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +168 -0
- package/dist/client.d.ts +110 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +135 -0
- package/dist/endpoints/applications.d.ts +48 -0
- package/dist/endpoints/applications.d.ts.map +1 -0
- package/dist/endpoints/applications.js +30 -0
- package/dist/endpoints/audit.d.ts +43 -0
- package/dist/endpoints/audit.d.ts.map +1 -0
- package/dist/endpoints/audit.js +41 -0
- package/dist/endpoints/auth.d.ts +56 -0
- package/dist/endpoints/auth.d.ts.map +1 -0
- package/dist/endpoints/auth.js +88 -0
- package/dist/endpoints/authz.d.ts +31 -0
- package/dist/endpoints/authz.d.ts.map +1 -0
- package/dist/endpoints/authz.js +39 -0
- package/dist/endpoints/bindings.d.ts +17 -0
- package/dist/endpoints/bindings.d.ts.map +1 -0
- package/dist/endpoints/bindings.js +16 -0
- package/dist/endpoints/clients.d.ts +35 -0
- package/dist/endpoints/clients.d.ts.map +1 -0
- package/dist/endpoints/clients.js +31 -0
- package/dist/endpoints/entitlements.d.ts +40 -0
- package/dist/endpoints/entitlements.d.ts.map +1 -0
- package/dist/endpoints/entitlements.js +43 -0
- package/dist/endpoints/index.d.ts +22 -0
- package/dist/endpoints/index.d.ts.map +1 -0
- package/dist/endpoints/index.js +21 -0
- package/dist/endpoints/navigation.d.ts +25 -0
- package/dist/endpoints/navigation.d.ts.map +1 -0
- package/dist/endpoints/navigation.js +24 -0
- package/dist/endpoints/roles.d.ts +25 -0
- package/dist/endpoints/roles.d.ts.map +1 -0
- package/dist/endpoints/roles.js +21 -0
- package/dist/endpoints/scopes.d.ts +19 -0
- package/dist/endpoints/scopes.d.ts.map +1 -0
- package/dist/endpoints/scopes.js +18 -0
- package/dist/endpoints/service-accounts.d.ts +19 -0
- package/dist/endpoints/service-accounts.d.ts.map +1 -0
- package/dist/endpoints/service-accounts.js +19 -0
- package/dist/endpoints/users.d.ts +39 -0
- package/dist/endpoints/users.d.ts.map +1 -0
- package/dist/endpoints/users.js +24 -0
- package/dist/errors.d.ts +70 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +113 -0
- package/dist/http.d.ts +113 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +164 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/lib/iam-client.d.ts +2 -0
- package/dist/lib/iam-client.d.ts.map +1 -0
- package/dist/lib/iam-client.js +3 -0
- package/dist/resolve-cache.d.ts +65 -0
- package/dist/resolve-cache.d.ts.map +1 -0
- package/dist/resolve-cache.js +98 -0
- package/dist/testing/mock-server.d.ts +65 -0
- package/dist/testing/mock-server.d.ts.map +1 -0
- package/dist/testing/mock-server.js +107 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/iam/audit` — the queryable side of governance, Doc 06 §12.
|
|
3
|
+
*
|
|
4
|
+
* The last gap in "every Doc 06 endpoint has a typed method". `client.ts` used
|
|
5
|
+
* to explain why it was open: Session 25 had not built the endpoint and
|
|
6
|
+
* `@plantops/contracts` typed no audit record, so a method here would have had
|
|
7
|
+
* to invent both. Both exist now, so this closes it.
|
|
8
|
+
*
|
|
9
|
+
* ## One route, two tiers, and no tier parameter
|
|
10
|
+
*
|
|
11
|
+
* `iam.platform.audit.read` and `iam.client.audit.read` both admit, and which of
|
|
12
|
+
* them the caller holds decides nothing about the request. What a reader sees is
|
|
13
|
+
* decided by the `audit_trail_read` policy alone (Doc 07 §6, Doc 10 §7): a client
|
|
14
|
+
* admin their own tenant's rows, a platform admin everything including the
|
|
15
|
+
* `client_id IS NULL` rows that record platform-level acts. So there is no
|
|
16
|
+
* `?tier=`, and `client_id` only ever *narrows* what RLS already allows — a
|
|
17
|
+
* tenant the caller may not see is an empty page, never a 403.
|
|
18
|
+
*
|
|
19
|
+
* ## The export answers CSV, and says so
|
|
20
|
+
*
|
|
21
|
+
* It takes the same filters and no page: it is the whole of the filter or it is
|
|
22
|
+
* refused, because a truncated compliance export is indistinguishable from a
|
|
23
|
+
* complete one. `accept: 'text'` is what lets it come back through the same
|
|
24
|
+
* transport as everything else — same token, same error mapping — rather than
|
|
25
|
+
* being fetched around the outside of this library.
|
|
26
|
+
*
|
|
27
|
+
* There is no mutating route here and there is not meant to be (Doc 10 §7).
|
|
28
|
+
*/
|
|
29
|
+
import type { AuditExportQuery, AuditQuery, AuditRecordDTO, Paginated } from '@plantops/contracts';
|
|
30
|
+
import type { Requester } from '../http.js';
|
|
31
|
+
export interface AuditApi {
|
|
32
|
+
list(query?: AuditQuery): Promise<Paginated<AuditRecordDTO>>;
|
|
33
|
+
/**
|
|
34
|
+
* The same filter as a CSV document, which the export itself audits
|
|
35
|
+
* (`audit.exported`, Doc 10 §7).
|
|
36
|
+
*
|
|
37
|
+
* Returns the CSV text. Handing it to the browser as a file is the caller's
|
|
38
|
+
* job — this library runs in Node too, where there is nothing to download to.
|
|
39
|
+
*/
|
|
40
|
+
export(query?: AuditExportQuery): Promise<string>;
|
|
41
|
+
}
|
|
42
|
+
export declare function auditEndpoints(request: Requester): AuditApi;
|
|
43
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/endpoints/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,SAAS,EACV,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC,CAAC;IAC7D;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACnD;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,GAAG,QAAQ,CAa3D"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/iam/audit` — the queryable side of governance, Doc 06 §12.
|
|
3
|
+
*
|
|
4
|
+
* The last gap in "every Doc 06 endpoint has a typed method". `client.ts` used
|
|
5
|
+
* to explain why it was open: Session 25 had not built the endpoint and
|
|
6
|
+
* `@plantops/contracts` typed no audit record, so a method here would have had
|
|
7
|
+
* to invent both. Both exist now, so this closes it.
|
|
8
|
+
*
|
|
9
|
+
* ## One route, two tiers, and no tier parameter
|
|
10
|
+
*
|
|
11
|
+
* `iam.platform.audit.read` and `iam.client.audit.read` both admit, and which of
|
|
12
|
+
* them the caller holds decides nothing about the request. What a reader sees is
|
|
13
|
+
* decided by the `audit_trail_read` policy alone (Doc 07 §6, Doc 10 §7): a client
|
|
14
|
+
* admin their own tenant's rows, a platform admin everything including the
|
|
15
|
+
* `client_id IS NULL` rows that record platform-level acts. So there is no
|
|
16
|
+
* `?tier=`, and `client_id` only ever *narrows* what RLS already allows — a
|
|
17
|
+
* tenant the caller may not see is an empty page, never a 403.
|
|
18
|
+
*
|
|
19
|
+
* ## The export answers CSV, and says so
|
|
20
|
+
*
|
|
21
|
+
* It takes the same filters and no page: it is the whole of the filter or it is
|
|
22
|
+
* refused, because a truncated compliance export is indistinguishable from a
|
|
23
|
+
* complete one. `accept: 'text'` is what lets it come back through the same
|
|
24
|
+
* transport as everything else — same token, same error mapping — rather than
|
|
25
|
+
* being fetched around the outside of this library.
|
|
26
|
+
*
|
|
27
|
+
* There is no mutating route here and there is not meant to be (Doc 10 §7).
|
|
28
|
+
*/
|
|
29
|
+
import { IAM_ROUTE_PREFIX } from '@plantops/contracts';
|
|
30
|
+
export function auditEndpoints(request) {
|
|
31
|
+
const base = `${IAM_ROUTE_PREFIX}/audit`;
|
|
32
|
+
return {
|
|
33
|
+
list: (query) => request({ method: 'GET', path: base, query: { ...query } }),
|
|
34
|
+
export: (query) => request({
|
|
35
|
+
method: 'GET',
|
|
36
|
+
path: `${base}/export`,
|
|
37
|
+
query: { ...query },
|
|
38
|
+
accept: 'text',
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/auth/*` — Doc 06 §3.
|
|
3
|
+
*
|
|
4
|
+
* The only endpoint module that touches state: a login, a refresh and a service
|
|
5
|
+
* token all produce credentials, and dropping them into {@link TokenSession} is
|
|
6
|
+
* the whole reason a consumer never has to think about the `Authorization`
|
|
7
|
+
* header. Logout does the reverse on both sides of the wire — the server
|
|
8
|
+
* revokes the session, the client forgets the tokens — and both happen even if
|
|
9
|
+
* the call fails, because a caller who asked to be logged out is logged out.
|
|
10
|
+
*/
|
|
11
|
+
import type { AccessTokenResponse, LoginRequest, ServiceTokenRequest, SessionDTO, TokenPairResponse } from '@plantops/contracts';
|
|
12
|
+
import type { TokenSession } from '../auth.js';
|
|
13
|
+
import type { Requester } from '../http.js';
|
|
14
|
+
/**
|
|
15
|
+
* `POST /auth/login`, plus the device label.
|
|
16
|
+
*
|
|
17
|
+
* The label is what makes the session list of Doc 06 §3 actionable — "Gate-3
|
|
18
|
+
* Terminal" rather than a uuid — and the endpoint has always accepted it. It is
|
|
19
|
+
* absent from `LoginRequest` in `@plantops/contracts`, so it is added here
|
|
20
|
+
* rather than silently dropped.
|
|
21
|
+
*/
|
|
22
|
+
export interface LoginInput extends LoginRequest {
|
|
23
|
+
device_label?: string;
|
|
24
|
+
}
|
|
25
|
+
/** `POST /auth/password/reset-request` (Doc 06 §3). */
|
|
26
|
+
export interface PasswordResetRequestInput {
|
|
27
|
+
email: string;
|
|
28
|
+
client_slug: string;
|
|
29
|
+
}
|
|
30
|
+
/** `POST /auth/password/reset` (Doc 06 §3). */
|
|
31
|
+
export interface PasswordResetInput {
|
|
32
|
+
token: string;
|
|
33
|
+
new_password: string;
|
|
34
|
+
}
|
|
35
|
+
export interface AuthApi {
|
|
36
|
+
/** Human login. Stores the pair; the client is authenticated when it returns. */
|
|
37
|
+
login(input: LoginInput): Promise<TokenPairResponse>;
|
|
38
|
+
/**
|
|
39
|
+
* Renews explicitly. Shares the single in-flight exchange with the automatic
|
|
40
|
+
* refresh, so calling it during a burst costs nothing extra.
|
|
41
|
+
*/
|
|
42
|
+
refresh(): Promise<TokenPairResponse>;
|
|
43
|
+
/** Service-account client-credentials exchange. No refresh token by design. */
|
|
44
|
+
serviceToken(input: ServiceTokenRequest): Promise<AccessTokenResponse>;
|
|
45
|
+
/** Revokes the current session server-side and forgets the tokens locally. */
|
|
46
|
+
logout(): Promise<void>;
|
|
47
|
+
/** The caller's own sessions — never anybody else's (Doc 03 §6). */
|
|
48
|
+
sessions(): Promise<SessionDTO[]>;
|
|
49
|
+
revokeSession(sessionId: string): Promise<void>;
|
|
50
|
+
requestPasswordReset(input: PasswordResetRequestInput): Promise<void>;
|
|
51
|
+
resetPassword(input: PasswordResetInput): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
export declare function authEndpoints(request: Requester, session: TokenSession,
|
|
54
|
+
/** Fired whenever the subject behind the tokens may have changed. */
|
|
55
|
+
onIdentityChange: () => void): AuthApi;
|
|
56
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/endpoints/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,mBAAmB,EACnB,YAAY,EACZ,mBAAmB,EACnB,UAAU,EACV,iBAAiB,EAClB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;;;;;;GAOG;AACH,MAAM,WAAW,UAAW,SAAQ,YAAY;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,uDAAuD;AACvD,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,+CAA+C;AAC/C,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,OAAO;IACtB,iFAAiF;IACjF,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACrD;;;OAGG;IACH,OAAO,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACtC,+EAA+E;IAC/E,YAAY,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACvE,8EAA8E;IAC9E,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,oEAAoE;IACpE,QAAQ,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAClC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,SAAS,EAClB,OAAO,EAAE,YAAY;AACrB,qEAAqE;AACrE,gBAAgB,EAAE,MAAM,IAAI,GAC3B,OAAO,CAgFT"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/auth/*` — Doc 06 §3.
|
|
3
|
+
*
|
|
4
|
+
* The only endpoint module that touches state: a login, a refresh and a service
|
|
5
|
+
* token all produce credentials, and dropping them into {@link TokenSession} is
|
|
6
|
+
* the whole reason a consumer never has to think about the `Authorization`
|
|
7
|
+
* header. Logout does the reverse on both sides of the wire — the server
|
|
8
|
+
* revokes the session, the client forgets the tokens — and both happen even if
|
|
9
|
+
* the call fails, because a caller who asked to be logged out is logged out.
|
|
10
|
+
*/
|
|
11
|
+
import { AUTH_ROUTE_PREFIX } from '@plantops/contracts';
|
|
12
|
+
export function authEndpoints(request, session,
|
|
13
|
+
/** Fired whenever the subject behind the tokens may have changed. */
|
|
14
|
+
onIdentityChange) {
|
|
15
|
+
return {
|
|
16
|
+
async login(input) {
|
|
17
|
+
const pair = await request({
|
|
18
|
+
method: 'POST',
|
|
19
|
+
path: `${AUTH_ROUTE_PREFIX}/login`,
|
|
20
|
+
body: input,
|
|
21
|
+
auth: 'none',
|
|
22
|
+
});
|
|
23
|
+
await session.adopt(pair);
|
|
24
|
+
onIdentityChange();
|
|
25
|
+
return pair;
|
|
26
|
+
},
|
|
27
|
+
async refresh() {
|
|
28
|
+
const pair = await session.refresh();
|
|
29
|
+
onIdentityChange();
|
|
30
|
+
return pair;
|
|
31
|
+
},
|
|
32
|
+
async serviceToken(input) {
|
|
33
|
+
const token = await request({
|
|
34
|
+
method: 'POST',
|
|
35
|
+
path: `${AUTH_ROUTE_PREFIX}/token`,
|
|
36
|
+
body: input,
|
|
37
|
+
auth: 'none',
|
|
38
|
+
});
|
|
39
|
+
await session.adopt(token);
|
|
40
|
+
onIdentityChange();
|
|
41
|
+
return token;
|
|
42
|
+
},
|
|
43
|
+
async logout() {
|
|
44
|
+
try {
|
|
45
|
+
await request({
|
|
46
|
+
method: 'POST',
|
|
47
|
+
path: `${AUTH_ROUTE_PREFIX}/logout`,
|
|
48
|
+
body: {},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
// Local state is cleared even when the revocation call failed. The
|
|
53
|
+
// alternative — staying "logged in" because the network was down —
|
|
54
|
+
// leaves a token in a store the user believes they have emptied.
|
|
55
|
+
await session.clear();
|
|
56
|
+
onIdentityChange();
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
sessions() {
|
|
60
|
+
return request({
|
|
61
|
+
method: 'GET',
|
|
62
|
+
path: `${AUTH_ROUTE_PREFIX}/sessions`,
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
revokeSession(sessionId) {
|
|
66
|
+
return request({
|
|
67
|
+
method: 'POST',
|
|
68
|
+
path: `${AUTH_ROUTE_PREFIX}/sessions/${encodeURIComponent(sessionId)}/revoke`,
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
requestPasswordReset(input) {
|
|
72
|
+
return request({
|
|
73
|
+
method: 'POST',
|
|
74
|
+
path: `${AUTH_ROUTE_PREFIX}/password/reset-request`,
|
|
75
|
+
body: input,
|
|
76
|
+
auth: 'none',
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
resetPassword(input) {
|
|
80
|
+
return request({
|
|
81
|
+
method: 'POST',
|
|
82
|
+
path: `${AUTH_ROUTE_PREFIX}/password/reset`,
|
|
83
|
+
body: input,
|
|
84
|
+
auth: 'none',
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/iam/permissions/*`, `/iam/introspect`, `/iam/.well-known/jwks.json` — the
|
|
3
|
+
* resolution endpoints of Doc 06 §11.
|
|
4
|
+
*
|
|
5
|
+
* These are the contract every future PlantOps module depends on, and the only
|
|
6
|
+
* part of this library that is on a hot path. `resolve` here is the *uncached*
|
|
7
|
+
* call; the cached one a module should actually use is `IamClient.grants()`,
|
|
8
|
+
* which puts {@link ResolveCache} in front of it.
|
|
9
|
+
*
|
|
10
|
+
* `jwks` is deliberately unauthenticated: it is the public half of the signing
|
|
11
|
+
* key, fetched by anyone verifying a token locally — which Doc 06 §11 says
|
|
12
|
+
* modules should prefer over `introspect`, so that the IAM stays off the
|
|
13
|
+
* per-request critical path.
|
|
14
|
+
*/
|
|
15
|
+
import type { IntrospectResponse, JwksResponse, PermissionCheckRequest, PermissionCheckResponse, ResolvedGrants, ResolveQuery } from '@plantops/contracts';
|
|
16
|
+
import type { Requester } from '../http.js';
|
|
17
|
+
export interface PermissionsApi {
|
|
18
|
+
/**
|
|
19
|
+
* The bearer's complete grant set, minimized per Doc 04 §4.1. Not paginated —
|
|
20
|
+
* it is one cacheable unit. `applicationId` narrows it to one app's slice.
|
|
21
|
+
*/
|
|
22
|
+
resolve(query?: ResolveQuery): Promise<ResolvedGrants>;
|
|
23
|
+
/** The point check: does this subject hold this permission over this node? */
|
|
24
|
+
check(body: PermissionCheckRequest): Promise<PermissionCheckResponse>;
|
|
25
|
+
/** For modules verifying an opaque or edge-case token. */
|
|
26
|
+
introspect(token: string): Promise<IntrospectResponse>;
|
|
27
|
+
/** The public keys, for local JWT verification. Needs no bearer token. */
|
|
28
|
+
jwks(): Promise<JwksResponse>;
|
|
29
|
+
}
|
|
30
|
+
export declare function permissionsEndpoints(request: Requester): PermissionsApi;
|
|
31
|
+
//# sourceMappingURL=authz.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz.d.ts","sourceRoot":"","sources":["../../src/endpoints/authz.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,sBAAsB,EACtB,uBAAuB,EACvB,cAAc,EACd,YAAY,EACb,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,OAAO,CAAC,KAAK,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACvD,8EAA8E;IAC9E,KAAK,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;IACtE,0DAA0D;IAC1D,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACvD,0EAA0E;IAC1E,IAAI,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;CAC/B;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,GAAG,cAAc,CA2BvE"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/iam/permissions/*`, `/iam/introspect`, `/iam/.well-known/jwks.json` — the
|
|
3
|
+
* resolution endpoints of Doc 06 §11.
|
|
4
|
+
*
|
|
5
|
+
* These are the contract every future PlantOps module depends on, and the only
|
|
6
|
+
* part of this library that is on a hot path. `resolve` here is the *uncached*
|
|
7
|
+
* call; the cached one a module should actually use is `IamClient.grants()`,
|
|
8
|
+
* which puts {@link ResolveCache} in front of it.
|
|
9
|
+
*
|
|
10
|
+
* `jwks` is deliberately unauthenticated: it is the public half of the signing
|
|
11
|
+
* key, fetched by anyone verifying a token locally — which Doc 06 §11 says
|
|
12
|
+
* modules should prefer over `introspect`, so that the IAM stays off the
|
|
13
|
+
* per-request critical path.
|
|
14
|
+
*/
|
|
15
|
+
import { IAM_ROUTE_PREFIX } from '@plantops/contracts';
|
|
16
|
+
export function permissionsEndpoints(request) {
|
|
17
|
+
return {
|
|
18
|
+
resolve: (query) => request({
|
|
19
|
+
method: 'GET',
|
|
20
|
+
path: `${IAM_ROUTE_PREFIX}/permissions/resolve`,
|
|
21
|
+
query: { ...query },
|
|
22
|
+
}),
|
|
23
|
+
check: (body) => request({
|
|
24
|
+
method: 'POST',
|
|
25
|
+
path: `${IAM_ROUTE_PREFIX}/permissions/check`,
|
|
26
|
+
body,
|
|
27
|
+
}),
|
|
28
|
+
introspect: (token) => request({
|
|
29
|
+
method: 'POST',
|
|
30
|
+
path: `${IAM_ROUTE_PREFIX}/introspect`,
|
|
31
|
+
body: { token },
|
|
32
|
+
}),
|
|
33
|
+
jwks: () => request({
|
|
34
|
+
method: 'GET',
|
|
35
|
+
path: `${IAM_ROUTE_PREFIX}/.well-known/jwks.json`,
|
|
36
|
+
auth: 'none',
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/iam/role-bindings/*` — where WHO, WHAT and WHERE meet, Doc 06 §9.
|
|
3
|
+
*
|
|
4
|
+
* A binding is the only object in the system that grants anything: subject ×
|
|
5
|
+
* role × scope node, optionally until a date. Everything the resolution engine
|
|
6
|
+
* answers is a fold over these rows (Doc 04 §4).
|
|
7
|
+
*/
|
|
8
|
+
import type { CreateRoleBindingRequest, Paginated, RoleBindingDTO, RoleBindingsQuery } from '@plantops/contracts';
|
|
9
|
+
import type { Requester } from '../http.js';
|
|
10
|
+
export interface RoleBindingsApi {
|
|
11
|
+
/** Subject is user XOR service account; the scope node must be the tenant's. */
|
|
12
|
+
create(body: CreateRoleBindingRequest): Promise<RoleBindingDTO>;
|
|
13
|
+
list(query?: RoleBindingsQuery): Promise<Paginated<RoleBindingDTO>>;
|
|
14
|
+
remove(id: string): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export declare function roleBindingsEndpoints(request: Requester): RoleBindingsApi;
|
|
17
|
+
//# sourceMappingURL=bindings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../../src/endpoints/bindings.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,wBAAwB,EACxB,SAAS,EACT,cAAc,EACd,iBAAiB,EAClB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,eAAe;IAC9B,gFAAgF;IAChF,MAAM,CAAC,IAAI,EAAE,wBAAwB,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAChE,IAAI,CAAC,KAAK,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC,CAAC;IACpE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,GAAG,eAAe,CASzE"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/iam/role-bindings/*` — where WHO, WHAT and WHERE meet, Doc 06 §9.
|
|
3
|
+
*
|
|
4
|
+
* A binding is the only object in the system that grants anything: subject ×
|
|
5
|
+
* role × scope node, optionally until a date. Everything the resolution engine
|
|
6
|
+
* answers is a fold over these rows (Doc 04 §4).
|
|
7
|
+
*/
|
|
8
|
+
import { IAM_ROUTE_PREFIX } from '@plantops/contracts';
|
|
9
|
+
export function roleBindingsEndpoints(request) {
|
|
10
|
+
const base = `${IAM_ROUTE_PREFIX}/role-bindings`;
|
|
11
|
+
return {
|
|
12
|
+
create: (body) => request({ method: 'POST', path: base, body }),
|
|
13
|
+
list: (query) => request({ method: 'GET', path: base, query: { ...query } }),
|
|
14
|
+
remove: (id) => request({ method: 'DELETE', path: `${base}/${encodeURIComponent(id)}` }),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/iam/clients/*` — tenant provisioning, Doc 06 §5.
|
|
3
|
+
*
|
|
4
|
+
* Platform-admin surface: creating a tenant, enabling applications for it, and
|
|
5
|
+
* minting the first client-admin who can then do everything else themselves
|
|
6
|
+
* (Doc 02 §5).
|
|
7
|
+
*/
|
|
8
|
+
import type { BillableSiteRequest, BillableSitesRequest, ClientAdminDTO, ClientApplicationDTO, ClientDTO, CreateClientAdminRequest, CreateClientRequest, EnableApplicationsRequest, Paginated, PaginationQuery, ScopeNodeDTO, UpdateClientApplicationRequest, UpdateClientRequest } from '@plantops/contracts';
|
|
9
|
+
import type { Requester } from '../http.js';
|
|
10
|
+
export interface ClientsApi {
|
|
11
|
+
create(body: CreateClientRequest): Promise<ClientDTO>;
|
|
12
|
+
list(query?: PaginationQuery): Promise<Paginated<ClientDTO>>;
|
|
13
|
+
/** Update, or suspend. */
|
|
14
|
+
update(id: string, body: UpdateClientRequest): Promise<ClientDTO>;
|
|
15
|
+
enableApplications(id: string, body: EnableApplicationsRequest): Promise<ClientApplicationDTO[]>;
|
|
16
|
+
listApplications(id: string): Promise<ClientApplicationDTO[]>;
|
|
17
|
+
/** The per-tenant on/off switch; disabling drops that app's grants (Doc 02 §7). */
|
|
18
|
+
updateApplication(id: string, applicationId: string, body: UpdateClientApplicationRequest): Promise<ClientApplicationDTO>;
|
|
19
|
+
/**
|
|
20
|
+
* Marks or clears one node as a billable site — the meter of Doc 11 §10.1.
|
|
21
|
+
* Platform-tier, and never derived from `scope_node.kind`.
|
|
22
|
+
*/
|
|
23
|
+
setBillableSite(id: string, scopeNodeId: string, body: BillableSiteRequest): Promise<ScopeNodeDTO>;
|
|
24
|
+
/**
|
|
25
|
+
* The same meter over many nodes — one call when provisioning a tenant.
|
|
26
|
+
*
|
|
27
|
+
* All-or-nothing: a batch breaching `max_sites` is refused entire. Ids
|
|
28
|
+
* outside this tenant are absent from the result rather than an error.
|
|
29
|
+
*/
|
|
30
|
+
setBillableSites(id: string, body: BillableSitesRequest): Promise<ScopeNodeDTO[]>;
|
|
31
|
+
/** The initial client-admin user and its binding, in one call. */
|
|
32
|
+
createAdmin(id: string, body: CreateClientAdminRequest): Promise<ClientAdminDTO>;
|
|
33
|
+
}
|
|
34
|
+
export declare function clientsEndpoints(request: Requester): ClientsApi;
|
|
35
|
+
//# sourceMappingURL=clients.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clients.d.ts","sourceRoot":"","sources":["../../src/endpoints/clients.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,EACd,oBAAoB,EACpB,SAAS,EACT,wBAAwB,EACxB,mBAAmB,EACnB,yBAAyB,EACzB,SAAS,EACT,eAAe,EACf,YAAY,EACZ,8BAA8B,EAC9B,mBAAmB,EACpB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACtD,IAAI,CAAC,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC;IAC7D,0BAA0B;IAC1B,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAElE,kBAAkB,CAChB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;IACnC,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;IAC9D,mFAAmF;IACnF,iBAAiB,CACf,EAAE,EAAE,MAAM,EACV,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,8BAA8B,GACnC,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAEjC;;;OAGG;IACH,eAAe,CACb,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,YAAY,CAAC,CAAC;IACzB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAElF,kEAAkE;IAClE,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,wBAAwB,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CAClF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,GAAG,UAAU,CAgC/D"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/iam/clients/*` — tenant provisioning, Doc 06 §5.
|
|
3
|
+
*
|
|
4
|
+
* Platform-admin surface: creating a tenant, enabling applications for it, and
|
|
5
|
+
* minting the first client-admin who can then do everything else themselves
|
|
6
|
+
* (Doc 02 §5).
|
|
7
|
+
*/
|
|
8
|
+
import { IAM_ROUTE_PREFIX } from '@plantops/contracts';
|
|
9
|
+
export function clientsEndpoints(request) {
|
|
10
|
+
const base = `${IAM_ROUTE_PREFIX}/clients`;
|
|
11
|
+
const at = (id, suffix = '') => `${base}/${encodeURIComponent(id)}${suffix}`;
|
|
12
|
+
return {
|
|
13
|
+
create: (body) => request({ method: 'POST', path: base, body }),
|
|
14
|
+
list: (query) => request({ method: 'GET', path: base, query: { ...query } }),
|
|
15
|
+
update: (id, body) => request({ method: 'PATCH', path: at(id), body }),
|
|
16
|
+
setBillableSite: (id, scopeNodeId, body) => request({
|
|
17
|
+
method: 'PATCH',
|
|
18
|
+
path: at(id, `/scopes/${encodeURIComponent(scopeNodeId)}/billable-site`),
|
|
19
|
+
body,
|
|
20
|
+
}),
|
|
21
|
+
setBillableSites: (id, body) => request({ method: 'PATCH', path: at(id, '/billable-sites'), body }),
|
|
22
|
+
enableApplications: (id, body) => request({ method: 'POST', path: at(id, '/applications'), body }),
|
|
23
|
+
listApplications: (id) => request({ method: 'GET', path: at(id, '/applications') }),
|
|
24
|
+
updateApplication: (id, applicationId, body) => request({
|
|
25
|
+
method: 'PATCH',
|
|
26
|
+
path: at(id, `/applications/${encodeURIComponent(applicationId)}`),
|
|
27
|
+
body,
|
|
28
|
+
}),
|
|
29
|
+
createAdmin: (id, body) => request({ method: 'POST', path: at(id, '/admins'), body }),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/iam/entitlements` and `/iam/licence` — what this tenant is entitled to.
|
|
3
|
+
*
|
|
4
|
+
* Both are readable by any authenticated subject of the tenant, so neither
|
|
5
|
+
* needs a platform credential. They answer different questions and a consumer
|
|
6
|
+
* usually wants the first:
|
|
7
|
+
*
|
|
8
|
+
* - {@link EntitlementsApi.view} is the whole picture — the tenant's term and
|
|
9
|
+
* ceilings, live usage counts, and one row per application with its own term.
|
|
10
|
+
* - {@link EntitlementsApi.licence} is the date alone, which is what a console
|
|
11
|
+
* header renders. It predates the other and is kept because it has consumers.
|
|
12
|
+
*
|
|
13
|
+
* ## Cache this on a clock, never on the grants counter
|
|
14
|
+
*
|
|
15
|
+
* `IamClient.grants()` is cached against a `(client, subject)` version counter
|
|
16
|
+
* that moves whenever access changes. Entitlement state has no such counter and
|
|
17
|
+
* cannot have one: **nothing writes at the moment a term expires**. Cache it
|
|
18
|
+
* against {@link EntitlementsView.as_of} with a bound of your choosing — five
|
|
19
|
+
* minutes is the documented suggestion — and never against `ResolvedGrants`.
|
|
20
|
+
*
|
|
21
|
+
* Getting this wrong does not produce a briefly-stale value. It produces
|
|
22
|
+
* `expired: false` forever.
|
|
23
|
+
*
|
|
24
|
+
* ## Nothing here is enforced by the IAM
|
|
25
|
+
*
|
|
26
|
+
* An application past its term still resolves grants and still appears in
|
|
27
|
+
* navigation. Refusing is the consuming module's job, and Doc 12 states that
|
|
28
|
+
* obligation normatively. Reading `expired` and doing nothing with it is the
|
|
29
|
+
* same as not reading it.
|
|
30
|
+
*/
|
|
31
|
+
import type { EntitlementsView, LicenceView } from '@plantops/contracts';
|
|
32
|
+
import type { Requester } from '../http.js';
|
|
33
|
+
export interface EntitlementsApi {
|
|
34
|
+
/** Term, ceilings, live usage, and one row per application. */
|
|
35
|
+
view(): Promise<EntitlementsView>;
|
|
36
|
+
/** The effective term alone — licence file and subscription, whichever is sooner. */
|
|
37
|
+
licence(): Promise<LicenceView>;
|
|
38
|
+
}
|
|
39
|
+
export declare function entitlementsEndpoints(request: Requester): EntitlementsApi;
|
|
40
|
+
//# sourceMappingURL=entitlements.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entitlements.d.ts","sourceRoot":"","sources":["../../src/endpoints/entitlements.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,eAAe;IAC9B,+DAA+D;IAC/D,IAAI,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAClC,qFAAqF;IACrF,OAAO,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;CACjC;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,GAAG,eAAe,CAazE"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/iam/entitlements` and `/iam/licence` — what this tenant is entitled to.
|
|
3
|
+
*
|
|
4
|
+
* Both are readable by any authenticated subject of the tenant, so neither
|
|
5
|
+
* needs a platform credential. They answer different questions and a consumer
|
|
6
|
+
* usually wants the first:
|
|
7
|
+
*
|
|
8
|
+
* - {@link EntitlementsApi.view} is the whole picture — the tenant's term and
|
|
9
|
+
* ceilings, live usage counts, and one row per application with its own term.
|
|
10
|
+
* - {@link EntitlementsApi.licence} is the date alone, which is what a console
|
|
11
|
+
* header renders. It predates the other and is kept because it has consumers.
|
|
12
|
+
*
|
|
13
|
+
* ## Cache this on a clock, never on the grants counter
|
|
14
|
+
*
|
|
15
|
+
* `IamClient.grants()` is cached against a `(client, subject)` version counter
|
|
16
|
+
* that moves whenever access changes. Entitlement state has no such counter and
|
|
17
|
+
* cannot have one: **nothing writes at the moment a term expires**. Cache it
|
|
18
|
+
* against {@link EntitlementsView.as_of} with a bound of your choosing — five
|
|
19
|
+
* minutes is the documented suggestion — and never against `ResolvedGrants`.
|
|
20
|
+
*
|
|
21
|
+
* Getting this wrong does not produce a briefly-stale value. It produces
|
|
22
|
+
* `expired: false` forever.
|
|
23
|
+
*
|
|
24
|
+
* ## Nothing here is enforced by the IAM
|
|
25
|
+
*
|
|
26
|
+
* An application past its term still resolves grants and still appears in
|
|
27
|
+
* navigation. Refusing is the consuming module's job, and Doc 12 states that
|
|
28
|
+
* obligation normatively. Reading `expired` and doing nothing with it is the
|
|
29
|
+
* same as not reading it.
|
|
30
|
+
*/
|
|
31
|
+
import { IAM_ROUTE_PREFIX } from '@plantops/contracts';
|
|
32
|
+
export function entitlementsEndpoints(request) {
|
|
33
|
+
return {
|
|
34
|
+
view: () => request({
|
|
35
|
+
method: 'GET',
|
|
36
|
+
path: `${IAM_ROUTE_PREFIX}/entitlements`,
|
|
37
|
+
}),
|
|
38
|
+
licence: () => request({
|
|
39
|
+
method: 'GET',
|
|
40
|
+
path: `${IAM_ROUTE_PREFIX}/licence`,
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The endpoint modules, one per section of Doc 06.
|
|
3
|
+
*
|
|
4
|
+
* Each exports an interface and a factory that takes a {@link Requester} and
|
|
5
|
+
* nothing else. That shape is what keeps `client.ts` from becoming a
|
|
6
|
+
* thousand-line class, and it is what lets a consumer that wants only one
|
|
7
|
+
* surface — a migration script that talks to the registry, say — build it
|
|
8
|
+
* without a token store or a cache.
|
|
9
|
+
*/
|
|
10
|
+
export * from './applications.js';
|
|
11
|
+
export * from './audit.js';
|
|
12
|
+
export * from './auth.js';
|
|
13
|
+
export * from './authz.js';
|
|
14
|
+
export * from './bindings.js';
|
|
15
|
+
export * from './clients.js';
|
|
16
|
+
export * from './entitlements.js';
|
|
17
|
+
export * from './navigation.js';
|
|
18
|
+
export * from './roles.js';
|
|
19
|
+
export * from './scopes.js';
|
|
20
|
+
export * from './service-accounts.js';
|
|
21
|
+
export * from './users.js';
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/endpoints/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The endpoint modules, one per section of Doc 06.
|
|
3
|
+
*
|
|
4
|
+
* Each exports an interface and a factory that takes a {@link Requester} and
|
|
5
|
+
* nothing else. That shape is what keeps `client.ts` from becoming a
|
|
6
|
+
* thousand-line class, and it is what lets a consumer that wants only one
|
|
7
|
+
* surface — a migration script that talks to the registry, say — build it
|
|
8
|
+
* without a token store or a cache.
|
|
9
|
+
*/
|
|
10
|
+
export * from './applications.js';
|
|
11
|
+
export * from './audit.js';
|
|
12
|
+
export * from './auth.js';
|
|
13
|
+
export * from './authz.js';
|
|
14
|
+
export * from './bindings.js';
|
|
15
|
+
export * from './clients.js';
|
|
16
|
+
export * from './entitlements.js';
|
|
17
|
+
export * from './navigation.js';
|
|
18
|
+
export * from './roles.js';
|
|
19
|
+
export * from './scopes.js';
|
|
20
|
+
export * from './service-accounts.js';
|
|
21
|
+
export * from './users.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /iam/navigation` — the menu, Doc 06 §11 and Doc 05.
|
|
3
|
+
*
|
|
4
|
+
* A pure function of the bearer's grants and the nav catalog: containers with
|
|
5
|
+
* no visible descendant are pruned, unmapped nodes are hidden unless they opt
|
|
6
|
+
* in with `is_public`, and disabled applications never appear. Called with no
|
|
7
|
+
* `applicationId` it returns the cross-application shell — one top-level node
|
|
8
|
+
* per enabled app.
|
|
9
|
+
*
|
|
10
|
+
* Not cached here. The menu changes when the catalog changes as well as when
|
|
11
|
+
* grants change, and it is fetched once per shell load rather than once per
|
|
12
|
+
* request, so there is no burst for a cache to absorb — only staleness to
|
|
13
|
+
* introduce.
|
|
14
|
+
*/
|
|
15
|
+
import type { NavigationResponse } from '@plantops/contracts';
|
|
16
|
+
import type { Requester } from '../http.js';
|
|
17
|
+
/** Doc 05 §5's optional narrowing to one application's menu. */
|
|
18
|
+
export interface NavigationQuery {
|
|
19
|
+
applicationId?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface NavigationApi {
|
|
22
|
+
tree(query?: NavigationQuery): Promise<NavigationResponse>;
|
|
23
|
+
}
|
|
24
|
+
export declare function navigationEndpoints(request: Requester): NavigationApi;
|
|
25
|
+
//# sourceMappingURL=navigation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/endpoints/navigation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAG9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;CAC5D;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,GAAG,aAAa,CASrE"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /iam/navigation` — the menu, Doc 06 §11 and Doc 05.
|
|
3
|
+
*
|
|
4
|
+
* A pure function of the bearer's grants and the nav catalog: containers with
|
|
5
|
+
* no visible descendant are pruned, unmapped nodes are hidden unless they opt
|
|
6
|
+
* in with `is_public`, and disabled applications never appear. Called with no
|
|
7
|
+
* `applicationId` it returns the cross-application shell — one top-level node
|
|
8
|
+
* per enabled app.
|
|
9
|
+
*
|
|
10
|
+
* Not cached here. The menu changes when the catalog changes as well as when
|
|
11
|
+
* grants change, and it is fetched once per shell load rather than once per
|
|
12
|
+
* request, so there is no burst for a cache to absorb — only staleness to
|
|
13
|
+
* introduce.
|
|
14
|
+
*/
|
|
15
|
+
import { IAM_ROUTE_PREFIX } from '@plantops/contracts';
|
|
16
|
+
export function navigationEndpoints(request) {
|
|
17
|
+
return {
|
|
18
|
+
tree: (query) => request({
|
|
19
|
+
method: 'GET',
|
|
20
|
+
path: `${IAM_ROUTE_PREFIX}/navigation`,
|
|
21
|
+
query: { ...query },
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/iam/roles/*` — the WHAT dimension, Doc 06 §7.
|
|
3
|
+
*
|
|
4
|
+
* A role is a bag of permissions drawn from the tenant's *enabled* applications;
|
|
5
|
+
* `setPermissions` is a PUT because the screen behind it is a checklist, and a
|
|
6
|
+
* checklist submits a set, not a diff.
|
|
7
|
+
*/
|
|
8
|
+
import type { CreateRoleRequest, Paginated, PaginationQuery, PermissionCatalogResponse, RoleDTO, RolePermissionsResponse, SetRolePermissionsRequest, UpdateRoleRequest } from '@plantops/contracts';
|
|
9
|
+
import type { Requester } from '../http.js';
|
|
10
|
+
export interface RolesApi {
|
|
11
|
+
create(body: CreateRoleRequest): Promise<RoleDTO>;
|
|
12
|
+
list(query?: PaginationQuery): Promise<Paginated<RoleDTO>>;
|
|
13
|
+
update(id: string, body: UpdateRoleRequest): Promise<RoleDTO>;
|
|
14
|
+
/** Cascades the role's bindings, with an audit record for each (Doc 06 §7). */
|
|
15
|
+
remove(id: string): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Everything a role of the caller's tenant may be given — the source of
|
|
18
|
+
* Doc 09 §3.2's picker. Every entry is one `setPermissions` will accept.
|
|
19
|
+
*/
|
|
20
|
+
permissionCatalog(): Promise<PermissionCatalogResponse>;
|
|
21
|
+
permissions(id: string): Promise<RolePermissionsResponse>;
|
|
22
|
+
setPermissions(id: string, body: SetRolePermissionsRequest): Promise<RolePermissionsResponse>;
|
|
23
|
+
}
|
|
24
|
+
export declare function rolesEndpoints(request: Requester): RolesApi;
|
|
25
|
+
//# sourceMappingURL=roles.d.ts.map
|