@c1m-public/platform-auth 0.13.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -18
- package/dist/access/index.d.ts +1 -1
- package/dist/access/index.js +4 -2
- package/dist/access/next.d.ts +2 -2
- package/dist/access/next.js +4 -2
- package/dist/{index-CPjLkXA-.d.ts → index-DExfJ-AE.d.ts} +3 -2
- package/dist/next.d.ts +1 -1
- package/dist/next.js +10 -5
- package/dist/service-accounts.d.ts +5 -2
- package/dist/service-accounts.js +8 -5
- package/package.json +1 -5
- package/dist/mcp-tokens.d.ts +0 -79
- package/dist/mcp-tokens.js +0 -357
package/README.md
CHANGED
|
@@ -11,7 +11,6 @@ need — the boundary is the subpath.
|
|
|
11
11
|
```
|
|
12
12
|
. session: token reads, handoff, proxy gate, refresh (Web Request/Response)
|
|
13
13
|
./next session, server-only guard (Next route handlers)
|
|
14
|
-
./mcp-tokens self-service MCP PAT lifecycle (framework-neutral)
|
|
15
14
|
./service-accounts operator-owned service accounts and credentials (framework-neutral)
|
|
16
15
|
./access authorization: snapshot, keys, predicates (framework-neutral)
|
|
17
16
|
./access/next access loader: require / forbidden / invalidate (Next, server-only)
|
|
@@ -21,23 +20,6 @@ need — the boundary is the subpath.
|
|
|
21
20
|
`next` and `react` are **optional** peer dependencies: a BFF that only reads
|
|
22
21
|
sessions never needs them.
|
|
23
22
|
|
|
24
|
-
## `./mcp-tokens` — MCP personal access tokens
|
|
25
|
-
|
|
26
|
-
`createMCPTokenClient` lists, mints, and revokes the signed-in platform user's
|
|
27
|
-
own MCP tokens under `/api/v1/platform/auth/me/mcp-tokens`. The client resolves
|
|
28
|
-
the bearer on every request, never retries mint or revoke, and emits bounded
|
|
29
|
-
observations containing operation metadata but no URL, bearer, or PAT material.
|
|
30
|
-
|
|
31
|
-
The subpath also exports `MCP_ACCESS_PERMISSION`,
|
|
32
|
-
`MCP_TOKENS_MANAGE_PERMISSION`, `MCP_TOOL_PERMISSIONS`,
|
|
33
|
-
`MCP_WILDCARD_TOOL_PERMISSIONS`, the request/response types, and the structural
|
|
34
|
-
`MCPTokenAPIError` predicate. The permission ceiling `["*"]` follows future
|
|
35
|
-
read-only tool permissions; mutating permissions must always be named
|
|
36
|
-
explicitly. A minted `raw_token` is a one-time value: callers must keep it out
|
|
37
|
-
of URLs, logs, storage, and telemetry. A `recent_authentication_required` API
|
|
38
|
-
error is a step-up challenge, not an expired session; send the user through the
|
|
39
|
-
host application's re-authentication path before retrying the mint.
|
|
40
|
-
|
|
41
23
|
## `./service-accounts` — operator service accounts
|
|
42
24
|
|
|
43
25
|
`createServiceAccountClient` manages the operator-owned service accounts under
|
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-DExfJ-AE.js';
|
package/dist/access/index.js
CHANGED
|
@@ -20,7 +20,6 @@ var PLATFORM_PERMISSIONS = [
|
|
|
20
20
|
"platform.docs.manage",
|
|
21
21
|
"platform.docs.view",
|
|
22
22
|
"platform.mcp.access",
|
|
23
|
-
"platform.mcp.tokens.manage",
|
|
24
23
|
"platform.observability.manage",
|
|
25
24
|
"platform.observability.view",
|
|
26
25
|
"platform.offboarding.view",
|
|
@@ -49,6 +48,7 @@ var DASH_PERMISSIONS = [
|
|
|
49
48
|
"dash.customers.manage",
|
|
50
49
|
"dash.domains.manage",
|
|
51
50
|
"dash.domains.view",
|
|
51
|
+
"dash.mcp.access",
|
|
52
52
|
"dash.members.manage",
|
|
53
53
|
"dash.ownership.transfer",
|
|
54
54
|
"dash.plans.manage",
|
|
@@ -109,7 +109,9 @@ var AccessSnapshotError = class extends Error {
|
|
|
109
109
|
}
|
|
110
110
|
};
|
|
111
111
|
function isAccessSnapshotError(error, code) {
|
|
112
|
-
|
|
112
|
+
if (typeof error !== "object" || error === null) return false;
|
|
113
|
+
const candidate = error;
|
|
114
|
+
return candidate.name === "AccessSnapshotError" && typeof candidate.message === "string" && (candidate.code === "unauthenticated" || candidate.code === "forbidden" || candidate.code === "unavailable" || candidate.code === "invalid_snapshot") && (candidate.status === void 0 || Number.isInteger(candidate.status) && candidate.status >= 100 && candidate.status <= 599) && (code === void 0 || candidate.code === code);
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
// src/access/snapshot.ts
|
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-DExfJ-AE.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-DExfJ-AE.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
|
@@ -61,7 +61,9 @@ var AccessSnapshotError = class extends Error {
|
|
|
61
61
|
}
|
|
62
62
|
};
|
|
63
63
|
function isAccessSnapshotError(error, code) {
|
|
64
|
-
|
|
64
|
+
if (typeof error !== "object" || error === null) return false;
|
|
65
|
+
const candidate = error;
|
|
66
|
+
return candidate.name === "AccessSnapshotError" && typeof candidate.message === "string" && (candidate.code === "unauthenticated" || candidate.code === "forbidden" || candidate.code === "unavailable" || candidate.code === "invalid_snapshot") && (candidate.status === void 0 || Number.isInteger(candidate.status) && candidate.status >= 100 && candidate.status <= 599) && (code === void 0 || candidate.code === code);
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
// ../transport/src/forwarding.ts
|
|
@@ -276,7 +278,6 @@ var PLATFORM_PERMISSIONS = [
|
|
|
276
278
|
"platform.docs.manage",
|
|
277
279
|
"platform.docs.view",
|
|
278
280
|
"platform.mcp.access",
|
|
279
|
-
"platform.mcp.tokens.manage",
|
|
280
281
|
"platform.observability.manage",
|
|
281
282
|
"platform.observability.view",
|
|
282
283
|
"platform.offboarding.view",
|
|
@@ -305,6 +306,7 @@ var DASH_PERMISSIONS = [
|
|
|
305
306
|
"dash.customers.manage",
|
|
306
307
|
"dash.domains.manage",
|
|
307
308
|
"dash.domains.view",
|
|
309
|
+
"dash.mcp.access",
|
|
308
310
|
"dash.members.manage",
|
|
309
311
|
"dash.ownership.transfer",
|
|
310
312
|
"dash.plans.manage",
|
|
@@ -1,8 +1,8 @@
|
|
|
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.
|
|
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.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
|
-
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"];
|
|
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.mcp.access", "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"];
|
|
6
6
|
type DashPermissionKey = (typeof DASH_PERMISSIONS)[number];
|
|
7
7
|
/** Membership-mirrored dash role bundles (grant keys at dash scope). */
|
|
8
8
|
declare const DASH_ROLES: readonly ["dash.admin", "dash.owner"];
|
|
@@ -36,6 +36,7 @@ declare class AccessSnapshotError extends Error {
|
|
|
36
36
|
readonly status: number | undefined;
|
|
37
37
|
constructor(code: AccessErrorCode, message: string, status?: number);
|
|
38
38
|
}
|
|
39
|
+
/** Class identity is not shared by /access and /access/next (or duplicate installs). */
|
|
39
40
|
declare function isAccessSnapshotError(error: unknown, code?: AccessErrorCode): error is AccessSnapshotError;
|
|
40
41
|
|
|
41
42
|
/** Scope a permission entry applies to. Mirrors c1m-server authz.Scope*. */
|
package/dist/next.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ declare class PlatformServerFetchError extends Error {
|
|
|
30
30
|
declare const isSessionExpiredError: (error: unknown) => error is SessionExpiredError;
|
|
31
31
|
declare const isRecentAuthenticationRequiredError: (error: unknown) => error is RecentAuthenticationRequiredError;
|
|
32
32
|
declare const isSSOReauthenticationRequiredError: (error: unknown) => error is SSOReauthenticationRequiredError;
|
|
33
|
-
declare
|
|
33
|
+
declare function isPlatformServerFetchError(error: unknown): error is PlatformServerFetchError;
|
|
34
34
|
/** Map a 401 body's `code` onto the session error it means. */
|
|
35
35
|
declare function authenticationErrorFor(code: unknown): SessionExpiredError | RecentAuthenticationRequiredError | SSOReauthenticationRequiredError;
|
|
36
36
|
/**
|
package/dist/next.js
CHANGED
|
@@ -595,23 +595,28 @@ var PlatformServerFetchError = class extends Error {
|
|
|
595
595
|
code;
|
|
596
596
|
requestId;
|
|
597
597
|
};
|
|
598
|
-
var named = (error, name) => typeof error === "object" && error !== null && error.name === name;
|
|
598
|
+
var named = (error, name) => typeof error === "object" && error !== null && error.name === name && typeof error.message === "string";
|
|
599
599
|
var isSessionExpiredError = (error) => named(error, "SessionExpiredError");
|
|
600
600
|
var isRecentAuthenticationRequiredError = (error) => named(error, "RecentAuthenticationRequiredError");
|
|
601
601
|
var isSSOReauthenticationRequiredError = (error) => named(error, "SSOReauthenticationRequiredError");
|
|
602
|
-
|
|
602
|
+
function isPlatformServerFetchError(error) {
|
|
603
|
+
if (!named(error, "PlatformServerFetchError")) return false;
|
|
604
|
+
const candidate = error;
|
|
605
|
+
return typeof candidate.path === "string" && (candidate.status === void 0 || Number.isInteger(candidate.status) && candidate.status >= 100 && candidate.status <= 599) && (candidate.code === void 0 || typeof candidate.code === "string") && (candidate.requestId === void 0 || typeof candidate.requestId === "string");
|
|
606
|
+
}
|
|
603
607
|
function authenticationErrorFor(code) {
|
|
604
608
|
if (code === "sso_reauthentication_required") return new SSOReauthenticationRequiredError();
|
|
605
609
|
if (code === "recent_authentication_required") return new RecentAuthenticationRequiredError();
|
|
606
610
|
return new SessionExpiredError();
|
|
607
611
|
}
|
|
608
612
|
function isControlAPIErrorLike(error) {
|
|
609
|
-
return typeof error === "object" && error !== null && error.name === "ControlAPIError" && typeof error.status === "number" && typeof error.code === "string" && typeof error.requestId === "string";
|
|
613
|
+
return typeof error === "object" && error !== null && error.name === "ControlAPIError" && typeof error.message === "string" && typeof error.status === "number" && typeof error.code === "string" && typeof error.requestId === "string";
|
|
610
614
|
}
|
|
611
615
|
function mapControlError(error) {
|
|
612
616
|
if (!isControlAPIErrorLike(error)) return error;
|
|
613
617
|
if (error.status === 401) return authenticationErrorFor(error.code);
|
|
614
|
-
|
|
618
|
+
const status = Number.isInteger(error.status) && error.status >= 400 && error.status <= 599 ? error.status : error.code === "control_request_timeout" ? 504 : 502;
|
|
619
|
+
return new PlatformServerFetchError(error.message, "control API", status, error.code, error.requestId);
|
|
615
620
|
}
|
|
616
621
|
async function controlRequest(run) {
|
|
617
622
|
try {
|
|
@@ -652,7 +657,7 @@ function createPlatformFetch(config) {
|
|
|
652
657
|
signal: init?.signal ? AbortSignal.any([init.signal, deadline]) : deadline
|
|
653
658
|
});
|
|
654
659
|
} catch (error) {
|
|
655
|
-
if (error
|
|
660
|
+
if (isPlatformServerFetchError(error)) throw error;
|
|
656
661
|
if (isTimeout(error)) throw new PlatformServerFetchError("Platform service timed out", path, 504, "upstream_timeout");
|
|
657
662
|
throw new PlatformServerFetchError("Platform service is unavailable", path);
|
|
658
663
|
}
|
|
@@ -77,10 +77,13 @@ interface ReplaceServiceAccountGrantsInput {
|
|
|
77
77
|
expected_version: number;
|
|
78
78
|
reason: string;
|
|
79
79
|
}
|
|
80
|
+
type ServiceAccountCredentialTemplate = "read_only" | "full" | "custom";
|
|
80
81
|
interface MintServiceAccountCredentialInput {
|
|
81
82
|
name: string;
|
|
82
83
|
audience: ServiceAccountAudience;
|
|
83
|
-
|
|
84
|
+
/** Authority template; `custom` names the permissions to carry. Operations are derived server-side. */
|
|
85
|
+
template: ServiceAccountCredentialTemplate;
|
|
86
|
+
permissions?: string[];
|
|
84
87
|
dash_ids: string[];
|
|
85
88
|
expires_in_days: number;
|
|
86
89
|
expected_version: number;
|
|
@@ -143,4 +146,4 @@ declare function createServiceAccountClient(options: CreateServiceAccountClientO
|
|
|
143
146
|
};
|
|
144
147
|
type ServiceAccountClient = ReturnType<typeof createServiceAccountClient>;
|
|
145
148
|
|
|
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 };
|
|
149
|
+
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 ServiceAccountCredentialTemplate, 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 };
|
package/dist/service-accounts.js
CHANGED
|
@@ -149,7 +149,6 @@ function isServiceAccountAPIError(error) {
|
|
|
149
149
|
var basePath = "/api/v1/platform/identity/service-accounts";
|
|
150
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
151
|
var credentialIDPattern = /^[0-9a-f]{32}$/;
|
|
152
|
-
var operationIDPattern = /^[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*)+$/;
|
|
153
152
|
var timestampPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
154
153
|
var secretPattern = /^mml_sa_([0-9a-f]{32})_([0-9a-f]{64})$/;
|
|
155
154
|
var configurationError = (message) => new Error(message);
|
|
@@ -298,9 +297,12 @@ function mintInput(value) {
|
|
|
298
297
|
if (audience !== "platform" && !audience.startsWith("dash:")) {
|
|
299
298
|
throw configurationError("audience must be platform or dash:<dash_id>");
|
|
300
299
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
300
|
+
if (value.template !== "read_only" && value.template !== "full" && value.template !== "custom") {
|
|
301
|
+
throw configurationError("template must be read_only, full or custom");
|
|
302
|
+
}
|
|
303
|
+
const permissions = [...new Set((value.permissions ?? []).map((item) => requiredString(item, "permission", configurationError)))];
|
|
304
|
+
if (value.template === "custom" && (permissions.length < 1 || permissions.length > 64)) {
|
|
305
|
+
throw configurationError("a custom template must name 1 to 64 permissions");
|
|
304
306
|
}
|
|
305
307
|
const dashIDs = [...new Set(value.dash_ids.map((item) => requiredString(item, "dash_id", configurationError)))];
|
|
306
308
|
if (dashIDs.length > 32 || dashIDs.includes("*")) throw configurationError("dash_ids must name at most 32 explicit Dashes");
|
|
@@ -316,7 +318,8 @@ function mintInput(value) {
|
|
|
316
318
|
return {
|
|
317
319
|
name,
|
|
318
320
|
audience,
|
|
319
|
-
|
|
321
|
+
template: value.template,
|
|
322
|
+
...value.template === "custom" ? { permissions } : {},
|
|
320
323
|
dash_ids: dashIDs,
|
|
321
324
|
expires_in_days: value.expires_in_days,
|
|
322
325
|
expected_version: version(value.expected_version),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c1m-public/platform-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
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",
|
|
@@ -20,10 +20,6 @@
|
|
|
20
20
|
"types": "./dist/next.d.ts",
|
|
21
21
|
"default": "./dist/next.js"
|
|
22
22
|
},
|
|
23
|
-
"./mcp-tokens": {
|
|
24
|
-
"types": "./dist/mcp-tokens.d.ts",
|
|
25
|
-
"default": "./dist/mcp-tokens.js"
|
|
26
|
-
},
|
|
27
23
|
"./service-accounts": {
|
|
28
24
|
"types": "./dist/service-accounts.d.ts",
|
|
29
25
|
"default": "./dist/service-accounts.js"
|
package/dist/mcp-tokens.d.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
declare const MCP_TOOL_PERMISSIONS: readonly ["dash.domains.view", "dash.workspaces.view", "platform.audit.view", "platform.ops.manage", "platform.ops.view", "platform.tenants.view"];
|
|
2
|
-
declare const MCP_WILDCARD_TOOL_PERMISSIONS: readonly ["dash.domains.view", "dash.workspaces.view", "platform.audit.view", "platform.ops.view", "platform.tenants.view"];
|
|
3
|
-
type MCPToolPermission = (typeof MCP_TOOL_PERMISSIONS)[number];
|
|
4
|
-
|
|
5
|
-
declare const MCP_ACCESS_PERMISSION: "platform.mcp.access";
|
|
6
|
-
declare const MCP_TOKENS_MANAGE_PERMISSION: "platform.mcp.tokens.manage";
|
|
7
|
-
declare const MCP_PERMISSION_WILDCARD: "*";
|
|
8
|
-
type MCPTokenPermission = typeof MCP_ACCESS_PERMISSION | typeof MCP_PERMISSION_WILDCARD | MCPToolPermission;
|
|
9
|
-
type MCPTokenOperation = "platform_user.auth.mcp_tokens.list" | "platform_user.auth.mcp_tokens.mint" | "platform_user.auth.mcp_tokens.revoke";
|
|
10
|
-
type MCPTokenRequestOutcome = "success" | "api_error" | "network_error" | "timeout" | "invalid_response" | "cancelled";
|
|
11
|
-
interface MCPTokenView {
|
|
12
|
-
token_id: string;
|
|
13
|
-
name: string;
|
|
14
|
-
/** Recorded ceilings may include permissions added by a newer server. */
|
|
15
|
-
permissions: string[];
|
|
16
|
-
app_allowlist: string[];
|
|
17
|
-
created_at: string;
|
|
18
|
-
expires_at: string;
|
|
19
|
-
last_used_at?: string;
|
|
20
|
-
revoked_at?: string;
|
|
21
|
-
}
|
|
22
|
-
interface MCPTokenPage {
|
|
23
|
-
items: MCPTokenView[];
|
|
24
|
-
next_cursor?: string;
|
|
25
|
-
has_more: boolean;
|
|
26
|
-
mcp_enabled: boolean;
|
|
27
|
-
}
|
|
28
|
-
interface MCPTokenListParams {
|
|
29
|
-
cursor?: string;
|
|
30
|
-
limit?: number;
|
|
31
|
-
}
|
|
32
|
-
interface MintMCPTokenInput {
|
|
33
|
-
name: string;
|
|
34
|
-
expires_in_days: number;
|
|
35
|
-
permissions: MCPTokenPermission[];
|
|
36
|
-
app_allowlist: string[];
|
|
37
|
-
}
|
|
38
|
-
interface MintMCPTokenResponse {
|
|
39
|
-
token: MCPTokenView;
|
|
40
|
-
raw_token: string;
|
|
41
|
-
}
|
|
42
|
-
interface MCPTokenRequestObservation {
|
|
43
|
-
operation: MCPTokenOperation;
|
|
44
|
-
method: string;
|
|
45
|
-
outcome: MCPTokenRequestOutcome;
|
|
46
|
-
status: number;
|
|
47
|
-
requestId: string;
|
|
48
|
-
durationMs: number;
|
|
49
|
-
attempts: number;
|
|
50
|
-
}
|
|
51
|
-
interface CreateMCPTokenClientOptions {
|
|
52
|
-
baseUrl: string;
|
|
53
|
-
getAccessToken: () => Promise<string | null | undefined> | string | null | undefined;
|
|
54
|
-
getHeaders?: () => Promise<HeadersInit | undefined> | HeadersInit | undefined;
|
|
55
|
-
fetch?: typeof globalThis.fetch;
|
|
56
|
-
/** Per attempt (default 10000). */
|
|
57
|
-
timeoutMs?: number;
|
|
58
|
-
/** Additional attempts for GET only (default 1). Mint and revoke are never retried. */
|
|
59
|
-
retryAttempts?: number;
|
|
60
|
-
/** Cap on one retry delay (default 2000). */
|
|
61
|
-
maxRetryDelayMs?: number;
|
|
62
|
-
observe?: (event: MCPTokenRequestObservation) => void;
|
|
63
|
-
signal?: AbortSignal;
|
|
64
|
-
}
|
|
65
|
-
declare class MCPTokenAPIError extends Error {
|
|
66
|
-
readonly status: number;
|
|
67
|
-
readonly code: string;
|
|
68
|
-
readonly requestId: string;
|
|
69
|
-
constructor(message: string, status: number, code: string, requestId: string);
|
|
70
|
-
}
|
|
71
|
-
declare function isMCPTokenAPIError(error: unknown): error is MCPTokenAPIError;
|
|
72
|
-
declare function createMCPTokenClient(options: CreateMCPTokenClientOptions): {
|
|
73
|
-
readonly list: (params?: MCPTokenListParams) => Promise<MCPTokenPage>;
|
|
74
|
-
readonly mint: (input: MintMCPTokenInput) => Promise<MintMCPTokenResponse>;
|
|
75
|
-
readonly revoke: (tokenId: string) => Promise<void>;
|
|
76
|
-
};
|
|
77
|
-
type MCPTokenClient = ReturnType<typeof createMCPTokenClient>;
|
|
78
|
-
|
|
79
|
-
export { type CreateMCPTokenClientOptions, MCPTokenAPIError, type MCPTokenClient, type MCPTokenListParams, type MCPTokenOperation, type MCPTokenPage, type MCPTokenPermission, type MCPTokenRequestObservation, type MCPTokenRequestOutcome, type MCPTokenView, type MCPToolPermission, MCP_ACCESS_PERMISSION, MCP_PERMISSION_WILDCARD, MCP_TOKENS_MANAGE_PERMISSION, MCP_TOOL_PERMISSIONS, MCP_WILDCARD_TOOL_PERMISSIONS, type MintMCPTokenInput, type MintMCPTokenResponse, createMCPTokenClient, isMCPTokenAPIError };
|
package/dist/mcp-tokens.js
DELETED
|
@@ -1,357 +0,0 @@
|
|
|
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/access/generated/mcp-tools.ts
|
|
132
|
-
var MCP_TOOL_PERMISSIONS = [
|
|
133
|
-
"dash.domains.view",
|
|
134
|
-
"dash.workspaces.view",
|
|
135
|
-
"platform.audit.view",
|
|
136
|
-
"platform.ops.manage",
|
|
137
|
-
"platform.ops.view",
|
|
138
|
-
"platform.tenants.view"
|
|
139
|
-
];
|
|
140
|
-
var MCP_WILDCARD_TOOL_PERMISSIONS = [
|
|
141
|
-
"dash.domains.view",
|
|
142
|
-
"dash.workspaces.view",
|
|
143
|
-
"platform.audit.view",
|
|
144
|
-
"platform.ops.view",
|
|
145
|
-
"platform.tenants.view"
|
|
146
|
-
];
|
|
147
|
-
|
|
148
|
-
// src/mcp-tokens.ts
|
|
149
|
-
var MCP_ACCESS_PERMISSION = "platform.mcp.access";
|
|
150
|
-
var MCP_TOKENS_MANAGE_PERMISSION = "platform.mcp.tokens.manage";
|
|
151
|
-
var MCP_PERMISSION_WILDCARD = "*";
|
|
152
|
-
var MCPTokenAPIError = class extends Error {
|
|
153
|
-
constructor(message, status, code, requestId) {
|
|
154
|
-
super(message);
|
|
155
|
-
this.status = status;
|
|
156
|
-
this.code = code;
|
|
157
|
-
this.requestId = requestId;
|
|
158
|
-
this.name = "MCPTokenAPIError";
|
|
159
|
-
}
|
|
160
|
-
status;
|
|
161
|
-
code;
|
|
162
|
-
requestId;
|
|
163
|
-
};
|
|
164
|
-
function isMCPTokenAPIError(error) {
|
|
165
|
-
return error instanceof MCPTokenAPIError || typeof error === "object" && error !== null && error.name === "MCPTokenAPIError" && typeof error.status === "number" && typeof error.code === "string" && typeof error.requestId === "string";
|
|
166
|
-
}
|
|
167
|
-
var configurationError = (message) => new Error(message);
|
|
168
|
-
var basePath = "/api/v1/platform/auth/me/mcp-tokens";
|
|
169
|
-
var tokenIDPattern = /^[0-9a-f]{32}$/;
|
|
170
|
-
var rawTokenPattern = /^sd_pat_([0-9a-f]{32})_[A-Za-z0-9_-]{43}$/;
|
|
171
|
-
var timestampPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
172
|
-
function invalidResponse() {
|
|
173
|
-
throw new Error("invalid MCP token response");
|
|
174
|
-
}
|
|
175
|
-
function objectValue(value) {
|
|
176
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) invalidResponse();
|
|
177
|
-
return value;
|
|
178
|
-
}
|
|
179
|
-
function responseString(value, options = {}) {
|
|
180
|
-
if (value === void 0 && options.optional) return void 0;
|
|
181
|
-
if (typeof value !== "string" || value.length === 0) invalidResponse();
|
|
182
|
-
if (options.tokenID && !tokenIDPattern.test(value)) invalidResponse();
|
|
183
|
-
if (options.timestamp && (!timestampPattern.test(value) || !Number.isFinite(Date.parse(value)))) invalidResponse();
|
|
184
|
-
return value;
|
|
185
|
-
}
|
|
186
|
-
function responseStrings(value) {
|
|
187
|
-
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.length === 0)) invalidResponse();
|
|
188
|
-
return [...value];
|
|
189
|
-
}
|
|
190
|
-
function parseMCPTokenView(value) {
|
|
191
|
-
const item = objectValue(value);
|
|
192
|
-
return {
|
|
193
|
-
token_id: responseString(item.token_id, { tokenID: true }),
|
|
194
|
-
name: responseString(item.name),
|
|
195
|
-
permissions: responseStrings(item.permissions),
|
|
196
|
-
app_allowlist: responseStrings(item.app_allowlist),
|
|
197
|
-
created_at: responseString(item.created_at, { timestamp: true }),
|
|
198
|
-
expires_at: responseString(item.expires_at, { timestamp: true }),
|
|
199
|
-
last_used_at: responseString(item.last_used_at, { optional: true, timestamp: true }),
|
|
200
|
-
revoked_at: responseString(item.revoked_at, { optional: true, timestamp: true })
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
function parseMCPTokenPage(value) {
|
|
204
|
-
const page = objectValue(value);
|
|
205
|
-
if (!Array.isArray(page.items) || typeof page.has_more !== "boolean" || typeof page.mcp_enabled !== "boolean") invalidResponse();
|
|
206
|
-
const nextCursor = responseString(page.next_cursor, { optional: true });
|
|
207
|
-
if (Boolean(nextCursor) !== page.has_more) invalidResponse();
|
|
208
|
-
return {
|
|
209
|
-
items: page.items.map(parseMCPTokenView),
|
|
210
|
-
next_cursor: nextCursor,
|
|
211
|
-
has_more: page.has_more,
|
|
212
|
-
mcp_enabled: page.mcp_enabled
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
function parseMintMCPTokenResponse(value) {
|
|
216
|
-
const response = objectValue(value);
|
|
217
|
-
const token = parseMCPTokenView(response.token);
|
|
218
|
-
const rawToken = responseString(response.raw_token);
|
|
219
|
-
const match = rawToken.match(rawTokenPattern);
|
|
220
|
-
if (!match || match[1] !== token.token_id) invalidResponse();
|
|
221
|
-
return { token, raw_token: rawToken };
|
|
222
|
-
}
|
|
223
|
-
function normalizedStrings(values, label, max) {
|
|
224
|
-
if (!Array.isArray(values)) throw configurationError(`${label} must be an array`);
|
|
225
|
-
const normalized = [...new Set(values.map((value) => requiredString(value, label, configurationError)))];
|
|
226
|
-
if (normalized.length > max) throw configurationError(`${label} must contain at most ${max} entries`);
|
|
227
|
-
return normalized;
|
|
228
|
-
}
|
|
229
|
-
function mintInput(value) {
|
|
230
|
-
const name = requiredString(value.name, "name", configurationError);
|
|
231
|
-
if (name.length > 64) throw configurationError("name must be 64 characters or fewer");
|
|
232
|
-
if (!Number.isSafeInteger(value.expires_in_days) || value.expires_in_days < 1 || value.expires_in_days > 90) {
|
|
233
|
-
throw configurationError("expires_in_days must be an integer from 1 to 90");
|
|
234
|
-
}
|
|
235
|
-
const permissions = normalizedStrings(value.permissions, "permissions", 32);
|
|
236
|
-
if (permissions.includes(MCP_PERMISSION_WILDCARD)) {
|
|
237
|
-
if (permissions.length !== 1) {
|
|
238
|
-
throw configurationError('permissions "*" must be the only entry');
|
|
239
|
-
}
|
|
240
|
-
} else {
|
|
241
|
-
if (!permissions.includes(MCP_ACCESS_PERMISSION)) throw configurationError(`permissions must include ${MCP_ACCESS_PERMISSION}`);
|
|
242
|
-
if (permissions.length < 2) throw configurationError("permissions must include at least one MCP tool permission");
|
|
243
|
-
const allowedPermissions = /* @__PURE__ */ new Set([MCP_ACCESS_PERMISSION, ...MCP_TOOL_PERMISSIONS]);
|
|
244
|
-
if (permissions.some((permission) => !allowedPermissions.has(permission))) {
|
|
245
|
-
throw configurationError("permissions contains a key that is not available to MCP tools");
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
const appAllowlist = normalizedStrings(value.app_allowlist, "app_allowlist", 32);
|
|
249
|
-
if (appAllowlist.includes("*") && appAllowlist.length !== 1) {
|
|
250
|
-
throw configurationError('app_allowlist "*" must be the only entry');
|
|
251
|
-
}
|
|
252
|
-
return {
|
|
253
|
-
name,
|
|
254
|
-
expires_in_days: value.expires_in_days,
|
|
255
|
-
permissions,
|
|
256
|
-
app_allowlist: appAllowlist
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
function tokenIDInput(value) {
|
|
260
|
-
const tokenID = requiredString(value, "tokenId", configurationError);
|
|
261
|
-
if (!tokenIDPattern.test(tokenID)) throw configurationError("tokenId must be a 32-character hexadecimal MCP token id");
|
|
262
|
-
return tokenID;
|
|
263
|
-
}
|
|
264
|
-
function createMCPTokenClient(options) {
|
|
265
|
-
const baseUrl = requiredString(options.baseUrl, "baseUrl", configurationError).replace(/\/+$/, "");
|
|
266
|
-
const transport = {
|
|
267
|
-
fetch: (input, init) => (options.fetch ?? globalThis.fetch)(input, init),
|
|
268
|
-
timeoutMs: positiveNumber(options.timeoutMs ?? 1e4, "timeoutMs", configurationError),
|
|
269
|
-
retry: {
|
|
270
|
-
attempts: nonNegativeInteger(options.retryAttempts ?? 1, "retryAttempts", configurationError),
|
|
271
|
-
maxDelayMs: positiveNumber(options.maxRetryDelayMs ?? 2e3, "maxRetryDelayMs", configurationError),
|
|
272
|
-
idempotentWrites: false
|
|
273
|
-
},
|
|
274
|
-
signal: options.signal
|
|
275
|
-
};
|
|
276
|
-
async function request(operation, path, decode, init = {}) {
|
|
277
|
-
const method = (init.method ?? "GET").toUpperCase();
|
|
278
|
-
const report = options.observe && ((event) => {
|
|
279
|
-
options.observe?.({ operation, ...event });
|
|
280
|
-
});
|
|
281
|
-
let headers;
|
|
282
|
-
try {
|
|
283
|
-
const accessToken = requiredString(await options.getAccessToken() ?? "", "access token", configurationError);
|
|
284
|
-
headers = new Headers(await options.getHeaders?.());
|
|
285
|
-
new Headers(init.headers).forEach((value, key) => headers.set(key, value));
|
|
286
|
-
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
287
|
-
headers.set("Accept", "application/json");
|
|
288
|
-
if (init.body !== void 0) headers.set("Content-Type", "application/json");
|
|
289
|
-
} catch (error) {
|
|
290
|
-
try {
|
|
291
|
-
report?.({ method, outcome: "network_error", status: 0, requestId: "", durationMs: 0, attempts: 0 });
|
|
292
|
-
} catch {
|
|
293
|
-
}
|
|
294
|
-
throw error;
|
|
295
|
-
}
|
|
296
|
-
try {
|
|
297
|
-
const result = await performRequest(
|
|
298
|
-
transport,
|
|
299
|
-
`${baseUrl}${path}`,
|
|
300
|
-
{ ...init, method, headers, cache: "no-store" },
|
|
301
|
-
report,
|
|
302
|
-
decode
|
|
303
|
-
);
|
|
304
|
-
return result.payload;
|
|
305
|
-
} catch (error) {
|
|
306
|
-
if (!isTransportError(error)) throw error;
|
|
307
|
-
const { outcome, status, code, message, requestId } = error.failure;
|
|
308
|
-
switch (outcome) {
|
|
309
|
-
case "timeout":
|
|
310
|
-
throw new MCPTokenAPIError("MCP token request timed out", 0, "mcp_token_request_timeout", "");
|
|
311
|
-
case "cancelled":
|
|
312
|
-
throw new MCPTokenAPIError("MCP token request was cancelled", 0, "mcp_token_request_cancelled", "");
|
|
313
|
-
case "network_error":
|
|
314
|
-
throw new MCPTokenAPIError("MCP token service is unavailable", 0, "mcp_token_service_unavailable", "");
|
|
315
|
-
case "invalid_response":
|
|
316
|
-
throw new MCPTokenAPIError("MCP token service returned an invalid response", status, "mcp_token_invalid_response", requestId);
|
|
317
|
-
case "api_error":
|
|
318
|
-
throw new MCPTokenAPIError(message || code || "MCP token request failed", status, code, requestId);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
return {
|
|
323
|
-
list(params = {}) {
|
|
324
|
-
const query = new URLSearchParams();
|
|
325
|
-
const cursor = params.cursor?.trim();
|
|
326
|
-
if (cursor) query.set("cursor", cursor);
|
|
327
|
-
if (params.limit !== void 0) {
|
|
328
|
-
const limit = positiveNumber(params.limit, "limit", configurationError);
|
|
329
|
-
if (!Number.isSafeInteger(limit) || limit > 100) throw configurationError("limit must be an integer from 1 to 100");
|
|
330
|
-
query.set("limit", String(limit));
|
|
331
|
-
}
|
|
332
|
-
return request(
|
|
333
|
-
"platform_user.auth.mcp_tokens.list",
|
|
334
|
-
`${basePath}${query.size ? `?${query}` : ""}`,
|
|
335
|
-
parseMCPTokenPage
|
|
336
|
-
);
|
|
337
|
-
},
|
|
338
|
-
mint(input) {
|
|
339
|
-
return request("platform_user.auth.mcp_tokens.mint", basePath, parseMintMCPTokenResponse, {
|
|
340
|
-
method: "POST",
|
|
341
|
-
body: JSON.stringify(mintInput(input))
|
|
342
|
-
});
|
|
343
|
-
},
|
|
344
|
-
revoke(tokenId) {
|
|
345
|
-
return request(
|
|
346
|
-
"platform_user.auth.mcp_tokens.revoke",
|
|
347
|
-
`${basePath}/${tokenIDInput(tokenId)}`,
|
|
348
|
-
(payload) => {
|
|
349
|
-
if (payload !== void 0) invalidResponse();
|
|
350
|
-
},
|
|
351
|
-
{ method: "DELETE" }
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
export { MCPTokenAPIError, MCP_ACCESS_PERMISSION, MCP_PERMISSION_WILDCARD, MCP_TOKENS_MANAGE_PERMISSION, MCP_TOOL_PERMISSIONS, MCP_WILDCARD_TOOL_PERMISSIONS, createMCPTokenClient, isMCPTokenAPIError };
|