skybridge 0.0.0-dev.8f8a525 → 0.0.0-dev.8f8b61a
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/dist/server/auth/setup.d.ts +1 -3
- package/dist/server/auth/setup.js +21 -68
- package/dist/server/auth/setup.js.map +1 -1
- package/dist/server/auth/setup.test.js +2 -267
- package/dist/server/auth/setup.test.js.map +1 -1
- package/dist/server/server.d.ts +4 -17
- package/dist/server/server.js +12 -66
- package/dist/server/server.js.map +1 -1
- package/dist/web/plugin/plugin.js +1 -0
- package/dist/web/plugin/plugin.js.map +1 -1
- package/package.json +1 -1
- package/dist/server/auth/security-schemes.d.ts +0 -13
- package/dist/server/auth/security-schemes.js +0 -53
- package/dist/server/auth/security-schemes.js.map +0 -1
- package/dist/server/auth/security-schemes.test.d.ts +0 -1
- package/dist/server/auth/security-schemes.test.js +0 -99
- package/dist/server/auth/security-schemes.test.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { AuthInfo } from "../auth.js";
|
|
2
|
-
import type { SecurityScheme, ToolAuth } from "../server.js";
|
|
3
|
-
export declare function authToSecuritySchemes(auth: ToolAuth): SecurityScheme[];
|
|
4
|
-
export declare function securitySchemesAllowAnonymous(schemes: SecurityScheme[] | undefined): boolean;
|
|
5
|
-
export type SchemeFailure = {
|
|
6
|
-
error: "invalid_token" | "insufficient_scope";
|
|
7
|
-
description: string;
|
|
8
|
-
scopes: string[];
|
|
9
|
-
};
|
|
10
|
-
export declare function evaluateSecuritySchemes(schemes: SecurityScheme[] | undefined, authInfo: AuthInfo | undefined): SchemeFailure | undefined;
|
|
11
|
-
export declare function httpStatusForFailure(failure: SchemeFailure): 401 | 403;
|
|
12
|
-
export declare function clientPrefersInBandChallenge(userAgent: string | undefined): boolean;
|
|
13
|
-
export declare function wwwAuthenticateHeader(failure: SchemeFailure, resourceMetadataUrl?: string): string;
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
export function authToSecuritySchemes(auth) {
|
|
2
|
-
const oauth2 = {
|
|
3
|
-
type: "oauth2",
|
|
4
|
-
...(auth.scopes?.length ? { scopes: auth.scopes } : {}),
|
|
5
|
-
};
|
|
6
|
-
return auth.public ? [{ type: "noauth" }, oauth2] : [oauth2];
|
|
7
|
-
}
|
|
8
|
-
export function securitySchemesAllowAnonymous(schemes) {
|
|
9
|
-
return !!schemes?.some((scheme) => scheme.type === "noauth");
|
|
10
|
-
}
|
|
11
|
-
export function evaluateSecuritySchemes(schemes, authInfo) {
|
|
12
|
-
if (securitySchemesAllowAnonymous(schemes) && !authInfo) {
|
|
13
|
-
return undefined;
|
|
14
|
-
}
|
|
15
|
-
const oauth2 = (schemes ?? []).filter((scheme) => scheme.type === "oauth2");
|
|
16
|
-
const required = oauth2.length ? oauth2 : [{ type: "oauth2" }];
|
|
17
|
-
const scopes = [
|
|
18
|
-
...new Set(required.flatMap((scheme) => scheme.scopes ?? [])),
|
|
19
|
-
];
|
|
20
|
-
if (!authInfo) {
|
|
21
|
-
return {
|
|
22
|
-
error: "invalid_token",
|
|
23
|
-
description: "Sign in to use this tool.",
|
|
24
|
-
scopes,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
const satisfied = required.some((scheme) => (scheme.scopes ?? []).every((scope) => authInfo.scopes.includes(scope)));
|
|
28
|
-
return satisfied
|
|
29
|
-
? undefined
|
|
30
|
-
: {
|
|
31
|
-
error: "insufficient_scope",
|
|
32
|
-
description: "Missing required scope for this tool.",
|
|
33
|
-
scopes,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
export function httpStatusForFailure(failure) {
|
|
37
|
-
return failure.error === "insufficient_scope" ? 403 : 401;
|
|
38
|
-
}
|
|
39
|
-
export function clientPrefersInBandChallenge(userAgent) {
|
|
40
|
-
const ua = (userAgent ?? "").toLowerCase();
|
|
41
|
-
return ua.includes("openai") || ua.includes("chatgpt");
|
|
42
|
-
}
|
|
43
|
-
export function wwwAuthenticateHeader(failure, resourceMetadataUrl) {
|
|
44
|
-
let header = `Bearer error="${failure.error}", error_description="${failure.description}"`;
|
|
45
|
-
if (failure.scopes.length) {
|
|
46
|
-
header += `, scope="${failure.scopes.join(" ")}"`;
|
|
47
|
-
}
|
|
48
|
-
if (resourceMetadataUrl) {
|
|
49
|
-
header += `, resource_metadata="${resourceMetadataUrl}"`;
|
|
50
|
-
}
|
|
51
|
-
return header;
|
|
52
|
-
}
|
|
53
|
-
//# sourceMappingURL=security-schemes.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"security-schemes.js","sourceRoot":"","sources":["../../../src/server/auth/security-schemes.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,qBAAqB,CAAC,IAAc;IAClD,MAAM,MAAM,GAAmB;QAC7B,IAAI,EAAE,QAAQ;QACd,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxD,CAAC;IACF,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,6BAA6B,CAC3C,OAAqC;IAErC,OAAO,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;AAC/D,CAAC;AAQD,MAAM,UAAU,uBAAuB,CACrC,OAAqC,EACrC,QAA8B;IAE9B,IAAI,6BAA6B,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACxD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;IAC5E,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAiB,EAAE,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG;QACb,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;KAC9D,CAAC;IACF,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO;YACL,KAAK,EAAE,eAAe;YACtB,WAAW,EAAE,2BAA2B;YACxC,MAAM;SACP,CAAC;IACJ,CAAC;IACD,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CACzC,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CACxE,CAAC;IACF,OAAO,SAAS;QACd,CAAC,CAAC,SAAS;QACX,CAAC,CAAC;YACE,KAAK,EAAE,oBAAoB;YAC3B,WAAW,EAAE,uCAAuC;YACpD,MAAM;SACP,CAAC;AACR,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,OAAsB;IACzD,OAAO,OAAO,CAAC,KAAK,KAAK,oBAAoB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,4BAA4B,CAC1C,SAA6B;IAE7B,MAAM,EAAE,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAC3C,OAAO,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,OAAsB,EACtB,mBAA4B;IAE5B,IAAI,MAAM,GAAG,iBAAiB,OAAO,CAAC,KAAK,yBAAyB,OAAO,CAAC,WAAW,GAAG,CAAC;IAC3F,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QAC1B,MAAM,IAAI,YAAY,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IACpD,CAAC;IACD,IAAI,mBAAmB,EAAE,CAAC;QACxB,MAAM,IAAI,wBAAwB,mBAAmB,GAAG,CAAC;IAC3D,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import type { AuthInfo } from \"../auth.js\";\nimport type { SecurityScheme, ToolAuth } from \"../server.js\";\n\nexport function authToSecuritySchemes(auth: ToolAuth): SecurityScheme[] {\n const oauth2: SecurityScheme = {\n type: \"oauth2\",\n ...(auth.scopes?.length ? { scopes: auth.scopes } : {}),\n };\n return auth.public ? [{ type: \"noauth\" }, oauth2] : [oauth2];\n}\n\nexport function securitySchemesAllowAnonymous(\n schemes: SecurityScheme[] | undefined,\n): boolean {\n return !!schemes?.some((scheme) => scheme.type === \"noauth\");\n}\n\nexport type SchemeFailure = {\n error: \"invalid_token\" | \"insufficient_scope\";\n description: string;\n scopes: string[];\n};\n\nexport function evaluateSecuritySchemes(\n schemes: SecurityScheme[] | undefined,\n authInfo: AuthInfo | undefined,\n): SchemeFailure | undefined {\n if (securitySchemesAllowAnonymous(schemes) && !authInfo) {\n return undefined;\n }\n const oauth2 = (schemes ?? []).filter((scheme) => scheme.type === \"oauth2\");\n const required = oauth2.length ? oauth2 : [{ type: \"oauth2\" as const }];\n const scopes = [\n ...new Set(required.flatMap((scheme) => scheme.scopes ?? [])),\n ];\n if (!authInfo) {\n return {\n error: \"invalid_token\",\n description: \"Sign in to use this tool.\",\n scopes,\n };\n }\n const satisfied = required.some((scheme) =>\n (scheme.scopes ?? []).every((scope) => authInfo.scopes.includes(scope)),\n );\n return satisfied\n ? undefined\n : {\n error: \"insufficient_scope\",\n description: \"Missing required scope for this tool.\",\n scopes,\n };\n}\n\nexport function httpStatusForFailure(failure: SchemeFailure): 401 | 403 {\n return failure.error === \"insufficient_scope\" ? 403 : 401;\n}\n\nexport function clientPrefersInBandChallenge(\n userAgent: string | undefined,\n): boolean {\n const ua = (userAgent ?? \"\").toLowerCase();\n return ua.includes(\"openai\") || ua.includes(\"chatgpt\");\n}\n\nexport function wwwAuthenticateHeader(\n failure: SchemeFailure,\n resourceMetadataUrl?: string,\n): string {\n let header = `Bearer error=\"${failure.error}\", error_description=\"${failure.description}\"`;\n if (failure.scopes.length) {\n header += `, scope=\"${failure.scopes.join(\" \")}\"`;\n }\n if (resourceMetadataUrl) {\n header += `, resource_metadata=\"${resourceMetadataUrl}\"`;\n }\n return header;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { authToSecuritySchemes, clientPrefersInBandChallenge, evaluateSecuritySchemes, securitySchemesAllowAnonymous, wwwAuthenticateHeader, } from "./security-schemes.js";
|
|
3
|
-
describe("clientPrefersInBandChallenge", () => {
|
|
4
|
-
it("detects ChatGPT / OpenAI user agents", () => {
|
|
5
|
-
expect(clientPrefersInBandChallenge("openai-mcp/1.0")).toBe(true);
|
|
6
|
-
expect(clientPrefersInBandChallenge("ChatGPT")).toBe(true);
|
|
7
|
-
expect(clientPrefersInBandChallenge("Claude-User/1.0")).toBe(false);
|
|
8
|
-
expect(clientPrefersInBandChallenge(undefined)).toBe(false);
|
|
9
|
-
});
|
|
10
|
-
});
|
|
11
|
-
describe("authToSecuritySchemes", () => {
|
|
12
|
-
it("maps the auth shorthand", () => {
|
|
13
|
-
expect(authToSecuritySchemes({ public: true })).toEqual([
|
|
14
|
-
{ type: "noauth" },
|
|
15
|
-
{ type: "oauth2" },
|
|
16
|
-
]);
|
|
17
|
-
expect(authToSecuritySchemes({})).toEqual([{ type: "oauth2" }]);
|
|
18
|
-
expect(authToSecuritySchemes({ scopes: ["checkout"] })).toEqual([
|
|
19
|
-
{ type: "oauth2", scopes: ["checkout"] },
|
|
20
|
-
]);
|
|
21
|
-
expect(authToSecuritySchemes({ public: true, scopes: ["checkout"] })).toEqual([{ type: "noauth" }, { type: "oauth2", scopes: ["checkout"] }]);
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
const authInfo = (scopes) => ({
|
|
25
|
-
token: "t",
|
|
26
|
-
clientId: "c",
|
|
27
|
-
scopes,
|
|
28
|
-
});
|
|
29
|
-
describe("securitySchemesAllowAnonymous", () => {
|
|
30
|
-
it("is true only when a noauth scheme is present", () => {
|
|
31
|
-
expect(securitySchemesAllowAnonymous([{ type: "noauth" }])).toBe(true);
|
|
32
|
-
expect(securitySchemesAllowAnonymous([{ type: "noauth" }, { type: "oauth2" }])).toBe(true);
|
|
33
|
-
expect(securitySchemesAllowAnonymous([{ type: "oauth2" }])).toBe(false);
|
|
34
|
-
expect(securitySchemesAllowAnonymous(undefined)).toBe(false);
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
describe("evaluateSecuritySchemes", () => {
|
|
38
|
-
it("lets anonymous callers through a noauth tool", () => {
|
|
39
|
-
const schemes = [{ type: "noauth" }, { type: "oauth2" }];
|
|
40
|
-
expect(evaluateSecuritySchemes(schemes, undefined)).toBeUndefined();
|
|
41
|
-
});
|
|
42
|
-
it("enforces oauth2 scopes on a public tool once a token is present", () => {
|
|
43
|
-
const schemes = [
|
|
44
|
-
{ type: "noauth" },
|
|
45
|
-
{ type: "oauth2", scopes: ["checkout"] },
|
|
46
|
-
];
|
|
47
|
-
expect(evaluateSecuritySchemes(schemes, undefined)).toBeUndefined();
|
|
48
|
-
expect(evaluateSecuritySchemes(schemes, authInfo(["openid"]))?.error).toBe("insufficient_scope");
|
|
49
|
-
expect(evaluateSecuritySchemes(schemes, authInfo(["checkout"]))).toBeUndefined();
|
|
50
|
-
});
|
|
51
|
-
it("fails an oauth2-only tool with no token (invalid_token / 401)", () => {
|
|
52
|
-
expect(evaluateSecuritySchemes([{ type: "oauth2" }], undefined)?.error).toBe("invalid_token");
|
|
53
|
-
});
|
|
54
|
-
it("fails when the token is missing a required scope (insufficient_scope / 403)", () => {
|
|
55
|
-
const schemes = [
|
|
56
|
-
{ type: "oauth2", scopes: ["checkout"] },
|
|
57
|
-
];
|
|
58
|
-
expect(evaluateSecuritySchemes(schemes, authInfo(["openid"]))?.error).toBe("insufficient_scope");
|
|
59
|
-
});
|
|
60
|
-
it("passes when the token carries every required scope", () => {
|
|
61
|
-
const schemes = [
|
|
62
|
-
{ type: "oauth2", scopes: ["checkout"] },
|
|
63
|
-
];
|
|
64
|
-
expect(evaluateSecuritySchemes(schemes, authInfo(["openid", "checkout"]))).toBeUndefined();
|
|
65
|
-
});
|
|
66
|
-
it("treats an undeclared tool as auth-required, satisfied by any valid token", () => {
|
|
67
|
-
expect(evaluateSecuritySchemes(undefined, undefined)?.error).toBe("invalid_token");
|
|
68
|
-
expect(evaluateSecuritySchemes(undefined, authInfo([]))).toBeUndefined();
|
|
69
|
-
});
|
|
70
|
-
it("carries the tool's required scopes on the failure", () => {
|
|
71
|
-
const schemes = [
|
|
72
|
-
{ type: "oauth2", scopes: ["checkout"] },
|
|
73
|
-
];
|
|
74
|
-
expect(evaluateSecuritySchemes(schemes, undefined)?.scopes).toEqual([
|
|
75
|
-
"checkout",
|
|
76
|
-
]);
|
|
77
|
-
expect(evaluateSecuritySchemes(schemes, authInfo(["openid"]))?.scopes).toEqual(["checkout"]);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
describe("wwwAuthenticateHeader", () => {
|
|
81
|
-
it("names the required scopes so the client can step up", () => {
|
|
82
|
-
const header = wwwAuthenticateHeader({
|
|
83
|
-
error: "insufficient_scope",
|
|
84
|
-
description: "Missing required scope for this tool.",
|
|
85
|
-
scopes: ["checkout"],
|
|
86
|
-
});
|
|
87
|
-
expect(header).toContain('error="insufficient_scope"');
|
|
88
|
-
expect(header).toContain('scope="checkout"');
|
|
89
|
-
});
|
|
90
|
-
it("omits the scope parameter when there are none", () => {
|
|
91
|
-
const header = wwwAuthenticateHeader({
|
|
92
|
-
error: "invalid_token",
|
|
93
|
-
description: "Sign in to use this tool.",
|
|
94
|
-
scopes: [],
|
|
95
|
-
});
|
|
96
|
-
expect(header).not.toContain("scope=");
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
//# sourceMappingURL=security-schemes.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"security-schemes.test.js","sourceRoot":"","sources":["../../../src/server/auth/security-schemes.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport type { AuthInfo } from \"../auth.js\";\nimport type { SecurityScheme } from \"../server.js\";\nimport {\n authToSecuritySchemes,\n clientPrefersInBandChallenge,\n evaluateSecuritySchemes,\n securitySchemesAllowAnonymous,\n wwwAuthenticateHeader,\n} from \"./security-schemes.js\";\n\ndescribe(\"clientPrefersInBandChallenge\", () => {\n it(\"detects ChatGPT / OpenAI user agents\", () => {\n expect(clientPrefersInBandChallenge(\"openai-mcp/1.0\")).toBe(true);\n expect(clientPrefersInBandChallenge(\"ChatGPT\")).toBe(true);\n expect(clientPrefersInBandChallenge(\"Claude-User/1.0\")).toBe(false);\n expect(clientPrefersInBandChallenge(undefined)).toBe(false);\n });\n});\n\ndescribe(\"authToSecuritySchemes\", () => {\n it(\"maps the auth shorthand\", () => {\n expect(authToSecuritySchemes({ public: true })).toEqual([\n { type: \"noauth\" },\n { type: \"oauth2\" },\n ]);\n expect(authToSecuritySchemes({})).toEqual([{ type: \"oauth2\" }]);\n expect(authToSecuritySchemes({ scopes: [\"checkout\"] })).toEqual([\n { type: \"oauth2\", scopes: [\"checkout\"] },\n ]);\n expect(\n authToSecuritySchemes({ public: true, scopes: [\"checkout\"] }),\n ).toEqual([{ type: \"noauth\" }, { type: \"oauth2\", scopes: [\"checkout\"] }]);\n });\n});\n\nconst authInfo = (scopes: string[]): AuthInfo => ({\n token: \"t\",\n clientId: \"c\",\n scopes,\n});\n\ndescribe(\"securitySchemesAllowAnonymous\", () => {\n it(\"is true only when a noauth scheme is present\", () => {\n expect(securitySchemesAllowAnonymous([{ type: \"noauth\" }])).toBe(true);\n expect(\n securitySchemesAllowAnonymous([{ type: \"noauth\" }, { type: \"oauth2\" }]),\n ).toBe(true);\n expect(securitySchemesAllowAnonymous([{ type: \"oauth2\" }])).toBe(false);\n expect(securitySchemesAllowAnonymous(undefined)).toBe(false);\n });\n});\n\ndescribe(\"evaluateSecuritySchemes\", () => {\n it(\"lets anonymous callers through a noauth tool\", () => {\n const schemes: SecurityScheme[] = [{ type: \"noauth\" }, { type: \"oauth2\" }];\n expect(evaluateSecuritySchemes(schemes, undefined)).toBeUndefined();\n });\n\n it(\"enforces oauth2 scopes on a public tool once a token is present\", () => {\n const schemes: SecurityScheme[] = [\n { type: \"noauth\" },\n { type: \"oauth2\", scopes: [\"checkout\"] },\n ];\n expect(evaluateSecuritySchemes(schemes, undefined)).toBeUndefined();\n expect(evaluateSecuritySchemes(schemes, authInfo([\"openid\"]))?.error).toBe(\n \"insufficient_scope\",\n );\n expect(\n evaluateSecuritySchemes(schemes, authInfo([\"checkout\"])),\n ).toBeUndefined();\n });\n\n it(\"fails an oauth2-only tool with no token (invalid_token / 401)\", () => {\n expect(\n evaluateSecuritySchemes([{ type: \"oauth2\" }], undefined)?.error,\n ).toBe(\"invalid_token\");\n });\n\n it(\"fails when the token is missing a required scope (insufficient_scope / 403)\", () => {\n const schemes: SecurityScheme[] = [\n { type: \"oauth2\", scopes: [\"checkout\"] },\n ];\n expect(evaluateSecuritySchemes(schemes, authInfo([\"openid\"]))?.error).toBe(\n \"insufficient_scope\",\n );\n });\n\n it(\"passes when the token carries every required scope\", () => {\n const schemes: SecurityScheme[] = [\n { type: \"oauth2\", scopes: [\"checkout\"] },\n ];\n expect(\n evaluateSecuritySchemes(schemes, authInfo([\"openid\", \"checkout\"])),\n ).toBeUndefined();\n });\n\n it(\"treats an undeclared tool as auth-required, satisfied by any valid token\", () => {\n expect(evaluateSecuritySchemes(undefined, undefined)?.error).toBe(\n \"invalid_token\",\n );\n expect(evaluateSecuritySchemes(undefined, authInfo([]))).toBeUndefined();\n });\n\n it(\"carries the tool's required scopes on the failure\", () => {\n const schemes: SecurityScheme[] = [\n { type: \"oauth2\", scopes: [\"checkout\"] },\n ];\n expect(evaluateSecuritySchemes(schemes, undefined)?.scopes).toEqual([\n \"checkout\",\n ]);\n expect(\n evaluateSecuritySchemes(schemes, authInfo([\"openid\"]))?.scopes,\n ).toEqual([\"checkout\"]);\n });\n});\n\ndescribe(\"wwwAuthenticateHeader\", () => {\n it(\"names the required scopes so the client can step up\", () => {\n const header = wwwAuthenticateHeader({\n error: \"insufficient_scope\",\n description: \"Missing required scope for this tool.\",\n scopes: [\"checkout\"],\n });\n expect(header).toContain('error=\"insufficient_scope\"');\n expect(header).toContain('scope=\"checkout\"');\n });\n\n it(\"omits the scope parameter when there are none\", () => {\n const header = wwwAuthenticateHeader({\n error: \"invalid_token\",\n description: \"Sign in to use this tool.\",\n scopes: [],\n });\n expect(header).not.toContain(\"scope=\");\n });\n});\n"]}
|