@c1m-public/platform-auth 0.12.0 → 0.13.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 +24 -0
- package/dist/access/index.d.ts +1 -1
- package/dist/access/index.js +2 -0
- package/dist/access/next.d.ts +2 -2
- package/dist/access/next.js +2 -0
- package/dist/{index-D-cH_sfe.d.ts → index-CPjLkXA-.d.ts} +1 -1
- package/dist/service-accounts.d.ts +146 -0
- package/dist/service-accounts.js +481 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ need — the boundary is the subpath.
|
|
|
12
12
|
. session: token reads, handoff, proxy gate, refresh (Web Request/Response)
|
|
13
13
|
./next session, server-only guard (Next route handlers)
|
|
14
14
|
./mcp-tokens self-service MCP PAT lifecycle (framework-neutral)
|
|
15
|
+
./service-accounts operator-owned service accounts and credentials (framework-neutral)
|
|
15
16
|
./access authorization: snapshot, keys, predicates (framework-neutral)
|
|
16
17
|
./access/next access loader: require / forbidden / invalidate (Next, server-only)
|
|
17
18
|
./access/react <Can>, useCan, AccessProvider (client)
|
|
@@ -37,6 +38,29 @@ of URLs, logs, storage, and telemetry. A `recent_authentication_required` API
|
|
|
37
38
|
error is a step-up challenge, not an expired session; send the user through the
|
|
38
39
|
host application's re-authentication path before retrying the mint.
|
|
39
40
|
|
|
41
|
+
## `./service-accounts` — operator service accounts
|
|
42
|
+
|
|
43
|
+
`createServiceAccountClient` manages the operator-owned service accounts under
|
|
44
|
+
`/api/v1/platform/identity/service-accounts`: list, create, update, replace
|
|
45
|
+
grants, and list, mint or revoke credentials. Reads need
|
|
46
|
+
`SERVICE_ACCOUNTS_VIEW_PERMISSION`; every mutation needs
|
|
47
|
+
`SERVICE_ACCOUNTS_MANAGE_PERMISSION` and is never retried.
|
|
48
|
+
|
|
49
|
+
A credential is minted from **reviewed operation ids**, not from arbitrary
|
|
50
|
+
permissions: callers name `operation_ids` and the backend derives the
|
|
51
|
+
permission ceiling, so a caller cannot widen its own grant. The client also
|
|
52
|
+
holds the caller to the wire contract before a request leaves the process — a
|
|
53
|
+
`dash:<id>` audience must carry exactly its route Dash in `dash_ids`, a Dash
|
|
54
|
+
grant must name one Dash rather than `*`, `expires_in_days` is 1–30, and
|
|
55
|
+
`max_output_bytes` is 1–1048576.
|
|
56
|
+
|
|
57
|
+
Every mutation carries `expected_version`; a `409` with
|
|
58
|
+
`service_account_version_conflict` means the record moved under the caller and
|
|
59
|
+
the read has to be repeated. A `recent_authentication_required` API error is a
|
|
60
|
+
step-up challenge, not an expired session. The minted `secret` is one-time and
|
|
61
|
+
is checked to correlate with the credential it belongs to: keep it out of URLs,
|
|
62
|
+
logs, storage, and telemetry — the server never returns it again.
|
|
63
|
+
|
|
40
64
|
## `.` — session
|
|
41
65
|
|
|
42
66
|
| Export | Purpose |
|
package/dist/access/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { a as AccessEntry, b as AccessErrorCode, c as AccessScopeType, A as AccessSnapshot, d as AccessSnapshotError, D as DASH_PERMISSIONS, e as DASH_ROLES, f as DEFAULT_ACCESS_ENDPOINT_PATH, g as DashPermissionKey, h as DashRole, F as FetchAccessSnapshotOptions, L as LEADBROKER_PERMISSIONS, i as LeadBrokerPermissionKey, j as PIPELINE_STAGES, k as PLATFORM_PERMISSIONS, P as PermissionCheckOptions, l as PermissionKey, m as PipelineStage, n as PlatformPermissionKey, W as WORKSPACE_PERMISSIONS, o as WORKSPACE_ROLES, p as WorkspacePermissionKey, q as WorkspaceRole, r as appScopes, s as dashRole, t as emptySnapshot, u as entryCoversApp, v as fetchAccessSnapshot, w as hasAllPermissions, x as hasAnyPermission, y as hasPermission, z as hasWorkspacePermission, B as isAccessSnapshotError, C as isPipelineStage, E as isPlatformOwner, G as managedDashes, H as parseAccessSnapshot, I as permissionsFor } from '../index-
|
|
1
|
+
export { a as AccessEntry, b as AccessErrorCode, c as AccessScopeType, A as AccessSnapshot, d as AccessSnapshotError, D as DASH_PERMISSIONS, e as DASH_ROLES, f as DEFAULT_ACCESS_ENDPOINT_PATH, g as DashPermissionKey, h as DashRole, F as FetchAccessSnapshotOptions, L as LEADBROKER_PERMISSIONS, i as LeadBrokerPermissionKey, j as PIPELINE_STAGES, k as PLATFORM_PERMISSIONS, P as PermissionCheckOptions, l as PermissionKey, m as PipelineStage, n as PlatformPermissionKey, W as WORKSPACE_PERMISSIONS, o as WORKSPACE_ROLES, p as WorkspacePermissionKey, q as WorkspaceRole, r as appScopes, s as dashRole, t as emptySnapshot, u as entryCoversApp, v as fetchAccessSnapshot, w as hasAllPermissions, x as hasAnyPermission, y as hasPermission, z as hasWorkspacePermission, B as isAccessSnapshotError, C as isPipelineStage, E as isPlatformOwner, G as managedDashes, H as parseAccessSnapshot, I as permissionsFor } from '../index-CPjLkXA-.js';
|
package/dist/access/index.js
CHANGED
|
@@ -26,6 +26,8 @@ var PLATFORM_PERMISSIONS = [
|
|
|
26
26
|
"platform.offboarding.view",
|
|
27
27
|
"platform.ops.manage",
|
|
28
28
|
"platform.ops.view",
|
|
29
|
+
"platform.service_accounts.manage",
|
|
30
|
+
"platform.service_accounts.view",
|
|
29
31
|
"platform.support.impersonate",
|
|
30
32
|
"platform.support.sessions",
|
|
31
33
|
"platform.tenants.archive",
|
package/dist/access/next.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as AccessSnapshot, P as PermissionCheckOptions, R as Resolvable } from '../index-
|
|
2
|
-
export { a as AccessEntry, b as AccessErrorCode, c as AccessScopeType, d as AccessSnapshotError, D as DASH_PERMISSIONS, e as DASH_ROLES, f as DEFAULT_ACCESS_ENDPOINT_PATH, g as DashPermissionKey, h as DashRole, F as FetchAccessSnapshotOptions, L as LEADBROKER_PERMISSIONS, i as LeadBrokerPermissionKey, j as PIPELINE_STAGES, k as PLATFORM_PERMISSIONS, l as PermissionKey, m as PipelineStage, n as PlatformPermissionKey, W as WORKSPACE_PERMISSIONS, o as WORKSPACE_ROLES, p as WorkspacePermissionKey, q as WorkspaceRole, r as appScopes, s as dashRole, t as emptySnapshot, u as entryCoversApp, v as fetchAccessSnapshot, w as hasAllPermissions, x as hasAnyPermission, y as hasPermission, z as hasWorkspacePermission, B as isAccessSnapshotError, C as isPipelineStage, E as isPlatformOwner, G as managedDashes, H as parseAccessSnapshot, I as permissionsFor } from '../index-
|
|
1
|
+
import { A as AccessSnapshot, P as PermissionCheckOptions, R as Resolvable } from '../index-CPjLkXA-.js';
|
|
2
|
+
export { a as AccessEntry, b as AccessErrorCode, c as AccessScopeType, d as AccessSnapshotError, D as DASH_PERMISSIONS, e as DASH_ROLES, f as DEFAULT_ACCESS_ENDPOINT_PATH, g as DashPermissionKey, h as DashRole, F as FetchAccessSnapshotOptions, L as LEADBROKER_PERMISSIONS, i as LeadBrokerPermissionKey, j as PIPELINE_STAGES, k as PLATFORM_PERMISSIONS, l as PermissionKey, m as PipelineStage, n as PlatformPermissionKey, W as WORKSPACE_PERMISSIONS, o as WORKSPACE_ROLES, p as WorkspacePermissionKey, q as WorkspaceRole, r as appScopes, s as dashRole, t as emptySnapshot, u as entryCoversApp, v as fetchAccessSnapshot, w as hasAllPermissions, x as hasAnyPermission, y as hasPermission, z as hasWorkspacePermission, B as isAccessSnapshotError, C as isPipelineStage, E as isPlatformOwner, G as managedDashes, H as parseAccessSnapshot, I as permissionsFor } from '../index-CPjLkXA-.js';
|
|
3
3
|
|
|
4
4
|
/** Default cache tag for one subject's snapshot: `access:<subjectId>`. */
|
|
5
5
|
declare function accessCacheTag(subjectId: string): string;
|
package/dist/access/next.js
CHANGED
|
@@ -282,6 +282,8 @@ var PLATFORM_PERMISSIONS = [
|
|
|
282
282
|
"platform.offboarding.view",
|
|
283
283
|
"platform.ops.manage",
|
|
284
284
|
"platform.ops.view",
|
|
285
|
+
"platform.service_accounts.manage",
|
|
286
|
+
"platform.service_accounts.view",
|
|
285
287
|
"platform.support.impersonate",
|
|
286
288
|
"platform.support.sessions",
|
|
287
289
|
"platform.tenants.archive",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Platform-operator permission keys. */
|
|
2
|
-
declare const PLATFORM_PERMISSIONS: readonly ["affiliates.enrollments.manage", "affiliates.finance.manage", "affiliates.partners.manage", "affiliates.programmes.manage", "affiliates.view", "linkservice.domains.manage", "linkservice.links.manage", "linkservice.qr.manage", "linkservice.view", "platform.admin.view", "platform.audit.view", "platform.auth.manage", "platform.billing.manage", "platform.billing.view", "platform.channels.manage", "platform.channels.view", "platform.config.manage", "platform.docs.manage", "platform.docs.view", "platform.mcp.access", "platform.mcp.tokens.manage", "platform.observability.manage", "platform.observability.view", "platform.offboarding.view", "platform.ops.manage", "platform.ops.view", "platform.support.impersonate", "platform.support.sessions", "platform.tenants.archive", "platform.tenants.manage", "platform.tenants.restore", "platform.tenants.view", "platform.users.deactivate", "platform.users.delete", "platform.users.invite", "platform.users.manage_access", "platform.users.reactivate", "platform.users.view"];
|
|
2
|
+
declare const PLATFORM_PERMISSIONS: readonly ["affiliates.enrollments.manage", "affiliates.finance.manage", "affiliates.partners.manage", "affiliates.programmes.manage", "affiliates.view", "linkservice.domains.manage", "linkservice.links.manage", "linkservice.qr.manage", "linkservice.view", "platform.admin.view", "platform.audit.view", "platform.auth.manage", "platform.billing.manage", "platform.billing.view", "platform.channels.manage", "platform.channels.view", "platform.config.manage", "platform.docs.manage", "platform.docs.view", "platform.mcp.access", "platform.mcp.tokens.manage", "platform.observability.manage", "platform.observability.view", "platform.offboarding.view", "platform.ops.manage", "platform.ops.view", "platform.service_accounts.manage", "platform.service_accounts.view", "platform.support.impersonate", "platform.support.sessions", "platform.tenants.archive", "platform.tenants.manage", "platform.tenants.restore", "platform.tenants.view", "platform.users.deactivate", "platform.users.delete", "platform.users.invite", "platform.users.manage_access", "platform.users.reactivate", "platform.users.view"];
|
|
3
3
|
type PlatformPermissionKey = (typeof PLATFORM_PERMISSIONS)[number];
|
|
4
4
|
/** Dash-owner permission keys (granted at dash scope, mirrored from the membership role). */
|
|
5
5
|
declare const DASH_PERMISSIONS: readonly ["dash.apps.manage", "dash.billing.manage", "dash.billing.view", "dash.channels.manage", "dash.customers.manage", "dash.domains.manage", "dash.domains.view", "dash.members.manage", "dash.ownership.transfer", "dash.plans.manage", "dash.plans.view", "dash.publish", "dash.settings.manage", "dash.workspaces.admin", "dash.workspaces.manage", "dash.workspaces.view"];
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
declare const SERVICE_ACCOUNTS_VIEW_PERMISSION: "platform.service_accounts.view";
|
|
2
|
+
declare const SERVICE_ACCOUNTS_MANAGE_PERMISSION: "platform.service_accounts.manage";
|
|
3
|
+
type ServiceAccountAudience = "platform" | `dash:${string}`;
|
|
4
|
+
type ServiceAccountGrantScope = "platform" | "dash_id";
|
|
5
|
+
type ServiceAccountOperation = "platform.identity.service_accounts.list" | "platform.identity.service_accounts.create" | "platform.identity.service_accounts.get" | "platform.identity.service_accounts.update" | "platform.identity.service_accounts.grants.list" | "platform.identity.service_accounts.grants.replace" | "platform.identity.service_accounts.credentials.list" | "platform.identity.service_accounts.credentials.mint" | "platform.identity.service_accounts.credentials.revoke";
|
|
6
|
+
type ServiceAccountRequestOutcome = "success" | "api_error" | "network_error" | "timeout" | "invalid_response" | "cancelled";
|
|
7
|
+
interface ServiceAccountView {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
purpose: string;
|
|
11
|
+
owner_user_id: string;
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
version: number;
|
|
14
|
+
created_by: string;
|
|
15
|
+
updated_by: string;
|
|
16
|
+
created_at: string;
|
|
17
|
+
updated_at: string;
|
|
18
|
+
}
|
|
19
|
+
interface ServiceAccountEnvelope {
|
|
20
|
+
service_account: ServiceAccountView;
|
|
21
|
+
}
|
|
22
|
+
interface ServiceAccountPage {
|
|
23
|
+
items: ServiceAccountView[];
|
|
24
|
+
next_cursor?: string;
|
|
25
|
+
has_more: boolean;
|
|
26
|
+
}
|
|
27
|
+
interface ServiceAccountGrant {
|
|
28
|
+
grant_key: string;
|
|
29
|
+
scope_type: string;
|
|
30
|
+
scope_id: string;
|
|
31
|
+
id?: string;
|
|
32
|
+
subject_id?: string;
|
|
33
|
+
subject_type?: string;
|
|
34
|
+
granted_by?: string;
|
|
35
|
+
created_at?: string;
|
|
36
|
+
}
|
|
37
|
+
interface ServiceAccountGrantInput {
|
|
38
|
+
grant_key: string;
|
|
39
|
+
scope_type: ServiceAccountGrantScope;
|
|
40
|
+
scope_id: string;
|
|
41
|
+
}
|
|
42
|
+
interface ServiceAccountGrantPage {
|
|
43
|
+
items: ServiceAccountGrant[];
|
|
44
|
+
}
|
|
45
|
+
interface ServiceAccountCredential {
|
|
46
|
+
id: string;
|
|
47
|
+
service_account_id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
audience: ServiceAccountAudience;
|
|
50
|
+
permissions: string[];
|
|
51
|
+
operation_ids: string[];
|
|
52
|
+
dash_ids: string[];
|
|
53
|
+
max_output_bytes: number;
|
|
54
|
+
created_by: string;
|
|
55
|
+
created_at: string;
|
|
56
|
+
expires_at: string;
|
|
57
|
+
last_used_at?: string;
|
|
58
|
+
revoked_at?: string;
|
|
59
|
+
}
|
|
60
|
+
interface ServiceAccountCredentialPage {
|
|
61
|
+
items: ServiceAccountCredential[];
|
|
62
|
+
next_cursor?: string;
|
|
63
|
+
has_more: boolean;
|
|
64
|
+
}
|
|
65
|
+
interface CreateServiceAccountInput {
|
|
66
|
+
name: string;
|
|
67
|
+
purpose: string;
|
|
68
|
+
owner_user_id: string;
|
|
69
|
+
reason: string;
|
|
70
|
+
}
|
|
71
|
+
interface UpdateServiceAccountInput extends CreateServiceAccountInput {
|
|
72
|
+
enabled: boolean;
|
|
73
|
+
expected_version: number;
|
|
74
|
+
}
|
|
75
|
+
interface ReplaceServiceAccountGrantsInput {
|
|
76
|
+
grants: ServiceAccountGrantInput[];
|
|
77
|
+
expected_version: number;
|
|
78
|
+
reason: string;
|
|
79
|
+
}
|
|
80
|
+
interface MintServiceAccountCredentialInput {
|
|
81
|
+
name: string;
|
|
82
|
+
audience: ServiceAccountAudience;
|
|
83
|
+
operation_ids: string[];
|
|
84
|
+
dash_ids: string[];
|
|
85
|
+
expires_in_days: number;
|
|
86
|
+
expected_version: number;
|
|
87
|
+
reason: string;
|
|
88
|
+
max_output_bytes?: number;
|
|
89
|
+
}
|
|
90
|
+
interface MintServiceAccountCredentialResponse {
|
|
91
|
+
credential: ServiceAccountCredential;
|
|
92
|
+
/** One-time presented secret. The server never returns it again. */
|
|
93
|
+
secret: string;
|
|
94
|
+
}
|
|
95
|
+
interface RevokeServiceAccountCredentialInput {
|
|
96
|
+
expected_version: number;
|
|
97
|
+
reason: string;
|
|
98
|
+
}
|
|
99
|
+
interface ServiceAccountListParams {
|
|
100
|
+
cursor?: string;
|
|
101
|
+
limit?: number;
|
|
102
|
+
}
|
|
103
|
+
interface ServiceAccountRequestObservation {
|
|
104
|
+
operation: ServiceAccountOperation;
|
|
105
|
+
method: string;
|
|
106
|
+
outcome: ServiceAccountRequestOutcome;
|
|
107
|
+
status: number;
|
|
108
|
+
requestId: string;
|
|
109
|
+
durationMs: number;
|
|
110
|
+
attempts: number;
|
|
111
|
+
}
|
|
112
|
+
interface CreateServiceAccountClientOptions {
|
|
113
|
+
baseUrl: string;
|
|
114
|
+
getAccessToken: () => Promise<string | null | undefined> | string | null | undefined;
|
|
115
|
+
getHeaders?: () => Promise<HeadersInit | undefined> | HeadersInit | undefined;
|
|
116
|
+
fetch?: typeof globalThis.fetch;
|
|
117
|
+
/** Per attempt (default 10000). */
|
|
118
|
+
timeoutMs?: number;
|
|
119
|
+
/** Additional attempts for GET only (default 1). Mutations are never retried. */
|
|
120
|
+
retryAttempts?: number;
|
|
121
|
+
/** Cap on one retry delay (default 2000). */
|
|
122
|
+
maxRetryDelayMs?: number;
|
|
123
|
+
observe?: (event: ServiceAccountRequestObservation) => void;
|
|
124
|
+
signal?: AbortSignal;
|
|
125
|
+
}
|
|
126
|
+
declare class ServiceAccountAPIError extends Error {
|
|
127
|
+
readonly status: number;
|
|
128
|
+
readonly code: string;
|
|
129
|
+
readonly requestId: string;
|
|
130
|
+
constructor(message: string, status: number, code: string, requestId: string);
|
|
131
|
+
}
|
|
132
|
+
declare function isServiceAccountAPIError(error: unknown): error is ServiceAccountAPIError;
|
|
133
|
+
declare function createServiceAccountClient(options: CreateServiceAccountClientOptions): {
|
|
134
|
+
readonly list: (params?: ServiceAccountListParams) => Promise<ServiceAccountPage>;
|
|
135
|
+
readonly get: (serviceAccountID: string) => Promise<ServiceAccountEnvelope>;
|
|
136
|
+
readonly create: (input: CreateServiceAccountInput) => Promise<ServiceAccountEnvelope>;
|
|
137
|
+
readonly update: (serviceAccountID: string, input: UpdateServiceAccountInput) => Promise<ServiceAccountEnvelope>;
|
|
138
|
+
readonly listGrants: (serviceAccountID: string) => Promise<ServiceAccountGrantPage>;
|
|
139
|
+
readonly replaceGrants: (serviceAccountID: string, input: ReplaceServiceAccountGrantsInput) => Promise<ServiceAccountEnvelope>;
|
|
140
|
+
readonly listCredentials: (serviceAccountID: string, params?: ServiceAccountListParams) => Promise<ServiceAccountCredentialPage>;
|
|
141
|
+
readonly mintCredential: (serviceAccountID: string, input: MintServiceAccountCredentialInput) => Promise<MintServiceAccountCredentialResponse>;
|
|
142
|
+
readonly revokeCredential: (serviceAccountID: string, credentialID: string, input: RevokeServiceAccountCredentialInput) => Promise<ServiceAccountEnvelope>;
|
|
143
|
+
};
|
|
144
|
+
type ServiceAccountClient = ReturnType<typeof createServiceAccountClient>;
|
|
145
|
+
|
|
146
|
+
export { type CreateServiceAccountClientOptions, type CreateServiceAccountInput, type MintServiceAccountCredentialInput, type MintServiceAccountCredentialResponse, type ReplaceServiceAccountGrantsInput, type RevokeServiceAccountCredentialInput, SERVICE_ACCOUNTS_MANAGE_PERMISSION, SERVICE_ACCOUNTS_VIEW_PERMISSION, ServiceAccountAPIError, type ServiceAccountAudience, type ServiceAccountClient, type ServiceAccountCredential, type ServiceAccountCredentialPage, type ServiceAccountEnvelope, type ServiceAccountGrant, type ServiceAccountGrantInput, type ServiceAccountGrantPage, type ServiceAccountGrantScope, type ServiceAccountListParams, type ServiceAccountOperation, type ServiceAccountPage, type ServiceAccountRequestObservation, type ServiceAccountRequestOutcome, type ServiceAccountView, type UpdateServiceAccountInput, createServiceAccountClient, isServiceAccountAPIError };
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
// ../transport/src/index.ts
|
|
2
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
3
|
+
var TransportError = class extends Error {
|
|
4
|
+
constructor(failure) {
|
|
5
|
+
super(failure.message || failure.outcome);
|
|
6
|
+
this.failure = failure;
|
|
7
|
+
this.name = "TransportError";
|
|
8
|
+
}
|
|
9
|
+
failure;
|
|
10
|
+
};
|
|
11
|
+
function isTransportError(error) {
|
|
12
|
+
return error instanceof TransportError || typeof error === "object" && error !== null && error.name === "TransportError";
|
|
13
|
+
}
|
|
14
|
+
function positiveNumber(value, label, fail) {
|
|
15
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) throw fail(`${label} must be a positive finite number`);
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
function nonNegativeInteger(value, label, fail) {
|
|
19
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw fail(`${label} must be a non-negative safe integer`);
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
function requiredString(value, label, fail) {
|
|
23
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
24
|
+
if (!normalized) throw fail(`${label} is required`);
|
|
25
|
+
return normalized;
|
|
26
|
+
}
|
|
27
|
+
function backoffDelay(attempt, maxDelayMs) {
|
|
28
|
+
const base = Math.min(250 * 2 ** Math.max(attempt, 0), maxDelayMs);
|
|
29
|
+
return Math.floor(Math.random() * base);
|
|
30
|
+
}
|
|
31
|
+
function retryDelay(response, attempt, maxDelayMs) {
|
|
32
|
+
const value = response.headers.get("Retry-After");
|
|
33
|
+
if (!value) return backoffDelay(attempt, maxDelayMs);
|
|
34
|
+
const seconds = Number(value);
|
|
35
|
+
if (Number.isFinite(seconds)) return Math.min(Math.max(seconds * 1e3, 0), maxDelayMs);
|
|
36
|
+
const date = Date.parse(value);
|
|
37
|
+
return Number.isNaN(date) ? backoffDelay(attempt, maxDelayMs) : Math.min(Math.max(date - Date.now(), 0), maxDelayMs);
|
|
38
|
+
}
|
|
39
|
+
function wait(ms) {
|
|
40
|
+
return ms <= 0 ? Promise.resolve() : new Promise((resolve) => setTimeout(resolve, ms));
|
|
41
|
+
}
|
|
42
|
+
function combineSignals(...signals) {
|
|
43
|
+
const active = signals.filter((signal) => Boolean(signal));
|
|
44
|
+
if (active.length === 0) return void 0;
|
|
45
|
+
if (active.length === 1) return active[0];
|
|
46
|
+
return AbortSignal.any(active);
|
|
47
|
+
}
|
|
48
|
+
function isRetryableRequest(method, headers, retry) {
|
|
49
|
+
if (method === "GET") return true;
|
|
50
|
+
return retry.idempotentWrites === true && headers.has("Idempotency-Key");
|
|
51
|
+
}
|
|
52
|
+
async function performRequest(transport, url, init, observe, decode) {
|
|
53
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
54
|
+
const headers = new Headers(init.headers);
|
|
55
|
+
const startedAt = Date.now();
|
|
56
|
+
const retryable = isRetryableRequest(method, headers, transport.retry);
|
|
57
|
+
const maxAttempts = retryable ? transport.retry.attempts : 0;
|
|
58
|
+
let attempts = 0;
|
|
59
|
+
let status = 0;
|
|
60
|
+
let requestId = "";
|
|
61
|
+
let outcome = "network_error";
|
|
62
|
+
const fail = (failure) => {
|
|
63
|
+
outcome = failure.outcome;
|
|
64
|
+
throw new TransportError({ requestId, ...failure });
|
|
65
|
+
};
|
|
66
|
+
try {
|
|
67
|
+
let response;
|
|
68
|
+
for (let attempt = 0; attempt <= maxAttempts; attempt += 1) {
|
|
69
|
+
attempts = attempt + 1;
|
|
70
|
+
const controller = new AbortController();
|
|
71
|
+
const timeout = setTimeout(() => controller.abort(), transport.timeoutMs);
|
|
72
|
+
try {
|
|
73
|
+
response = await transport.fetch(url, {
|
|
74
|
+
...init,
|
|
75
|
+
method,
|
|
76
|
+
headers,
|
|
77
|
+
signal: combineSignals(transport.signal, init.signal, controller.signal)
|
|
78
|
+
});
|
|
79
|
+
} catch {
|
|
80
|
+
if (controller.signal.aborted) fail({ outcome: "timeout", status: 0, code: "", message: "" });
|
|
81
|
+
if (transport.signal?.aborted || init.signal?.aborted) fail({ outcome: "cancelled", status: 0, code: "", message: "" });
|
|
82
|
+
if (attempt < maxAttempts) {
|
|
83
|
+
await wait(backoffDelay(attempt, transport.retry.maxDelayMs));
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
fail({ outcome: "network_error", status: 0, code: "", message: "" });
|
|
87
|
+
} finally {
|
|
88
|
+
clearTimeout(timeout);
|
|
89
|
+
}
|
|
90
|
+
if (!RETRYABLE_STATUSES.has(response.status) || attempt === maxAttempts) break;
|
|
91
|
+
await wait(retryDelay(response, attempt, transport.retry.maxDelayMs));
|
|
92
|
+
}
|
|
93
|
+
if (!response) return fail({ outcome: "network_error", status: 0, code: "", message: "" });
|
|
94
|
+
status = response.status;
|
|
95
|
+
requestId = response.headers.get("X-Request-ID") ?? "";
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
const envelope = await response.json().catch(() => ({}));
|
|
98
|
+
if (typeof envelope.request_id === "string") requestId = envelope.request_id;
|
|
99
|
+
return fail({
|
|
100
|
+
outcome: "api_error",
|
|
101
|
+
status,
|
|
102
|
+
code: typeof envelope.code === "string" ? envelope.code : "",
|
|
103
|
+
message: typeof envelope.message === "string" ? envelope.message : "",
|
|
104
|
+
requestId
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
let payload;
|
|
108
|
+
try {
|
|
109
|
+
if (status === 204) {
|
|
110
|
+
payload = void 0;
|
|
111
|
+
} else {
|
|
112
|
+
const text = await response.text();
|
|
113
|
+
const parsed = text ? JSON.parse(text) : void 0;
|
|
114
|
+
payload = decode ? decode(parsed) : parsed;
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
return fail({ outcome: "invalid_response", status, code: "", message: "", requestId });
|
|
118
|
+
}
|
|
119
|
+
outcome = "success";
|
|
120
|
+
return { payload, status, requestId };
|
|
121
|
+
} finally {
|
|
122
|
+
if (observe) {
|
|
123
|
+
try {
|
|
124
|
+
observe({ method, outcome, status, requestId, durationMs: Math.max(0, Date.now() - startedAt), attempts });
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/service-accounts.ts
|
|
132
|
+
var SERVICE_ACCOUNTS_VIEW_PERMISSION = "platform.service_accounts.view";
|
|
133
|
+
var SERVICE_ACCOUNTS_MANAGE_PERMISSION = "platform.service_accounts.manage";
|
|
134
|
+
var ServiceAccountAPIError = class extends Error {
|
|
135
|
+
constructor(message, status, code, requestId) {
|
|
136
|
+
super(message);
|
|
137
|
+
this.status = status;
|
|
138
|
+
this.code = code;
|
|
139
|
+
this.requestId = requestId;
|
|
140
|
+
this.name = "ServiceAccountAPIError";
|
|
141
|
+
}
|
|
142
|
+
status;
|
|
143
|
+
code;
|
|
144
|
+
requestId;
|
|
145
|
+
};
|
|
146
|
+
function isServiceAccountAPIError(error) {
|
|
147
|
+
return error instanceof ServiceAccountAPIError || typeof error === "object" && error !== null && error.name === "ServiceAccountAPIError" && typeof error.status === "number" && typeof error.code === "string" && typeof error.requestId === "string";
|
|
148
|
+
}
|
|
149
|
+
var basePath = "/api/v1/platform/identity/service-accounts";
|
|
150
|
+
var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
151
|
+
var credentialIDPattern = /^[0-9a-f]{32}$/;
|
|
152
|
+
var operationIDPattern = /^[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*)+$/;
|
|
153
|
+
var timestampPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
154
|
+
var secretPattern = /^mml_sa_([0-9a-f]{32})_([0-9a-f]{64})$/;
|
|
155
|
+
var configurationError = (message) => new Error(message);
|
|
156
|
+
function invalidResponse() {
|
|
157
|
+
throw new Error("invalid Service Account response");
|
|
158
|
+
}
|
|
159
|
+
function objectValue(value) {
|
|
160
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) invalidResponse();
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
function responseString(value, options = {}) {
|
|
164
|
+
if (value === void 0 && options.optional) return void 0;
|
|
165
|
+
if (typeof value !== "string" || value.length === 0) invalidResponse();
|
|
166
|
+
if (options.pattern && !options.pattern.test(value)) invalidResponse();
|
|
167
|
+
if (options.timestamp && (!timestampPattern.test(value) || !Number.isFinite(Date.parse(value)))) invalidResponse();
|
|
168
|
+
return value;
|
|
169
|
+
}
|
|
170
|
+
function responseStrings(value) {
|
|
171
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.length === 0)) {
|
|
172
|
+
invalidResponse();
|
|
173
|
+
}
|
|
174
|
+
return [...value];
|
|
175
|
+
}
|
|
176
|
+
function positiveIntegerResponse(value) {
|
|
177
|
+
if (!Number.isSafeInteger(value) || Number(value) < 1) invalidResponse();
|
|
178
|
+
return Number(value);
|
|
179
|
+
}
|
|
180
|
+
function parseServiceAccount(value) {
|
|
181
|
+
const account = objectValue(value);
|
|
182
|
+
if (typeof account.enabled !== "boolean") invalidResponse();
|
|
183
|
+
return {
|
|
184
|
+
id: responseString(account.id, { pattern: uuidPattern }),
|
|
185
|
+
name: responseString(account.name),
|
|
186
|
+
purpose: responseString(account.purpose),
|
|
187
|
+
owner_user_id: responseString(account.owner_user_id),
|
|
188
|
+
enabled: account.enabled,
|
|
189
|
+
version: positiveIntegerResponse(account.version),
|
|
190
|
+
created_by: responseString(account.created_by),
|
|
191
|
+
updated_by: responseString(account.updated_by),
|
|
192
|
+
created_at: responseString(account.created_at, { timestamp: true }),
|
|
193
|
+
updated_at: responseString(account.updated_at, { timestamp: true })
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function parseServiceAccountEnvelope(value) {
|
|
197
|
+
return { service_account: parseServiceAccount(objectValue(value).service_account) };
|
|
198
|
+
}
|
|
199
|
+
function parsePage(value, parseItem) {
|
|
200
|
+
const page = objectValue(value);
|
|
201
|
+
if (!Array.isArray(page.items) || typeof page.has_more !== "boolean") invalidResponse();
|
|
202
|
+
const nextCursor = responseString(page.next_cursor, { optional: true });
|
|
203
|
+
if (Boolean(nextCursor) !== page.has_more) invalidResponse();
|
|
204
|
+
return { items: page.items.map(parseItem), next_cursor: nextCursor, has_more: page.has_more };
|
|
205
|
+
}
|
|
206
|
+
function parseGrant(value) {
|
|
207
|
+
const grant = objectValue(value);
|
|
208
|
+
return {
|
|
209
|
+
grant_key: responseString(grant.grant_key),
|
|
210
|
+
scope_type: responseString(grant.scope_type),
|
|
211
|
+
scope_id: responseString(grant.scope_id),
|
|
212
|
+
id: responseString(grant.id, { optional: true }),
|
|
213
|
+
subject_id: responseString(grant.subject_id, { optional: true }),
|
|
214
|
+
subject_type: responseString(grant.subject_type, { optional: true }),
|
|
215
|
+
granted_by: responseString(grant.granted_by, { optional: true }),
|
|
216
|
+
created_at: responseString(grant.created_at, { optional: true, timestamp: true })
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function parseCredential(value) {
|
|
220
|
+
const credential = objectValue(value);
|
|
221
|
+
const audience = responseString(credential.audience);
|
|
222
|
+
if (audience !== "platform" && !audience.startsWith("dash:")) invalidResponse();
|
|
223
|
+
return {
|
|
224
|
+
id: responseString(credential.id, { pattern: credentialIDPattern }),
|
|
225
|
+
service_account_id: responseString(credential.service_account_id, { pattern: uuidPattern }),
|
|
226
|
+
name: responseString(credential.name),
|
|
227
|
+
audience,
|
|
228
|
+
permissions: responseStrings(credential.permissions),
|
|
229
|
+
operation_ids: responseStrings(credential.operation_ids),
|
|
230
|
+
dash_ids: responseStrings(credential.dash_ids),
|
|
231
|
+
max_output_bytes: positiveIntegerResponse(credential.max_output_bytes),
|
|
232
|
+
created_by: responseString(credential.created_by),
|
|
233
|
+
created_at: responseString(credential.created_at, { timestamp: true }),
|
|
234
|
+
expires_at: responseString(credential.expires_at, { timestamp: true }),
|
|
235
|
+
last_used_at: responseString(credential.last_used_at, { optional: true, timestamp: true }),
|
|
236
|
+
revoked_at: responseString(credential.revoked_at, { optional: true, timestamp: true })
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function parseMintedCredential(value) {
|
|
240
|
+
const minted = objectValue(value);
|
|
241
|
+
const credential = parseCredential(minted.credential);
|
|
242
|
+
const secret = responseString(minted.secret);
|
|
243
|
+
const match = secret.match(secretPattern);
|
|
244
|
+
if (!match || match[1] !== credential.id) invalidResponse();
|
|
245
|
+
return { credential, secret };
|
|
246
|
+
}
|
|
247
|
+
function canonicalID(value, label, pattern) {
|
|
248
|
+
const id = requiredString(value, label, configurationError);
|
|
249
|
+
if (!pattern.test(id)) throw configurationError(`${label} is invalid`);
|
|
250
|
+
return id;
|
|
251
|
+
}
|
|
252
|
+
function version(value) {
|
|
253
|
+
if (!Number.isSafeInteger(value) || value < 1) throw configurationError("expected_version must be a positive integer");
|
|
254
|
+
return value;
|
|
255
|
+
}
|
|
256
|
+
function reason(value) {
|
|
257
|
+
const normalized = requiredString(value, "reason", configurationError);
|
|
258
|
+
if (normalized.length > 500) throw configurationError("reason must be 500 characters or fewer");
|
|
259
|
+
return normalized;
|
|
260
|
+
}
|
|
261
|
+
function accountFields(input) {
|
|
262
|
+
const name = requiredString(input.name, "name", configurationError);
|
|
263
|
+
const purpose = requiredString(input.purpose, "purpose", configurationError);
|
|
264
|
+
const ownerUserID = requiredString(input.owner_user_id, "owner_user_id", configurationError);
|
|
265
|
+
if (name.length > 64) throw configurationError("name must be 64 characters or fewer");
|
|
266
|
+
if (purpose.length > 500) throw configurationError("purpose must be 500 characters or fewer");
|
|
267
|
+
return { name, purpose, owner_user_id: ownerUserID, reason: reason(input.reason) };
|
|
268
|
+
}
|
|
269
|
+
function listQuery(params) {
|
|
270
|
+
const query = new URLSearchParams();
|
|
271
|
+
const cursor = params.cursor?.trim();
|
|
272
|
+
if (cursor) query.set("cursor", cursor);
|
|
273
|
+
if (params.limit !== void 0) {
|
|
274
|
+
const limit = positiveNumber(params.limit, "limit", configurationError);
|
|
275
|
+
if (!Number.isSafeInteger(limit) || limit > 100) throw configurationError("limit must be an integer from 1 to 100");
|
|
276
|
+
query.set("limit", String(limit));
|
|
277
|
+
}
|
|
278
|
+
return query.size ? `?${query}` : "";
|
|
279
|
+
}
|
|
280
|
+
function grantInput(value) {
|
|
281
|
+
const grantKey = requiredString(value.grant_key, "grant_key", configurationError);
|
|
282
|
+
if (value.scope_type !== "platform" && value.scope_type !== "dash_id") {
|
|
283
|
+
throw configurationError("scope_type must be platform or dash_id");
|
|
284
|
+
}
|
|
285
|
+
const scopeID = requiredString(value.scope_id, "scope_id", configurationError);
|
|
286
|
+
if (value.scope_type === "platform" && scopeID !== "*") {
|
|
287
|
+
throw configurationError('platform grants must use scope_id "*"');
|
|
288
|
+
}
|
|
289
|
+
if (value.scope_type === "dash_id" && scopeID === "*") {
|
|
290
|
+
throw configurationError("service-account Dash grants must name one Dash");
|
|
291
|
+
}
|
|
292
|
+
return { grant_key: grantKey, scope_type: value.scope_type, scope_id: scopeID };
|
|
293
|
+
}
|
|
294
|
+
function mintInput(value) {
|
|
295
|
+
const name = requiredString(value.name, "name", configurationError);
|
|
296
|
+
if (name.length > 64) throw configurationError("name must be 64 characters or fewer");
|
|
297
|
+
const audience = requiredString(value.audience, "audience", configurationError);
|
|
298
|
+
if (audience !== "platform" && !audience.startsWith("dash:")) {
|
|
299
|
+
throw configurationError("audience must be platform or dash:<dash_id>");
|
|
300
|
+
}
|
|
301
|
+
const operationIDs = [...new Set(value.operation_ids.map((item) => requiredString(item, "operation_id", configurationError)))];
|
|
302
|
+
if (operationIDs.length < 1 || operationIDs.length > 64 || operationIDs.some((item) => !operationIDPattern.test(item))) {
|
|
303
|
+
throw configurationError("operation_ids must contain 1 to 64 stable operation ids");
|
|
304
|
+
}
|
|
305
|
+
const dashIDs = [...new Set(value.dash_ids.map((item) => requiredString(item, "dash_id", configurationError)))];
|
|
306
|
+
if (dashIDs.length > 32 || dashIDs.includes("*")) throw configurationError("dash_ids must name at most 32 explicit Dashes");
|
|
307
|
+
if (audience.startsWith("dash:") && (dashIDs.length !== 1 || dashIDs[0] !== audience.slice(5))) {
|
|
308
|
+
throw configurationError("a Dash audience requires exactly its route Dash in dash_ids");
|
|
309
|
+
}
|
|
310
|
+
if (!Number.isSafeInteger(value.expires_in_days) || value.expires_in_days < 1 || value.expires_in_days > 30) {
|
|
311
|
+
throw configurationError("expires_in_days must be an integer from 1 to 30");
|
|
312
|
+
}
|
|
313
|
+
if (value.max_output_bytes !== void 0 && (!Number.isSafeInteger(value.max_output_bytes) || value.max_output_bytes < 1 || value.max_output_bytes > 1048576)) {
|
|
314
|
+
throw configurationError("max_output_bytes must be an integer from 1 to 1048576");
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
name,
|
|
318
|
+
audience,
|
|
319
|
+
operation_ids: operationIDs,
|
|
320
|
+
dash_ids: dashIDs,
|
|
321
|
+
expires_in_days: value.expires_in_days,
|
|
322
|
+
expected_version: version(value.expected_version),
|
|
323
|
+
reason: reason(value.reason),
|
|
324
|
+
...value.max_output_bytes === void 0 ? {} : { max_output_bytes: value.max_output_bytes }
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function createServiceAccountClient(options) {
|
|
328
|
+
const baseUrl = requiredString(options.baseUrl, "baseUrl", configurationError).replace(/\/+$/, "");
|
|
329
|
+
const transport = {
|
|
330
|
+
fetch: (input, init) => (options.fetch ?? globalThis.fetch)(input, init),
|
|
331
|
+
timeoutMs: positiveNumber(options.timeoutMs ?? 1e4, "timeoutMs", configurationError),
|
|
332
|
+
retry: {
|
|
333
|
+
attempts: nonNegativeInteger(options.retryAttempts ?? 1, "retryAttempts", configurationError),
|
|
334
|
+
maxDelayMs: positiveNumber(options.maxRetryDelayMs ?? 2e3, "maxRetryDelayMs", configurationError),
|
|
335
|
+
idempotentWrites: false
|
|
336
|
+
},
|
|
337
|
+
signal: options.signal
|
|
338
|
+
};
|
|
339
|
+
async function request(operation, path, decode, init = {}) {
|
|
340
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
341
|
+
const report = options.observe && ((event) => {
|
|
342
|
+
options.observe?.({ operation, ...event });
|
|
343
|
+
});
|
|
344
|
+
let headers;
|
|
345
|
+
try {
|
|
346
|
+
const accessToken = requiredString(await options.getAccessToken() ?? "", "access token", configurationError);
|
|
347
|
+
headers = new Headers(await options.getHeaders?.());
|
|
348
|
+
new Headers(init.headers).forEach((value, key) => headers.set(key, value));
|
|
349
|
+
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
350
|
+
headers.set("Accept", "application/json");
|
|
351
|
+
if (init.body !== void 0) headers.set("Content-Type", "application/json");
|
|
352
|
+
} catch (error) {
|
|
353
|
+
try {
|
|
354
|
+
report?.({ method, outcome: "network_error", status: 0, requestId: "", durationMs: 0, attempts: 0 });
|
|
355
|
+
} catch {
|
|
356
|
+
}
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
const result = await performRequest(
|
|
361
|
+
transport,
|
|
362
|
+
`${baseUrl}${path}`,
|
|
363
|
+
{ ...init, method, headers, cache: "no-store" },
|
|
364
|
+
report,
|
|
365
|
+
decode
|
|
366
|
+
);
|
|
367
|
+
return result.payload;
|
|
368
|
+
} catch (error) {
|
|
369
|
+
if (!isTransportError(error)) throw error;
|
|
370
|
+
const { outcome, status, code, message, requestId } = error.failure;
|
|
371
|
+
switch (outcome) {
|
|
372
|
+
case "timeout":
|
|
373
|
+
throw new ServiceAccountAPIError("Service Account request timed out", 0, "service_account_request_timeout", "");
|
|
374
|
+
case "cancelled":
|
|
375
|
+
throw new ServiceAccountAPIError("Service Account request was cancelled", 0, "service_account_request_cancelled", "");
|
|
376
|
+
case "network_error":
|
|
377
|
+
throw new ServiceAccountAPIError("Service Account service is unavailable", 0, "service_account_service_unavailable", "");
|
|
378
|
+
case "invalid_response":
|
|
379
|
+
throw new ServiceAccountAPIError("Service Account service returned an invalid response", status, "service_account_invalid_response", requestId);
|
|
380
|
+
case "api_error":
|
|
381
|
+
throw new ServiceAccountAPIError(message || code || "Service Account request failed", status, code, requestId);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function accountPath(serviceAccountID) {
|
|
386
|
+
return `${basePath}/${canonicalID(serviceAccountID, "serviceAccountId", uuidPattern)}`;
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
list(params = {}) {
|
|
390
|
+
return request(
|
|
391
|
+
"platform.identity.service_accounts.list",
|
|
392
|
+
`${basePath}${listQuery(params)}`,
|
|
393
|
+
(payload) => parsePage(payload, parseServiceAccount)
|
|
394
|
+
);
|
|
395
|
+
},
|
|
396
|
+
get(serviceAccountID) {
|
|
397
|
+
return request(
|
|
398
|
+
"platform.identity.service_accounts.get",
|
|
399
|
+
accountPath(serviceAccountID),
|
|
400
|
+
parseServiceAccountEnvelope
|
|
401
|
+
);
|
|
402
|
+
},
|
|
403
|
+
create(input) {
|
|
404
|
+
return request(
|
|
405
|
+
"platform.identity.service_accounts.create",
|
|
406
|
+
basePath,
|
|
407
|
+
parseServiceAccountEnvelope,
|
|
408
|
+
{ method: "POST", body: JSON.stringify(accountFields(input)) }
|
|
409
|
+
);
|
|
410
|
+
},
|
|
411
|
+
update(serviceAccountID, input) {
|
|
412
|
+
const fields = accountFields(input);
|
|
413
|
+
return request(
|
|
414
|
+
"platform.identity.service_accounts.update",
|
|
415
|
+
accountPath(serviceAccountID),
|
|
416
|
+
parseServiceAccountEnvelope,
|
|
417
|
+
{
|
|
418
|
+
method: "PUT",
|
|
419
|
+
body: JSON.stringify({ ...fields, enabled: input.enabled, expected_version: version(input.expected_version) })
|
|
420
|
+
}
|
|
421
|
+
);
|
|
422
|
+
},
|
|
423
|
+
listGrants(serviceAccountID) {
|
|
424
|
+
return request(
|
|
425
|
+
"platform.identity.service_accounts.grants.list",
|
|
426
|
+
`${accountPath(serviceAccountID)}/grants`,
|
|
427
|
+
(payload) => {
|
|
428
|
+
const page = objectValue(payload);
|
|
429
|
+
if (!Array.isArray(page.items)) invalidResponse();
|
|
430
|
+
return { items: page.items.map(parseGrant) };
|
|
431
|
+
}
|
|
432
|
+
);
|
|
433
|
+
},
|
|
434
|
+
replaceGrants(serviceAccountID, input) {
|
|
435
|
+
if (!Array.isArray(input.grants) || input.grants.length > 32) {
|
|
436
|
+
throw configurationError("grants must contain at most 32 entries");
|
|
437
|
+
}
|
|
438
|
+
return request(
|
|
439
|
+
"platform.identity.service_accounts.grants.replace",
|
|
440
|
+
`${accountPath(serviceAccountID)}/grants`,
|
|
441
|
+
parseServiceAccountEnvelope,
|
|
442
|
+
{
|
|
443
|
+
method: "PUT",
|
|
444
|
+
body: JSON.stringify({
|
|
445
|
+
grants: input.grants.map(grantInput),
|
|
446
|
+
expected_version: version(input.expected_version),
|
|
447
|
+
reason: reason(input.reason)
|
|
448
|
+
})
|
|
449
|
+
}
|
|
450
|
+
);
|
|
451
|
+
},
|
|
452
|
+
listCredentials(serviceAccountID, params = {}) {
|
|
453
|
+
return request(
|
|
454
|
+
"platform.identity.service_accounts.credentials.list",
|
|
455
|
+
`${accountPath(serviceAccountID)}/credentials${listQuery(params)}`,
|
|
456
|
+
(payload) => parsePage(payload, parseCredential)
|
|
457
|
+
);
|
|
458
|
+
},
|
|
459
|
+
mintCredential(serviceAccountID, input) {
|
|
460
|
+
return request(
|
|
461
|
+
"platform.identity.service_accounts.credentials.mint",
|
|
462
|
+
`${accountPath(serviceAccountID)}/credentials`,
|
|
463
|
+
parseMintedCredential,
|
|
464
|
+
{ method: "POST", body: JSON.stringify(mintInput(input)) }
|
|
465
|
+
);
|
|
466
|
+
},
|
|
467
|
+
revokeCredential(serviceAccountID, credentialID, input) {
|
|
468
|
+
return request(
|
|
469
|
+
"platform.identity.service_accounts.credentials.revoke",
|
|
470
|
+
`${accountPath(serviceAccountID)}/credentials/${canonicalID(credentialID, "credentialId", credentialIDPattern)}`,
|
|
471
|
+
parseServiceAccountEnvelope,
|
|
472
|
+
{
|
|
473
|
+
method: "DELETE",
|
|
474
|
+
body: JSON.stringify({ expected_version: version(input.expected_version), reason: reason(input.reason) })
|
|
475
|
+
}
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export { SERVICE_ACCOUNTS_MANAGE_PERMISSION, SERVICE_ACCOUNTS_VIEW_PERMISSION, ServiceAccountAPIError, createServiceAccountClient, isServiceAccountAPIError };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c1m-public/platform-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Session and authorization helpers for C1M platform surfaces. The root is runtime-neutral (Web Request/Response): token/session reads, the first-party handoff (PKCE target + source), proxy session gate and coalesced refresh; ./next adds the server-only guard; ./access carries the resolved access snapshot, typed permission keys, predicates, a Next loader and React bindings",
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"types": "./dist/mcp-tokens.d.ts",
|
|
25
25
|
"default": "./dist/mcp-tokens.js"
|
|
26
26
|
},
|
|
27
|
+
"./service-accounts": {
|
|
28
|
+
"types": "./dist/service-accounts.d.ts",
|
|
29
|
+
"default": "./dist/service-accounts.js"
|
|
30
|
+
},
|
|
27
31
|
"./access": {
|
|
28
32
|
"types": "./dist/access/index.d.ts",
|
|
29
33
|
"default": "./dist/access/index.js"
|