@softure-ai/mcp-access 0.1.5 → 0.1.7
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/CHANGELOG.md +23 -0
- package/README.md +110 -21
- package/adoption/move-app-tables.sql +100 -0
- package/dist/contract.d.ts +13 -1
- package/dist/contract.d.ts.map +1 -1
- package/dist/index.d.ts +51 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -5
- package/dist/index.js.map +1 -1
- package/dist/messages/en.d.ts +28 -0
- package/dist/messages/en.d.ts.map +1 -1
- package/dist/messages/en.js +28 -0
- package/dist/messages/en.js.map +1 -1
- package/dist/messages/index.d.ts +56 -0
- package/dist/messages/index.d.ts.map +1 -1
- package/dist/messages/pl.d.ts.map +1 -1
- package/dist/messages/pl.js +28 -0
- package/dist/messages/pl.js.map +1 -1
- package/dist/next/actions.d.ts +3 -1
- package/dist/next/actions.d.ts.map +1 -1
- package/dist/next/actions.js +19 -0
- package/dist/next/actions.js.map +1 -1
- package/dist/next/context.js +2 -2
- package/dist/next/context.js.map +1 -1
- package/dist/next/index.d.ts +3 -1
- package/dist/next/index.d.ts.map +1 -1
- package/dist/next/index.js +4 -2
- package/dist/next/index.js.map +1 -1
- package/dist/next/oauth-page.d.ts +7 -0
- package/dist/next/oauth-page.d.ts.map +1 -0
- package/dist/next/oauth-page.js +35 -0
- package/dist/next/oauth-page.js.map +1 -0
- package/dist/next/oauth-routes.d.ts +16 -0
- package/dist/next/oauth-routes.d.ts.map +1 -0
- package/dist/next/oauth-routes.js +50 -0
- package/dist/next/oauth-routes.js.map +1 -0
- package/dist/next/pages.d.ts.map +1 -1
- package/dist/next/pages.js +13 -2
- package/dist/next/pages.js.map +1 -1
- package/dist/options.d.ts +16 -0
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +28 -1
- package/dist/options.js.map +1 -1
- package/dist/schema.d.ts +486 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +45 -3
- package/dist/schema.js.map +1 -1
- package/dist/server/endpoint.d.ts +2 -4
- package/dist/server/endpoint.d.ts.map +1 -1
- package/dist/server/endpoint.js +10 -7
- package/dist/server/endpoint.js.map +1 -1
- package/dist/server/health.d.ts.map +1 -1
- package/dist/server/health.js +5 -3
- package/dist/server/health.js.map +1 -1
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +4 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/oauth-http.d.ts +113 -0
- package/dist/server/oauth-http.d.ts.map +1 -0
- package/dist/server/oauth-http.js +374 -0
- package/dist/server/oauth-http.js.map +1 -0
- package/dist/server/oauth-validation.d.ts +28 -0
- package/dist/server/oauth-validation.d.ts.map +1 -0
- package/dist/server/oauth-validation.js +88 -0
- package/dist/server/oauth-validation.js.map +1 -0
- package/dist/server/oauth.d.ts +106 -0
- package/dist/server/oauth.d.ts.map +1 -0
- package/dist/server/oauth.js +235 -0
- package/dist/server/oauth.js.map +1 -0
- package/dist/server/options.d.ts +8 -0
- package/dist/server/options.d.ts.map +1 -1
- package/dist/server/options.js +8 -1
- package/dist/server/options.js.map +1 -1
- package/dist/server/pkce.d.ts +7 -0
- package/dist/server/pkce.d.ts.map +1 -0
- package/dist/server/pkce.js +24 -0
- package/dist/server/pkce.js.map +1 -0
- package/dist/server/privacy.d.ts +7 -0
- package/dist/server/privacy.d.ts.map +1 -1
- package/dist/server/privacy.js +19 -9
- package/dist/server/privacy.js.map +1 -1
- package/dist/server/scopes.d.ts +5 -0
- package/dist/server/scopes.d.ts.map +1 -0
- package/dist/server/scopes.js +6 -0
- package/dist/server/scopes.js.map +1 -0
- package/dist/server/tokens.d.ts +18 -6
- package/dist/server/tokens.d.ts.map +1 -1
- package/dist/server/tokens.js +60 -25
- package/dist/server/tokens.js.map +1 -1
- package/dist/ui/consent-form.d.ts +31 -0
- package/dist/ui/consent-form.d.ts.map +1 -0
- package/dist/ui/consent-form.js +24 -0
- package/dist/ui/consent-form.js.map +1 -0
- package/dist/ui/index.d.ts +2 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/token-manager.d.ts +16 -2
- package/dist/ui/token-manager.d.ts.map +1 -1
- package/dist/ui/token-manager.js +6 -2
- package/dist/ui/token-manager.js.map +1 -1
- package/migrations/0002_create_oauth_grants.sql +68 -0
- package/module.json +18 -4
- package/package.json +7 -2
- package/src/contract.ts +16 -1
- package/src/index.ts +28 -4
- package/src/messages/en.ts +28 -0
- package/src/messages/pl.ts +28 -0
- package/src/next/actions.ts +19 -1
- package/src/next/context.ts +2 -2
- package/src/next/index.ts +11 -2
- package/src/next/oauth-page.tsx +67 -0
- package/src/next/oauth-routes.ts +65 -0
- package/src/next/pages.tsx +16 -3
- package/src/options.ts +32 -1
- package/src/schema.ts +51 -3
- package/src/server/endpoint.ts +10 -7
- package/src/server/health.ts +5 -3
- package/src/server/index.ts +50 -0
- package/src/server/oauth-http.ts +431 -0
- package/src/server/oauth-validation.ts +106 -0
- package/src/server/oauth.ts +315 -0
- package/src/server/options.ts +16 -1
- package/src/server/pkce.ts +27 -0
- package/src/server/privacy.ts +26 -9
- package/src/server/scopes.ts +6 -0
- package/src/server/tokens.ts +62 -25
- package/src/ui/consent-form.tsx +103 -0
- package/src/ui/index.ts +3 -0
- package/src/ui/token-manager.tsx +79 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"privacy.js","sourceRoot":"","sources":["../../src/server/privacy.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,
|
|
1
|
+
{"version":3,"file":"privacy.js","sourceRoot":"","sources":["../../src/server/privacy.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,mGAAmG;AACnG,mGAAmG;AACnG,OAAO,EAAE,EAAE,EAAwD,MAAM,kBAAkB,CAAC;AAE5F,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAoBhG,MAAM,IAAI,GAAG,iEAAiE,CAAC;AAE/E,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,OAAsB,EAAE,MAAc;IAClF,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC,CAAC;IAC3E,2FAA2F;IAC3F,MAAM,EAAE,GAAG,OAAO,CAAC,EAAe,CAAC;IACnC,MAAM,MAAM,GAAG,MAAM,EAAE;SACpB,MAAM,CAAC;QACN,IAAI,EAAE,YAAY,CAAC,IAAI;QACvB,QAAQ,EAAE,YAAY,CAAC,QAAQ;QAC/B,SAAS,EAAE,YAAY,CAAC,SAAS;QACjC,SAAS,EAAE,YAAY,CAAC,SAAS;QACjC,UAAU,EAAE,YAAY,CAAC,UAAU;KACpC,CAAC;SACD,IAAI,CAAC,YAAY,CAAC;SAClB,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;SACzE,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC;IACxC,MAAM,aAAa,GAAG,MAAM,EAAE;SAC3B,MAAM,CAAC,EAAE,UAAU,EAAE,YAAY,CAAC,UAAU,EAAE,QAAQ,EAAE,WAAW,CAAC,QAAQ,EAAE,SAAS,EAAE,WAAW,CAAC,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,UAAU,EAAE,CAAC;SACrJ,IAAI,CAAC,WAAW,CAAC;SACjB,SAAS,CAAC,YAAY,EAAE,EAAE,CAAC,WAAW,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC;SAClE,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;SACrC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC;IACvC,OAAO,EAAE,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,OAAsB,EAAE,MAAc;IAClF,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,EAAE,CAAC;IACpC,MAAM,EAAE,GAAG,OAAO,CAAC,EAAe,CAAC;IACnC,MAAM,EAAE,CAAC,MAAM,CAAC,uBAAuB,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3F,2FAA2F;IAC3F,MAAM,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnE,MAAM,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACrE,OAAO,EAAE,EAAE,CAAC;AACd,CAAC;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAuB;IAC7D,cAAc,EAAE,uBAAuB;IACvC,cAAc,EAAE,uBAAuB;CACxC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scopes.d.ts","sourceRoot":"","sources":["../../src/server/scopes.ts"],"names":[],"mappings":"AAEA,+BAA+B;AAC/B,eAAO,MAAM,cAAc,aAAa,CAAC;AACzC,0EAA0E;AAC1E,eAAO,MAAM,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// The two scopes of the MCP endpoint, shared by the Bearer check and the OAuth server.
|
|
2
|
+
/** Every valid token reads. */
|
|
3
|
+
export const MCP_READ_SCOPE = "mcp:read";
|
|
4
|
+
/** A token that may write: issued for writes, and the app allows them. */
|
|
5
|
+
export const MCP_WRITE_SCOPE = "mcp:write";
|
|
6
|
+
//# sourceMappingURL=scopes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../src/server/scopes.ts"],"names":[],"mappings":"AAAA,uFAAuF;AAEvF,+BAA+B;AAC/B,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;AACzC,0EAA0E;AAC1E,MAAM,CAAC,MAAM,eAAe,GAAG,WAAW,CAAC"}
|
package/dist/server/tokens.d.ts
CHANGED
|
@@ -5,8 +5,13 @@ export type McpAccessContext = ModuleContext<Queryable>;
|
|
|
5
5
|
/** Marks a token as this module's, for people and for secret scanners. */
|
|
6
6
|
export declare const ACCESS_TOKEN_PREFIX = "sftmcp_";
|
|
7
7
|
export declare function hashAccessToken(token: string): string;
|
|
8
|
-
/** Whether a presented value
|
|
8
|
+
/** Whether a presented value has the module's token shape. */
|
|
9
9
|
export declare function isAccessTokenShape(token: string): boolean;
|
|
10
|
+
/** A new secret with the given prefix and its hash; the plaintext is returned once, never stored. */
|
|
11
|
+
export declare function createSecret(prefix: string): {
|
|
12
|
+
readonly secret: string;
|
|
13
|
+
readonly secretHash: string;
|
|
14
|
+
};
|
|
10
15
|
export interface IssueAccessTokenInput {
|
|
11
16
|
/** The auth user id of the owner, from the session. */
|
|
12
17
|
readonly userId: string;
|
|
@@ -30,7 +35,10 @@ export type IssueAccessTokenResult = Ok<IssuedAccessToken> | Err<"mcp-access.nam
|
|
|
30
35
|
* Database failures propagate.
|
|
31
36
|
*/
|
|
32
37
|
export declare function issueAccessToken(ctx: McpAccessContext, input: IssueAccessTokenInput): Promise<IssueAccessTokenResult>;
|
|
33
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* The owner's hand-issued tokens, newest first, expired ones included (the list marks them).
|
|
40
|
+
* OAuth access tokens are left out: the page shows their grants instead.
|
|
41
|
+
*/
|
|
34
42
|
export declare function listAccessTokens(ctx: McpAccessContext, userId: string): Promise<AccessTokenView[]>;
|
|
35
43
|
export interface RevokeAccessTokenInput {
|
|
36
44
|
/** The owner, from the session. */
|
|
@@ -39,8 +47,9 @@ export interface RevokeAccessTokenInput {
|
|
|
39
47
|
readonly tokenId: string;
|
|
40
48
|
}
|
|
41
49
|
/**
|
|
42
|
-
* Deletes a token at once; the next request with it gets 401. The owner is part of the
|
|
43
|
-
* so another account's id matches no row instead of relying on a comparison here.
|
|
50
|
+
* Deletes a hand-issued token at once; the next request with it gets 401. The owner is part of the
|
|
51
|
+
* `WHERE`, so another account's id matches no row instead of relying on a comparison here. An
|
|
52
|
+
* OAuth access token is revoked with its grant (`revokeOAuthGrant`).
|
|
44
53
|
*/
|
|
45
54
|
export declare function revokeAccessToken(ctx: McpAccessContext, input: RevokeAccessTokenInput): Promise<Ok<undefined> | Err<"mcp-access.token_not_found">>;
|
|
46
55
|
/** The identity behind a valid token. */
|
|
@@ -50,6 +59,8 @@ export interface VerifiedAccessToken {
|
|
|
50
59
|
/** Effective write access: the token was issued for writes and the app allows them now. */
|
|
51
60
|
readonly canWrite: boolean;
|
|
52
61
|
readonly expiresAt: Date;
|
|
62
|
+
/** The OAuth grant the token was issued from; null for a hand-issued token. */
|
|
63
|
+
readonly grantId: string | null;
|
|
53
64
|
}
|
|
54
65
|
/**
|
|
55
66
|
* The identity behind a token, or null for anything unknown, revoked or expired: the caller cannot
|
|
@@ -59,8 +70,9 @@ export interface VerifiedAccessToken {
|
|
|
59
70
|
*/
|
|
60
71
|
export declare function verifyAccessToken(ctx: McpAccessContext, token: string): Promise<VerifiedAccessToken | null>;
|
|
61
72
|
/**
|
|
62
|
-
* Deletes every expired token, of every account
|
|
63
|
-
* may call this from a scheduled job so abandoned accounts do not
|
|
73
|
+
* Deletes every expired token, of every account, hand-issued and OAuth alike. Issuing already
|
|
74
|
+
* removes the issuer's own; an app may call this from a scheduled job so abandoned accounts do not
|
|
75
|
+
* keep dead rows (with `pruneOAuthRecords` for codes, grants and clients).
|
|
64
76
|
*/
|
|
65
77
|
export declare function pruneAccessTokens(ctx: McpAccessContext): Promise<number>;
|
|
66
78
|
//# sourceMappingURL=tokens.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/server/tokens.ts"],"names":[],"mappings":"AAIA,OAAO,EAA0B,KAAK,GAAG,EAAE,KAAK,aAAa,EAAE,KAAK,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACjG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAKtD,MAAM,MAAM,gBAAgB,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;AAExD,0EAA0E;AAC1E,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAU7C,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/server/tokens.ts"],"names":[],"mappings":"AAIA,OAAO,EAA0B,KAAK,GAAG,EAAE,KAAK,aAAa,EAAE,KAAK,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACjG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAKtD,MAAM,MAAM,gBAAgB,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;AAExD,0EAA0E;AAC1E,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAU7C,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzD;AAYD,qGAAqG;AACrG,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAGrG;AAED,MAAM,WAAW,qBAAqB;IACpC,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;CAC1B;AAED,MAAM,MAAM,sBAAsB,GAC9B,EAAE,CAAC,iBAAiB,CAAC,GACrB,GAAG,CAAC,0BAA0B,GAAG,0BAA0B,GAAG,gCAAgC,CAAC,CAAC;AAEpG;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,gBAAgB,EAAE,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CA4B3H;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAaxG;AAED,MAAM,WAAW,sBAAsB;IACrC,mCAAmC;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,gBAAgB,EACrB,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,EAAE,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,4BAA4B,CAAC,CAAC,CAO5D;AAED,yCAAyC;AACzC,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,2FAA2F;IAC3F,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAwBjH;AAsBD;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAM9E"}
|
package/dist/server/tokens.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
// Access tokens (
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Access tokens (an adopting app's token code, generalised). A token is `sftmcp_` and 32 random
|
|
2
|
+
// bytes; the database holds only its sha256, so a leaked row cannot be replayed. The plaintext
|
|
3
|
+
// leaves this file once, in the result of `issueAccessToken` (or of an OAuth exchange, `oauth.ts`).
|
|
4
4
|
import { createHash, randomBytes } from "node:crypto";
|
|
5
5
|
import { err, errorLogLabel, ok } from "@softure-ai/core";
|
|
6
6
|
import { and, count, desc, eq, gt, isNull, lt, lte, or, sql } from "drizzle-orm";
|
|
7
|
-
import { MAX_TOKEN_NAME_LENGTH } from "../options.js";
|
|
8
|
-
import { accessTokens } from "../schema.js";
|
|
7
|
+
import { MAX_PRESENTED_TOKEN_LENGTH, MAX_TOKEN_NAME_LENGTH } from "../options.js";
|
|
8
|
+
import { accessTokens, oauthGrants } from "../schema.js";
|
|
9
9
|
import { getMcpAccessOptions } from "./options.js";
|
|
10
10
|
/** Marks a token as this module's, for people and for secret scanners. */
|
|
11
11
|
export const ACCESS_TOKEN_PREFIX = "sftmcp_";
|
|
@@ -19,13 +19,24 @@ const LAST_USED_RESOLUTION_MS = 60_000;
|
|
|
19
19
|
export function hashAccessToken(token) {
|
|
20
20
|
return createHash("sha256").update(token, "utf8").digest("hex");
|
|
21
21
|
}
|
|
22
|
-
/** Whether a presented value
|
|
22
|
+
/** Whether a presented value has the module's token shape. */
|
|
23
23
|
export function isAccessTokenShape(token) {
|
|
24
24
|
return TOKEN_SHAPE.test(token);
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Whether a presented value is looked up at all: the module's shape, or the app's
|
|
28
|
+
* `legacyTokenPattern` for tokens issued before the module. The length cap runs first, so no
|
|
29
|
+
* pattern ever sees a huge header.
|
|
30
|
+
*/
|
|
31
|
+
function isAcceptedTokenShape(options, token) {
|
|
32
|
+
if (token.length > MAX_PRESENTED_TOKEN_LENGTH)
|
|
33
|
+
return false;
|
|
34
|
+
return isAccessTokenShape(token) || (options.legacyTokenPattern?.test(token) ?? false);
|
|
35
|
+
}
|
|
36
|
+
/** A new secret with the given prefix and its hash; the plaintext is returned once, never stored. */
|
|
37
|
+
export function createSecret(prefix) {
|
|
38
|
+
const secret = `${prefix}${randomBytes(TOKEN_BYTES).toString("base64url")}`;
|
|
39
|
+
return { secret, secretHash: hashAccessToken(secret) };
|
|
29
40
|
}
|
|
30
41
|
/**
|
|
31
42
|
* Issues a token for its owner. The per-account limit counts unexpired tokens, in one transaction
|
|
@@ -46,10 +57,14 @@ export async function issueAccessToken(ctx, input) {
|
|
|
46
57
|
return ctx.db.transaction(async (tx) => {
|
|
47
58
|
await tx.execute(sql `select pg_advisory_xact_lock(hashtextextended(${`mcp-access:${input.userId}`}, 0))`);
|
|
48
59
|
await tx.delete(accessTokens).where(and(eq(accessTokens.userId, input.userId), lte(accessTokens.expiresAt, now)));
|
|
49
|
-
|
|
60
|
+
// OAuth access tokens belong to their grant and rotate every hour: they do not take places.
|
|
61
|
+
const [held] = await tx
|
|
62
|
+
.select({ total: count() })
|
|
63
|
+
.from(accessTokens)
|
|
64
|
+
.where(and(eq(accessTokens.userId, input.userId), isNull(accessTokens.grantId)));
|
|
50
65
|
if ((held?.total ?? 0) >= options.maxTokensPerUser)
|
|
51
66
|
return err("mcp-access.token_limit_reached");
|
|
52
|
-
const { token, tokenHash } =
|
|
67
|
+
const { secret: token, secretHash: tokenHash } = createSecret(ACCESS_TOKEN_PREFIX);
|
|
53
68
|
const [row] = await tx
|
|
54
69
|
.insert(accessTokens)
|
|
55
70
|
.values({ userId: input.userId, name, tokenHash, canWrite, createdAt: now, expiresAt })
|
|
@@ -59,7 +74,10 @@ export async function issueAccessToken(ctx, input) {
|
|
|
59
74
|
return ok({ id: row.id, token, name, canWrite, expiresAt });
|
|
60
75
|
});
|
|
61
76
|
}
|
|
62
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* The owner's hand-issued tokens, newest first, expired ones included (the list marks them).
|
|
79
|
+
* OAuth access tokens are left out: the page shows their grants instead.
|
|
80
|
+
*/
|
|
63
81
|
export async function listAccessTokens(ctx, userId) {
|
|
64
82
|
return ctx.db
|
|
65
83
|
.select({
|
|
@@ -71,19 +89,20 @@ export async function listAccessTokens(ctx, userId) {
|
|
|
71
89
|
lastUsedAt: accessTokens.lastUsedAt,
|
|
72
90
|
})
|
|
73
91
|
.from(accessTokens)
|
|
74
|
-
.where(eq(accessTokens.userId, userId))
|
|
92
|
+
.where(and(eq(accessTokens.userId, userId), isNull(accessTokens.grantId)))
|
|
75
93
|
.orderBy(desc(accessTokens.createdAt), desc(accessTokens.id));
|
|
76
94
|
}
|
|
77
95
|
/**
|
|
78
|
-
* Deletes a token at once; the next request with it gets 401. The owner is part of the
|
|
79
|
-
* so another account's id matches no row instead of relying on a comparison here.
|
|
96
|
+
* Deletes a hand-issued token at once; the next request with it gets 401. The owner is part of the
|
|
97
|
+
* `WHERE`, so another account's id matches no row instead of relying on a comparison here. An
|
|
98
|
+
* OAuth access token is revoked with its grant (`revokeOAuthGrant`).
|
|
80
99
|
*/
|
|
81
100
|
export async function revokeAccessToken(ctx, input) {
|
|
82
101
|
if (!UUID_SHAPE.test(input.tokenId))
|
|
83
102
|
return err("mcp-access.token_not_found");
|
|
84
103
|
const removed = await ctx.db
|
|
85
104
|
.delete(accessTokens)
|
|
86
|
-
.where(and(eq(accessTokens.userId, input.userId), eq(accessTokens.id, input.tokenId)))
|
|
105
|
+
.where(and(eq(accessTokens.userId, input.userId), eq(accessTokens.id, input.tokenId), isNull(accessTokens.grantId)))
|
|
87
106
|
.returning();
|
|
88
107
|
return removed.length > 0 ? ok() : err("mcp-access.token_not_found");
|
|
89
108
|
}
|
|
@@ -94,39 +113,55 @@ export async function revokeAccessToken(ctx, input) {
|
|
|
94
113
|
* fail the request. Database failures of the lookup propagate.
|
|
95
114
|
*/
|
|
96
115
|
export async function verifyAccessToken(ctx, token) {
|
|
97
|
-
|
|
116
|
+
const options = getMcpAccessOptions(ctx.config);
|
|
117
|
+
if (!isAcceptedTokenShape(options, token))
|
|
98
118
|
return null;
|
|
99
119
|
const now = ctx.clock.now();
|
|
100
120
|
const [row] = await ctx.db
|
|
101
|
-
.select({
|
|
121
|
+
.select({
|
|
122
|
+
id: accessTokens.id,
|
|
123
|
+
userId: accessTokens.userId,
|
|
124
|
+
canWrite: accessTokens.canWrite,
|
|
125
|
+
expiresAt: accessTokens.expiresAt,
|
|
126
|
+
grantId: accessTokens.grantId,
|
|
127
|
+
})
|
|
102
128
|
.from(accessTokens)
|
|
103
129
|
.where(and(eq(accessTokens.tokenHash, hashAccessToken(token)), gt(accessTokens.expiresAt, now)));
|
|
104
130
|
if (row === undefined)
|
|
105
131
|
return null;
|
|
106
|
-
await recordUse(ctx, row
|
|
132
|
+
await recordUse(ctx, row, now);
|
|
107
133
|
return {
|
|
108
134
|
tokenId: row.id,
|
|
109
135
|
userId: row.userId,
|
|
110
|
-
canWrite: row.canWrite &&
|
|
136
|
+
canWrite: row.canWrite && options.allowWrites,
|
|
111
137
|
expiresAt: row.expiresAt,
|
|
138
|
+
grantId: row.grantId,
|
|
112
139
|
};
|
|
113
140
|
}
|
|
114
|
-
|
|
141
|
+
/** Records the use on the token and, for an OAuth token, on its grant (the connected apps list). */
|
|
142
|
+
async function recordUse(ctx, token, now) {
|
|
115
143
|
const staleBefore = new Date(now.getTime() - LAST_USED_RESOLUTION_MS);
|
|
116
144
|
try {
|
|
117
145
|
await ctx.db
|
|
118
146
|
.update(accessTokens)
|
|
119
147
|
.set({ lastUsedAt: now })
|
|
120
|
-
.where(and(eq(accessTokens.id,
|
|
148
|
+
.where(and(eq(accessTokens.id, token.id), or(isNull(accessTokens.lastUsedAt), lt(accessTokens.lastUsedAt, staleBefore))));
|
|
149
|
+
if (token.grantId !== null) {
|
|
150
|
+
await ctx.db
|
|
151
|
+
.update(oauthGrants)
|
|
152
|
+
.set({ lastUsedAt: now })
|
|
153
|
+
.where(and(eq(oauthGrants.id, token.grantId), or(isNull(oauthGrants.lastUsedAt), lt(oauthGrants.lastUsedAt, staleBefore))));
|
|
154
|
+
}
|
|
121
155
|
}
|
|
122
156
|
catch (error) {
|
|
123
157
|
// The token id is not a secret; the token is never logged.
|
|
124
|
-
console.error(`@softure-ai/mcp-access: recording the use of token ${
|
|
158
|
+
console.error(`@softure-ai/mcp-access: recording the use of token ${token.id} failed: ${errorLogLabel(error)}`);
|
|
125
159
|
}
|
|
126
160
|
}
|
|
127
161
|
/**
|
|
128
|
-
* Deletes every expired token, of every account
|
|
129
|
-
* may call this from a scheduled job so abandoned accounts do not
|
|
162
|
+
* Deletes every expired token, of every account, hand-issued and OAuth alike. Issuing already
|
|
163
|
+
* removes the issuer's own; an app may call this from a scheduled job so abandoned accounts do not
|
|
164
|
+
* keep dead rows (with `pruneOAuthRecords` for codes, grants and clients).
|
|
130
165
|
*/
|
|
131
166
|
export async function pruneAccessTokens(ctx) {
|
|
132
167
|
const removed = await ctx.db
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/server/tokens.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/server/tokens.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type ClassNames } from "@softure-ai/ui";
|
|
2
|
+
import type { McpAccessMessages } from "../messages/index.js";
|
|
3
|
+
export type ConsentFormSlot = "root" | "details" | "term" | "value" | "note" | "actions" | "text" | "link";
|
|
4
|
+
export interface ConsentFormProps {
|
|
5
|
+
/** The decision route the form posts to. */
|
|
6
|
+
readonly action: string;
|
|
7
|
+
/** The authorization request's parameters, carried in hidden fields. */
|
|
8
|
+
readonly params: readonly (readonly [string, string])[];
|
|
9
|
+
readonly clientName: string;
|
|
10
|
+
/** Where the code goes: the host for http(s), the whole URI for a native scheme. */
|
|
11
|
+
readonly redirectTarget: string;
|
|
12
|
+
readonly accountEmail: string;
|
|
13
|
+
/** Offer the change checkbox: the client asked for `mcp:write` and the app allows writes. */
|
|
14
|
+
readonly isWriteOffered: boolean;
|
|
15
|
+
readonly messages: McpAccessMessages;
|
|
16
|
+
readonly classNames?: ClassNames<ConsentFormSlot>;
|
|
17
|
+
readonly unstyled?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function ConsentForm({ action, params, clientName, redirectTarget, accountEmail, isWriteOffered, messages, classNames, unstyled }: ConsentFormProps): import("react").JSX.Element;
|
|
20
|
+
export interface ConsentErrorProps {
|
|
21
|
+
readonly message: string;
|
|
22
|
+
/** The client's redirect URI with the error, when the client was confirmed; a link, never automatic. */
|
|
23
|
+
readonly backLocation?: string;
|
|
24
|
+
readonly backTarget?: string;
|
|
25
|
+
readonly messages: McpAccessMessages;
|
|
26
|
+
readonly classNames?: ClassNames<ConsentFormSlot>;
|
|
27
|
+
readonly unstyled?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/** Why the request cannot go on. Nothing was granted; going back to the client is the person's click. */
|
|
30
|
+
export declare function ConsentError({ message, backLocation, backTarget, messages, classNames, unstyled }: ConsentErrorProps): import("react").JSX.Element;
|
|
31
|
+
//# sourceMappingURL=consent-form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent-form.d.ts","sourceRoot":"","sources":["../../src/ui/consent-form.tsx"],"names":[],"mappings":"AACA,OAAO,EAAoB,KAAK,UAAU,EAAyB,MAAM,gBAAgB,CAAC;AAC1F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAQ9D,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAE3G,MAAM,WAAW,gBAAgB;IAC/B,4CAA4C;IAC5C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;IACxD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,oFAAoF;IACpF,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,6FAA6F;IAC7F,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC,eAAe,CAAC,CAAC;IAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAaD,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+BAmCzJ;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wGAAwG;IACxG,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC,eAAe,CAAC,CAAC;IAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,yGAAyG;AACzG,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,iBAAiB,+BAepH"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { formatMessage } from "@softure-ai/core";
|
|
3
|
+
import { Button, Checkbox, createSlotClassGetter } from "@softure-ai/ui";
|
|
4
|
+
const DEFAULT_CLASSES = {
|
|
5
|
+
root: "sft:flex sft:flex-col sft:gap-4 sft:font-sans",
|
|
6
|
+
details: "sft:m-0 sft:flex sft:flex-col sft:gap-3 sft:text-sm",
|
|
7
|
+
term: "sft:text-muted",
|
|
8
|
+
value: "sft:m-0 sft:font-medium sft:text-foreground",
|
|
9
|
+
note: "sft:font-normal sft:text-muted",
|
|
10
|
+
actions: "sft:flex sft:flex-col sft:gap-3",
|
|
11
|
+
text: "sft:m-0 sft:text-sm sft:text-muted",
|
|
12
|
+
link: "sft:font-medium sft:text-accent",
|
|
13
|
+
};
|
|
14
|
+
export function ConsentForm({ action, params, clientName, redirectTarget, accountEmail, isWriteOffered, messages, classNames, unstyled }) {
|
|
15
|
+
const slot = createSlotClassGetter({ defaults: DEFAULT_CLASSES, classNames, unstyled });
|
|
16
|
+
const copy = messages.consent;
|
|
17
|
+
return (_jsxs("form", { method: "post", action: action, className: slot("root"), children: [params.map(([name, value]) => (_jsx("input", { type: "hidden", name: name, value: value }, name))), _jsxs("dl", { className: slot("details"), children: [_jsxs("div", { children: [_jsx("dt", { className: slot("term"), children: copy.app }), _jsxs("dd", { className: slot("value"), children: [clientName, " ", _jsxs("span", { className: slot("note"), children: ["(", formatMessage(copy.returnsTo, { target: redirectTarget }), ")"] })] })] }), _jsxs("div", { children: [_jsx("dt", { className: slot("term"), children: copy.account }), _jsx("dd", { className: slot("value"), children: accountEmail })] }), _jsxs("div", { children: [_jsx("dt", { className: slot("term"), children: copy.access }), _jsx("dd", { className: slot("value"), children: copy.readAccess })] })] }), isWriteOffered ? _jsx(Checkbox, { name: "canWrite", label: copy.allowWrite, description: copy.allowWriteHint, unstyled: unstyled }) : null, _jsxs("div", { className: slot("actions"), children: [_jsx(Button, { type: "submit", name: "decision", value: "allow", variant: "primary", size: "lg", fullWidth: true, unstyled: unstyled, children: copy.allow }), _jsx(Button, { type: "submit", name: "decision", value: "deny", variant: "secondary", size: "lg", fullWidth: true, unstyled: unstyled, children: copy.deny })] })] }));
|
|
18
|
+
}
|
|
19
|
+
/** Why the request cannot go on. Nothing was granted; going back to the client is the person's click. */
|
|
20
|
+
export function ConsentError({ message, backLocation, backTarget, messages, classNames, unstyled }) {
|
|
21
|
+
const slot = createSlotClassGetter({ defaults: DEFAULT_CLASSES, classNames, unstyled });
|
|
22
|
+
return (_jsxs("div", { className: slot("root"), children: [_jsx("p", { className: slot("text"), children: message }), _jsx("p", { className: slot("text"), children: messages.consent.nothingGranted }), backLocation === undefined || backTarget === undefined ? null : (_jsx("p", { className: slot("text"), children: _jsx("a", { href: backLocation, className: slot("link"), children: formatMessage(messages.consent.backToApp, { target: backTarget }) }) }))] }));
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=consent-form.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent-form.js","sourceRoot":"","sources":["../../src/ui/consent-form.tsx"],"names":[],"mappings":"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"}
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { ConsentError, ConsentForm, type ConsentErrorProps, type ConsentFormProps, type ConsentFormSlot } from "./consent-form.js";
|
|
2
|
+
export { TokenManager, type RevokeGrantAction, type TokenManagerGrant, type IssueTokenAction, type RevokeTokenAction, type TokenManagerProps, type TokenManagerRow, type TokenManagerSlot, type TokenManagerTool, } from "./token-manager.js";
|
|
2
3
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/ui/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACnI,OAAO,EACL,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC"}
|
package/dist/ui/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// The token manager, to compose into the app's own page. It takes the server actions from
|
|
2
2
|
// `@softure-ai/mcp-access/next` as props: no `next/*` import here (docs/02-module-standard.md §5).
|
|
3
|
+
export { ConsentError, ConsentForm } from "./consent-form.js";
|
|
3
4
|
export { TokenManager, } from "./token-manager.js";
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
package/dist/ui/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,mGAAmG;AACnG,OAAO,EACL,YAAY,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,mGAAmG;AACnG,OAAO,EAAE,YAAY,EAAE,WAAW,EAAuE,MAAM,mBAAmB,CAAC;AACnI,OAAO,EACL,YAAY,GASb,MAAM,oBAAoB,CAAC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type Locale } from "@softure-ai/core";
|
|
2
2
|
import { type ClassNames } from "@softure-ai/ui";
|
|
3
|
-
import type { IssueTokenFormState, RevokeTokenFormState } from "../contract.js";
|
|
3
|
+
import type { IssueTokenFormState, RevokeGrantFormState, RevokeTokenFormState } from "../contract.js";
|
|
4
4
|
import { type McpAccessMessages } from "../messages/index.js";
|
|
5
5
|
export type IssueTokenAction = (previous: IssueTokenFormState, formData: FormData) => Promise<IssueTokenFormState>;
|
|
6
6
|
export type RevokeTokenAction = (previous: RevokeTokenFormState, formData: FormData) => Promise<RevokeTokenFormState>;
|
|
7
|
+
export type RevokeGrantAction = (previous: RevokeGrantFormState, formData: FormData) => Promise<RevokeGrantFormState>;
|
|
7
8
|
export type TokenManagerSlot = "root" | "section" | "heading" | "text" | "list" | "item" | "itemHeader" | "badge" | "details" | "snippet" | "code";
|
|
8
9
|
/** One tool of the app's catalog, prepared on the server. */
|
|
9
10
|
export interface TokenManagerTool {
|
|
@@ -24,6 +25,15 @@ export interface TokenManagerRow {
|
|
|
24
25
|
/** Created, valid until and last used, already formatted. */
|
|
25
26
|
readonly details: readonly string[];
|
|
26
27
|
}
|
|
28
|
+
/** One app connected through OAuth, prepared on the server. */
|
|
29
|
+
export interface TokenManagerGrant {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly clientName: string;
|
|
32
|
+
/** Read only, or read and change, already in the app's copy. */
|
|
33
|
+
readonly scopeText: string;
|
|
34
|
+
/** Connected on and last used, already formatted. */
|
|
35
|
+
readonly details: readonly string[];
|
|
36
|
+
}
|
|
27
37
|
export interface TokenManagerProps {
|
|
28
38
|
readonly tools: readonly TokenManagerTool[];
|
|
29
39
|
readonly tokens: readonly TokenManagerRow[];
|
|
@@ -33,11 +43,15 @@ export interface TokenManagerProps {
|
|
|
33
43
|
readonly maxTokens: number;
|
|
34
44
|
readonly issueAction: IssueTokenAction;
|
|
35
45
|
readonly revokeAction: RevokeTokenAction;
|
|
46
|
+
/** The connected apps; leave out while OAuth is off, and the section is not shown. */
|
|
47
|
+
readonly grants?: readonly TokenManagerGrant[];
|
|
48
|
+
/** Disconnects one app; needed with `grants`. */
|
|
49
|
+
readonly revokeGrantAction?: RevokeGrantAction;
|
|
36
50
|
readonly messages: McpAccessMessages;
|
|
37
51
|
/** Locale of the built-in copy of the ui primitives. */
|
|
38
52
|
readonly locale?: Locale;
|
|
39
53
|
readonly classNames?: ClassNames<TokenManagerSlot>;
|
|
40
54
|
readonly unstyled?: boolean;
|
|
41
55
|
}
|
|
42
|
-
export declare function TokenManager({ tools, tokens, allowWrites, maxTokens, issueAction, revokeAction, messages, locale, classNames, unstyled }: TokenManagerProps): import("react").JSX.Element;
|
|
56
|
+
export declare function TokenManager({ tools, tokens, allowWrites, maxTokens, issueAction, revokeAction, grants, revokeGrantAction, messages, locale, classNames, unstyled, }: TokenManagerProps): import("react").JSX.Element;
|
|
43
57
|
//# sourceMappingURL=token-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-manager.d.ts","sourceRoot":"","sources":["../../src/ui/token-manager.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAgC,KAAK,UAAU,EAA+C,MAAM,gBAAgB,CAAC;AAE5H,OAAO,KAAK,EAAe,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"token-manager.d.ts","sourceRoot":"","sources":["../../src/ui/token-manager.tsx"],"names":[],"mappings":"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"}
|
package/dist/ui/token-manager.js
CHANGED
|
@@ -43,8 +43,12 @@ function TokenItem({ row, action, messages, slot, unstyled, }) {
|
|
|
43
43
|
const [state, formAction, isPending] = useActionState(action, { status: "idle" });
|
|
44
44
|
return (_jsxs("li", { className: slot("item"), "data-token-id": row.id, children: [_jsxs("div", { className: slot("itemHeader"), children: [_jsx("p", { className: slot("heading"), children: row.name }), _jsxs("form", { action: formAction, children: [_jsx("input", { type: "hidden", name: "id", value: row.id }), _jsx(Button, { type: "submit", variant: "danger", size: "sm", pending: isPending, "aria-label": `${messages.list.revoke}: ${row.name}`, unstyled: unstyled, children: isPending ? messages.list.revoking : messages.list.revoke })] })] }), _jsxs("p", { className: row.isExpired ? slot("details") : slot("badge"), children: [row.statusText, " \u00B7 ", row.scopeText] }), _jsx("p", { className: slot("details"), children: row.details.join(" · ") }), _jsx(FormError, { message: state.status === "error" ? getTokenErrorMessage(messages, state.error) : undefined, unstyled: unstyled })] }));
|
|
45
45
|
}
|
|
46
|
-
|
|
46
|
+
function GrantItem({ grant, action, messages, slot, unstyled, }) {
|
|
47
|
+
const [state, formAction, isPending] = useActionState(action, { status: "idle" });
|
|
48
|
+
return (_jsxs("li", { className: slot("item"), "data-grant-id": grant.id, children: [_jsxs("div", { className: slot("itemHeader"), children: [_jsx("p", { className: slot("heading"), children: grant.clientName }), _jsxs("form", { action: formAction, children: [_jsx("input", { type: "hidden", name: "id", value: grant.id }), _jsx(Button, { type: "submit", variant: "danger", size: "sm", pending: isPending, "aria-label": `${messages.grants.disconnect}: ${grant.clientName}`, unstyled: unstyled, children: isPending ? messages.grants.disconnecting : messages.grants.disconnect })] })] }), _jsx("p", { className: slot("badge"), children: grant.scopeText }), _jsx("p", { className: slot("details"), children: grant.details.join(" · ") }), _jsx(FormError, { message: state.status === "error" ? getTokenErrorMessage(messages, state.error) : undefined, unstyled: unstyled })] }));
|
|
49
|
+
}
|
|
50
|
+
export function TokenManager({ tools, tokens, allowWrites, maxTokens, issueAction, revokeAction, grants, revokeGrantAction, messages, locale, classNames, unstyled, }) {
|
|
47
51
|
const slot = createSlotClassGetter({ defaults: DEFAULT_CLASSES, classNames, unstyled });
|
|
48
|
-
return (_jsxs("div", { className: slot("root"), children: [_jsxs("section", { className: slot("section"), children: [_jsx("h2", { className: slot("heading"), children: messages.tools.title }), tools.length === 0 ? (_jsx("p", { className: slot("text"), children: messages.tools.empty })) : (_jsx("ul", { className: slot("list"), children: tools.map((tool) => (_jsxs("li", { className: slot("item"), children: [_jsxs("div", { className: slot("itemHeader"), children: [_jsx("code", { className: slot("heading"), children: tool.name }), _jsx("span", { className: slot("badge"), children: tool.accessText })] }), _jsx("p", { className: slot("text"), children: tool.description })] }, tool.name))) }))] }), _jsx(IssueForm, { action: issueAction, allowWrites: allowWrites, maxTokens: maxTokens, messages: messages, locale: locale, slot: slot, unstyled: unstyled }), _jsxs("section", { className: slot("section"), children: [_jsx("h2", { className: slot("heading"), children: messages.list.title }), tokens.length === 0 ? (_jsx("p", { className: slot("text"), children: messages.list.empty })) : (_jsx("ul", { className: slot("list"), children: tokens.map((row) => (_jsx(TokenItem, { row: row, action: revokeAction, messages: messages, slot: slot, unstyled: unstyled }, row.id))) }))] })] }));
|
|
52
|
+
return (_jsxs("div", { className: slot("root"), children: [_jsxs("section", { className: slot("section"), children: [_jsx("h2", { className: slot("heading"), children: messages.tools.title }), tools.length === 0 ? (_jsx("p", { className: slot("text"), children: messages.tools.empty })) : (_jsx("ul", { className: slot("list"), children: tools.map((tool) => (_jsxs("li", { className: slot("item"), children: [_jsxs("div", { className: slot("itemHeader"), children: [_jsx("code", { className: slot("heading"), children: tool.name }), _jsx("span", { className: slot("badge"), children: tool.accessText })] }), _jsx("p", { className: slot("text"), children: tool.description })] }, tool.name))) }))] }), _jsx(IssueForm, { action: issueAction, allowWrites: allowWrites, maxTokens: maxTokens, messages: messages, locale: locale, slot: slot, unstyled: unstyled }), _jsxs("section", { className: slot("section"), children: [_jsx("h2", { className: slot("heading"), children: messages.list.title }), tokens.length === 0 ? (_jsx("p", { className: slot("text"), children: messages.list.empty })) : (_jsx("ul", { className: slot("list"), children: tokens.map((row) => (_jsx(TokenItem, { row: row, action: revokeAction, messages: messages, slot: slot, unstyled: unstyled }, row.id))) }))] }), grants === undefined || revokeGrantAction === undefined ? null : (_jsxs("section", { className: slot("section"), children: [_jsx("h2", { className: slot("heading"), children: messages.grants.title }), _jsx("p", { className: slot("text"), children: messages.grants.lead }), grants.length === 0 ? (_jsx("p", { className: slot("text"), children: messages.grants.empty })) : (_jsx("ul", { className: slot("list"), children: grants.map((grant) => (_jsx(GrantItem, { grant: grant, action: revokeGrantAction, messages: messages, slot: slot, unstyled: unstyled }, grant.id))) }))] }))] }));
|
|
49
53
|
}
|
|
50
54
|
//# sourceMappingURL=token-manager.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-manager.js","sourceRoot":"","sources":["../../src/ui/token-manager.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,aAAa,EAAe,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAmB,qBAAqB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC5H,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjD,OAAO,EAAE,oBAAoB,EAA0B,MAAM,sBAAsB,CAAC;AACpF,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"token-manager.js","sourceRoot":"","sources":["../../src/ui/token-manager.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
-- OAuth 2.1 for MCP clients (opt-in, `oauth.enabled`): clients registered dynamically, short-lived
|
|
2
|
+
-- authorization codes, and one grant per account and client carrying a rotated refresh token. An
|
|
3
|
+
-- access token issued through OAuth is a row of access_tokens with its grant; revoking the grant
|
|
4
|
+
-- deletes its tokens by cascade. Every secret is stored as its sha256 only.
|
|
5
|
+
-- Rollback: ALTER TABLE mcp.access_tokens DROP COLUMN grant_id;
|
|
6
|
+
-- DROP TABLE mcp.oauth_grants, mcp.oauth_authorization_codes, mcp.oauth_clients; then
|
|
7
|
+
-- DELETE FROM softure.migrations WHERE module = 'mcp-access' AND version = 2;
|
|
8
|
+
CREATE TABLE oauth_clients (
|
|
9
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
10
|
+
-- Public identifier the client sends (RFC 6749 §2.2); not a secret.
|
|
11
|
+
client_id text NOT NULL CHECK (char_length(client_id) BETWEEN 1 AND 255),
|
|
12
|
+
-- Shown on the consent page and in the connected apps list; also the name of its access tokens.
|
|
13
|
+
client_name text NOT NULL CHECK (char_length(client_name) BETWEEN 1 AND 60 AND client_name = btrim(client_name)),
|
|
14
|
+
redirect_uris jsonb NOT NULL CHECK (jsonb_typeof(redirect_uris) = 'array' AND jsonb_array_length(redirect_uris) BETWEEN 1 AND 10),
|
|
15
|
+
token_endpoint_auth_method text NOT NULL CHECK (token_endpoint_auth_method IN ('none', 'client_secret_post', 'client_secret_basic')),
|
|
16
|
+
-- Only for confidential clients; a public client (`none`) is protected by PKCE alone.
|
|
17
|
+
client_secret_hash text CHECK (client_secret_hash ~ '^[0-9a-f]{64}$'),
|
|
18
|
+
created_at timestamptz NOT NULL,
|
|
19
|
+
CONSTRAINT oauth_clients_client_id_key UNIQUE (client_id),
|
|
20
|
+
CONSTRAINT oauth_clients_secret_matches_method CHECK ((token_endpoint_auth_method = 'none') = (client_secret_hash IS NULL))
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
CREATE TABLE oauth_authorization_codes (
|
|
24
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
25
|
+
code_hash text NOT NULL CHECK (code_hash ~ '^[0-9a-f]{64}$'),
|
|
26
|
+
client_id uuid NOT NULL REFERENCES oauth_clients (id) ON DELETE CASCADE,
|
|
27
|
+
user_id uuid NOT NULL REFERENCES auth.users (id) ON DELETE CASCADE,
|
|
28
|
+
redirect_uri text NOT NULL,
|
|
29
|
+
-- PKCE S256 challenge: base64url of a sha256, always 43 characters.
|
|
30
|
+
code_challenge text NOT NULL CHECK (code_challenge ~ '^[A-Za-z0-9_-]{43}$'),
|
|
31
|
+
can_write boolean NOT NULL,
|
|
32
|
+
expires_at timestamptz NOT NULL,
|
|
33
|
+
-- Set by the one exchange that may use the code; a second use revokes the grant it produced.
|
|
34
|
+
used_at timestamptz,
|
|
35
|
+
created_at timestamptz NOT NULL,
|
|
36
|
+
CHECK (expires_at > created_at),
|
|
37
|
+
CONSTRAINT oauth_authorization_codes_code_hash_key UNIQUE (code_hash)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
CREATE INDEX oauth_authorization_codes_client_id_idx ON oauth_authorization_codes (client_id);
|
|
41
|
+
CREATE INDEX oauth_authorization_codes_user_id_idx ON oauth_authorization_codes (user_id);
|
|
42
|
+
CREATE INDEX oauth_authorization_codes_expires_at_idx ON oauth_authorization_codes (expires_at);
|
|
43
|
+
|
|
44
|
+
CREATE TABLE oauth_grants (
|
|
45
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
46
|
+
user_id uuid NOT NULL REFERENCES auth.users (id) ON DELETE CASCADE,
|
|
47
|
+
client_id uuid NOT NULL REFERENCES oauth_clients (id) ON DELETE CASCADE,
|
|
48
|
+
-- Granted write access; writes also need the app's allowWrites option.
|
|
49
|
+
can_write boolean NOT NULL,
|
|
50
|
+
refresh_token_hash text NOT NULL CHECK (refresh_token_hash ~ '^[0-9a-f]{64}$'),
|
|
51
|
+
-- The refresh token before the last rotation: presenting it again revokes the grant.
|
|
52
|
+
previous_refresh_token_hash text CHECK (previous_refresh_token_hash ~ '^[0-9a-f]{64}$'),
|
|
53
|
+
refresh_expires_at timestamptz NOT NULL,
|
|
54
|
+
-- Informational, written at most once a minute; never an access decision.
|
|
55
|
+
last_used_at timestamptz,
|
|
56
|
+
created_at timestamptz NOT NULL,
|
|
57
|
+
CONSTRAINT oauth_grants_refresh_token_hash_key UNIQUE (refresh_token_hash),
|
|
58
|
+
-- One grant per account and client: a new consent replaces the old grant.
|
|
59
|
+
CONSTRAINT oauth_grants_user_id_client_id_key UNIQUE (user_id, client_id)
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
CREATE INDEX oauth_grants_client_id_idx ON oauth_grants (client_id);
|
|
63
|
+
CREATE INDEX oauth_grants_previous_refresh_token_hash_idx ON oauth_grants (previous_refresh_token_hash);
|
|
64
|
+
CREATE INDEX oauth_grants_refresh_expires_at_idx ON oauth_grants (refresh_expires_at);
|
|
65
|
+
|
|
66
|
+
-- The grant an access token was issued from; null for a token issued on the token page.
|
|
67
|
+
ALTER TABLE access_tokens ADD COLUMN grant_id uuid REFERENCES oauth_grants (id) ON DELETE CASCADE;
|
|
68
|
+
CREATE INDEX access_tokens_grant_id_idx ON access_tokens (grant_id);
|