@qauth-labs/resource-guard 0.0.0 → 0.2.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +247 -1
- package/dist/fastify.d.ts +9 -0
- package/dist/fastify.js +9 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +30 -0
- package/dist/lib/challenge.d.ts +32 -0
- package/dist/lib/challenge.js +72 -0
- package/dist/lib/core.d.ts +75 -0
- package/dist/lib/core.js +217 -0
- package/dist/lib/errors.d.ts +116 -0
- package/dist/lib/errors.js +137 -0
- package/dist/lib/fastify-plugin.d.ts +76 -0
- package/dist/lib/fastify-plugin.js +167 -0
- package/dist/lib/introspection-validator.d.ts +53 -0
- package/dist/lib/introspection-validator.js +120 -0
- package/dist/lib/jwt-validator.d.ts +72 -0
- package/dist/lib/jwt-validator.js +181 -0
- package/dist/lib/metadata.d.ts +59 -0
- package/dist/lib/metadata.js +88 -0
- package/dist/lib/scope.d.ts +38 -0
- package/dist/lib/scope.js +73 -0
- package/dist/types.d.ts +160 -0
- package/dist/types.js +14 -0
- package/package.json +64 -12
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 9728 — OAuth 2.0 Protected Resource Metadata (PRM).
|
|
3
|
+
*
|
|
4
|
+
* The RS publishes a JSON document describing itself and the authorization
|
|
5
|
+
* server(s) that protect it. MCP clients fetch it (advertised via the
|
|
6
|
+
* `WWW-Authenticate: ... resource_metadata="…"` challenge, or discovered at
|
|
7
|
+
* the well-known location as a fallback) to learn which AS to authenticate
|
|
8
|
+
* with — the entry point of the MCP authorization handshake.
|
|
9
|
+
*/
|
|
10
|
+
import { advertisableScopes } from './scope.js';
|
|
11
|
+
/** Well-known suffix for Protected Resource Metadata (RFC 9728 §3.1). */
|
|
12
|
+
export const PRM_WELL_KNOWN_PREFIX = '/.well-known/oauth-protected-resource';
|
|
13
|
+
/** Strip a single trailing slash; leaves the root path `/` untouched-ish. */
|
|
14
|
+
function stripTrailingSlash(value) {
|
|
15
|
+
return value.length > 1 && value.endsWith('/') ? value.slice(0, -1) : value;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Build the RFC 9728 PRM document. Pure (no I/O) so route handlers control
|
|
19
|
+
* caching and tests can assert the wire shape directly.
|
|
20
|
+
*/
|
|
21
|
+
export function buildProtectedResourceMetadata(input) {
|
|
22
|
+
const metadata = {
|
|
23
|
+
resource: stripTrailingSlash(input.resource),
|
|
24
|
+
authorization_servers: [stripTrailingSlash(input.authorizationServer)],
|
|
25
|
+
bearer_methods_supported: ['header'],
|
|
26
|
+
};
|
|
27
|
+
// Advertise only scopes that are genuinely a requirement of THIS resource:
|
|
28
|
+
// `offline_access` and friends concern refresh-token issuance at the AS, not
|
|
29
|
+
// resource authorization, and MCP Authorization 2026-07-28 ("Refresh
|
|
30
|
+
// Tokens") says a protected resource SHOULD NOT list them here (#284).
|
|
31
|
+
// Filtering can empty the list, in which case the key is omitted —
|
|
32
|
+
// RFC 9728 §3.2 makes `scopes_supported` OPTIONAL, and an empty array would
|
|
33
|
+
// wrongly assert the resource understands no scopes at all.
|
|
34
|
+
const scopesSupported = advertisableScopes(input.scopesSupported ?? []);
|
|
35
|
+
if (scopesSupported.length > 0) {
|
|
36
|
+
metadata.scopes_supported = scopesSupported;
|
|
37
|
+
}
|
|
38
|
+
if (input.resourceDocumentation) {
|
|
39
|
+
metadata.resource_documentation = input.resourceDocumentation;
|
|
40
|
+
}
|
|
41
|
+
return metadata;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Compute the well-known PRM **path** for a resource, per RFC 9728 §3.1.
|
|
45
|
+
*
|
|
46
|
+
* For a resource with no path component the document lives at
|
|
47
|
+
* `/.well-known/oauth-protected-resource`. For a resource that has a path
|
|
48
|
+
* (e.g. `https://host/mcp/memory`), the path is inserted **after** the
|
|
49
|
+
* well-known prefix: `/.well-known/oauth-protected-resource/mcp/memory`. This
|
|
50
|
+
* lets a single host expose distinct metadata per protected resource.
|
|
51
|
+
*
|
|
52
|
+
* @returns the path (always starting with the well-known prefix).
|
|
53
|
+
*/
|
|
54
|
+
export function metadataPathForResource(resource) {
|
|
55
|
+
let pathname;
|
|
56
|
+
try {
|
|
57
|
+
pathname = new URL(resource).pathname;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Not parseable as a URL — treat as opaque, root path.
|
|
61
|
+
pathname = '/';
|
|
62
|
+
}
|
|
63
|
+
// Only hierarchical (http/https) URLs have a `/`-rooted path we can insert
|
|
64
|
+
// after the well-known prefix. Opaque URIs (e.g. `urn:example:resource`)
|
|
65
|
+
// expose a non-rooted `pathname`; for those we serve the bare prefix.
|
|
66
|
+
if (!pathname.startsWith('/')) {
|
|
67
|
+
return PRM_WELL_KNOWN_PREFIX;
|
|
68
|
+
}
|
|
69
|
+
const normalized = stripTrailingSlash(pathname);
|
|
70
|
+
if (normalized === '' || normalized === '/') {
|
|
71
|
+
return PRM_WELL_KNOWN_PREFIX;
|
|
72
|
+
}
|
|
73
|
+
return `${PRM_WELL_KNOWN_PREFIX}${normalized}`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Absolute URL where this resource's PRM document is served — the value
|
|
77
|
+
* advertised in the `resource_metadata` challenge parameter (RFC 9728 §5.1).
|
|
78
|
+
*/
|
|
79
|
+
export function metadataUrlForResource(resource) {
|
|
80
|
+
const path = metadataPathForResource(resource);
|
|
81
|
+
try {
|
|
82
|
+
const url = new URL(resource);
|
|
83
|
+
return `${url.origin}${path}`;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return path;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope utilities. OAuth scopes are a space-delimited, case-sensitive set
|
|
3
|
+
* (RFC 6749 §3.3). QAuth emits the `scope` claim as a single space-separated
|
|
4
|
+
* string; introspection returns the same shape (RFC 7662 §2.2).
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Parse a scope claim into a de-duplicated list. Accepts the wire string
|
|
8
|
+
* form, an already-split array, or `undefined`/empty (→ no scopes). Splits on
|
|
9
|
+
* any run of ASCII whitespace and discards empty segments.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseScopes(scope: string | string[] | undefined | null): string[];
|
|
12
|
+
/**
|
|
13
|
+
* Return the required scopes the granted set does not satisfy. Empty result
|
|
14
|
+
* means the granted set is sufficient. Matching is exact and case-sensitive
|
|
15
|
+
* per RFC 6749 §3.3 (no hierarchical/prefix semantics).
|
|
16
|
+
*
|
|
17
|
+
* MCP Authorization 2026-07-28 ("Step-Up Authorization Flow") adds: servers
|
|
18
|
+
* MUST account for scope hierarchies, "where a broader scope implies narrower
|
|
19
|
+
* ones", when deciding whether a token is sufficient. QAuth's scope vocabulary
|
|
20
|
+
* declares no such implications — every scope is independent, so no broader
|
|
21
|
+
* scope stands in for a narrower one — which makes exact matching the complete
|
|
22
|
+
* and correct reading of that rule here, not a shortfall. A host that defines a
|
|
23
|
+
* genuinely hierarchical vocabulary of its own must expand the implied set
|
|
24
|
+
* itself when it configures `requiredScopes` / `requireScopes(...)`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function missingScopes(granted: string[], required: string[]): string[];
|
|
27
|
+
/** True when every required scope is present in the granted set. */
|
|
28
|
+
export declare function hasRequiredScopes(granted: string[], required: string[]): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Filter a scope list down to what is meaningful to advertise as a resource
|
|
31
|
+
* requirement — see {@link NON_RESOURCE_SCOPES}.
|
|
32
|
+
*
|
|
33
|
+
* This is applied at the two *advertisement* surfaces only (the
|
|
34
|
+
* `WWW-Authenticate` challenge and PRM `scopes_supported`), never to
|
|
35
|
+
* enforcement: a host that configures such a scope still has it checked
|
|
36
|
+
* against the token, so this cannot widen access.
|
|
37
|
+
*/
|
|
38
|
+
export declare function advertisableScopes(scopes: string[]): string[];
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope utilities. OAuth scopes are a space-delimited, case-sensitive set
|
|
3
|
+
* (RFC 6749 §3.3). QAuth emits the `scope` claim as a single space-separated
|
|
4
|
+
* string; introspection returns the same shape (RFC 7662 §2.2).
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Parse a scope claim into a de-duplicated list. Accepts the wire string
|
|
8
|
+
* form, an already-split array, or `undefined`/empty (→ no scopes). Splits on
|
|
9
|
+
* any run of ASCII whitespace and discards empty segments.
|
|
10
|
+
*/
|
|
11
|
+
export function parseScopes(scope) {
|
|
12
|
+
if (scope == null) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
const tokens = Array.isArray(scope) ? scope : scope.split(/\s+/);
|
|
16
|
+
const seen = new Set();
|
|
17
|
+
for (const token of tokens) {
|
|
18
|
+
const trimmed = token.trim();
|
|
19
|
+
if (trimmed.length > 0) {
|
|
20
|
+
seen.add(trimmed);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return [...seen];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Return the required scopes the granted set does not satisfy. Empty result
|
|
27
|
+
* means the granted set is sufficient. Matching is exact and case-sensitive
|
|
28
|
+
* per RFC 6749 §3.3 (no hierarchical/prefix semantics).
|
|
29
|
+
*
|
|
30
|
+
* MCP Authorization 2026-07-28 ("Step-Up Authorization Flow") adds: servers
|
|
31
|
+
* MUST account for scope hierarchies, "where a broader scope implies narrower
|
|
32
|
+
* ones", when deciding whether a token is sufficient. QAuth's scope vocabulary
|
|
33
|
+
* declares no such implications — every scope is independent, so no broader
|
|
34
|
+
* scope stands in for a narrower one — which makes exact matching the complete
|
|
35
|
+
* and correct reading of that rule here, not a shortfall. A host that defines a
|
|
36
|
+
* genuinely hierarchical vocabulary of its own must expand the implied set
|
|
37
|
+
* itself when it configures `requiredScopes` / `requireScopes(...)`.
|
|
38
|
+
*/
|
|
39
|
+
export function missingScopes(granted, required) {
|
|
40
|
+
if (required.length === 0) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
const grantedSet = new Set(granted);
|
|
44
|
+
return required.filter((scope) => !grantedSet.has(scope));
|
|
45
|
+
}
|
|
46
|
+
/** True when every required scope is present in the granted set. */
|
|
47
|
+
export function hasRequiredScopes(granted, required) {
|
|
48
|
+
return missingScopes(granted, required).length === 0;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Scopes that describe the client's relationship with the *authorization
|
|
52
|
+
* server* rather than anything this resource will authorize, and which must
|
|
53
|
+
* therefore never be advertised as a resource requirement (#284).
|
|
54
|
+
*
|
|
55
|
+
* MCP Authorization 2026-07-28 ("Refresh Tokens") names `offline_access`
|
|
56
|
+
* explicitly — a protected resource SHOULD NOT include it in a
|
|
57
|
+
* `WWW-Authenticate` scope or in PRM `scopes_supported`, since it only governs
|
|
58
|
+
* whether the AS issues a refresh token. Advertising it would tell a client to
|
|
59
|
+
* obtain consent that has no bearing on the call it is trying to make.
|
|
60
|
+
*/
|
|
61
|
+
const NON_RESOURCE_SCOPES = new Set(['offline_access']);
|
|
62
|
+
/**
|
|
63
|
+
* Filter a scope list down to what is meaningful to advertise as a resource
|
|
64
|
+
* requirement — see {@link NON_RESOURCE_SCOPES}.
|
|
65
|
+
*
|
|
66
|
+
* This is applied at the two *advertisement* surfaces only (the
|
|
67
|
+
* `WWW-Authenticate` challenge and PRM `scopes_supported`), never to
|
|
68
|
+
* enforcement: a host that configures such a scope still has it checked
|
|
69
|
+
* against the token, so this cannot widen access.
|
|
70
|
+
*/
|
|
71
|
+
export function advertisableScopes(scopes) {
|
|
72
|
+
return scopes.filter((scope) => !NON_RESOURCE_SCOPES.has(scope));
|
|
73
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for `@qauth-labs/resource-guard`.
|
|
3
|
+
*
|
|
4
|
+
* `resource-guard` is the resource-server (RS) side of the QAuth OAuth 2.1 stack.
|
|
5
|
+
* An MCP-server author drops it in to obtain spec-correct OAuth from a
|
|
6
|
+
* self-hosted QAuth authorization server (AS), per the MCP Authorization
|
|
7
|
+
* profile revision 2026-07-28 and the RFCs it references:
|
|
8
|
+
*
|
|
9
|
+
* - RFC 9728 — OAuth 2.0 Protected Resource Metadata (PRM)
|
|
10
|
+
* - RFC 8707 — Resource Indicators (audience binding; no token passthrough)
|
|
11
|
+
* - RFC 7662 — OAuth 2.0 Token Introspection
|
|
12
|
+
* - RFC 6750 §3 — Bearer token `WWW-Authenticate` challenges
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Token-validation strategy.
|
|
16
|
+
*
|
|
17
|
+
* - `jwt` — verify the bearer locally against the AS JWKS (offline, fast,
|
|
18
|
+
* the recommended path). Checks signature, `iss`, `exp`, and that `aud`
|
|
19
|
+
* contains this resource's identifier.
|
|
20
|
+
* - `introspection` — verify the bearer locally as in `jwt` mode, then call
|
|
21
|
+
* the AS RFC 7662 endpoint for the authoritative verdict. Use when the RS
|
|
22
|
+
* must honour near-real-time revocation (or needs the detached PQC
|
|
23
|
+
* signature the AS delivers over introspection). Only a token the AS signed
|
|
24
|
+
* is ever introspected, so an unauthenticated caller cannot spend the RS's
|
|
25
|
+
* introspection rate-limit budget. Requires confidential
|
|
26
|
+
* introspection-client credentials.
|
|
27
|
+
*/
|
|
28
|
+
export type ValidationMode = 'jwt' | 'introspection';
|
|
29
|
+
/**
|
|
30
|
+
* Result of a successful token validation. Claims are normalised to the
|
|
31
|
+
* shapes QAuth emits (see auth-server `signAccessToken` / introspect route).
|
|
32
|
+
*/
|
|
33
|
+
export interface ValidatedToken {
|
|
34
|
+
/** Subject — user UUID (authorization_code) or client id (client_credentials). */
|
|
35
|
+
sub?: string;
|
|
36
|
+
/** OAuth client that the token was issued to (`client_id` claim). */
|
|
37
|
+
clientId?: string;
|
|
38
|
+
/** Granted scopes, already split on whitespace (RFC 8693 `scope`). */
|
|
39
|
+
scopes: string[];
|
|
40
|
+
/** Audience — always the resource identifier(s) this token is bound to. */
|
|
41
|
+
audience: string[];
|
|
42
|
+
/** Issuer (`iss`). */
|
|
43
|
+
issuer?: string;
|
|
44
|
+
/** Expiry, seconds since epoch. */
|
|
45
|
+
expiresAt?: number;
|
|
46
|
+
/** Issued-at, seconds since epoch. */
|
|
47
|
+
issuedAt?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Raw claims as received (JWT payload, or introspection response body).
|
|
50
|
+
* Provided for advanced callers; prefer the normalised fields above.
|
|
51
|
+
*/
|
|
52
|
+
raw: Record<string, unknown>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Confidential client credentials used to authenticate to the AS
|
|
56
|
+
* introspection endpoint (RFC 7662 requires client authentication).
|
|
57
|
+
*/
|
|
58
|
+
export interface IntrospectionClientCredentials {
|
|
59
|
+
clientId: string;
|
|
60
|
+
clientSecret: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Minimal `fetch` shape used by the JWKS and introspection clients. Defaults
|
|
64
|
+
* to the global `fetch`; injectable so tests need no network and deployments
|
|
65
|
+
* can wrap it with an SSRF-guarded / instrumented implementation.
|
|
66
|
+
*/
|
|
67
|
+
export type FetchLike = (input: string | URL, init?: {
|
|
68
|
+
method?: string;
|
|
69
|
+
headers?: Record<string, string>;
|
|
70
|
+
body?: string;
|
|
71
|
+
signal?: AbortSignal;
|
|
72
|
+
}) => Promise<{
|
|
73
|
+
ok: boolean;
|
|
74
|
+
status: number;
|
|
75
|
+
json: () => Promise<unknown>;
|
|
76
|
+
text: () => Promise<string>;
|
|
77
|
+
}>;
|
|
78
|
+
/**
|
|
79
|
+
* Configuration shared by the framework-agnostic core and the Fastify plugin.
|
|
80
|
+
*/
|
|
81
|
+
export interface ResourceGuardConfig {
|
|
82
|
+
/**
|
|
83
|
+
* Identifier of THIS protected resource — the canonical URL clients use to
|
|
84
|
+
* reach it (RFC 9728 `resource`, RFC 8707 resource indicator). Every
|
|
85
|
+
* accepted token MUST carry this value in `aud`; tokens minted for a
|
|
86
|
+
* different audience are rejected (no token passthrough).
|
|
87
|
+
*
|
|
88
|
+
* @example 'https://memory.mcp.example.com'
|
|
89
|
+
*/
|
|
90
|
+
resource: string;
|
|
91
|
+
/**
|
|
92
|
+
* Issuer identifier of the QAuth authorization server. Advertised in PRM
|
|
93
|
+
* as `authorization_servers[]` and used to validate the JWT `iss` claim.
|
|
94
|
+
* Trailing slashes are normalised away.
|
|
95
|
+
*
|
|
96
|
+
* @example 'https://auth.example.com'
|
|
97
|
+
*/
|
|
98
|
+
authorizationServer: string;
|
|
99
|
+
/**
|
|
100
|
+
* Scopes a caller must hold to access the resource by default. An empty
|
|
101
|
+
* array means "any valid, audience-bound token is accepted". Per-operation
|
|
102
|
+
* step-up requirements are expressed at the call site (see `requireScopes`).
|
|
103
|
+
*/
|
|
104
|
+
requiredScopes?: string[];
|
|
105
|
+
/** Token-validation strategy. Defaults to `'jwt'`. */
|
|
106
|
+
validationMode?: ValidationMode;
|
|
107
|
+
/**
|
|
108
|
+
* JWKS URL of the AS. Defaults to `${authorizationServer}/.well-known/jwks.json`,
|
|
109
|
+
* matching the QAuth AS contract. Used in both modes: introspection mode
|
|
110
|
+
* verifies the bearer against the JWKS before introspecting it.
|
|
111
|
+
*/
|
|
112
|
+
jwksUri?: string;
|
|
113
|
+
/**
|
|
114
|
+
* How long (ms) a fetched JWKS may be reused before the next refresh is
|
|
115
|
+
* allowed. Maps to the jose remote-JWKS cooldown. Defaults to 300_000 (5m).
|
|
116
|
+
*/
|
|
117
|
+
jwksCacheTtlMs?: number;
|
|
118
|
+
/**
|
|
119
|
+
* Introspection endpoint URL. Defaults to `${authorizationServer}/oauth/introspect`,
|
|
120
|
+
* matching the QAuth AS contract. Only used in `introspection` mode.
|
|
121
|
+
*/
|
|
122
|
+
introspectionEndpoint?: string;
|
|
123
|
+
/**
|
|
124
|
+
* In `introspection` mode, verify the bearer locally against the JWKS before
|
|
125
|
+
* introspecting it, so a bearer the AS never signed is refused without
|
|
126
|
+
* spending an introspection call (the AS rate-limits introspection per
|
|
127
|
+
* caller). Defaults to `true`. Set `false` only for an AS that issues opaque
|
|
128
|
+
* (non-JWT) access tokens.
|
|
129
|
+
*/
|
|
130
|
+
introspectionLocalCheck?: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Confidential credentials for the introspection endpoint. REQUIRED in
|
|
133
|
+
* `introspection` mode (RFC 7662 mandates client authentication).
|
|
134
|
+
*/
|
|
135
|
+
introspectionClient?: IntrospectionClientCredentials;
|
|
136
|
+
/**
|
|
137
|
+
* Accepted JWT signature algorithms. Defaults to `['EdDSA']` — the only
|
|
138
|
+
* algorithm QAuth signs with (Ed25519). Constrained to defend against
|
|
139
|
+
* algorithm-confusion attacks.
|
|
140
|
+
*/
|
|
141
|
+
allowedAlgorithms?: string[];
|
|
142
|
+
/**
|
|
143
|
+
* Require the RFC 9068 `typ: at+jwt` protected header to be PRESENT on every
|
|
144
|
+
* bearer token (#283). Defaults to `false`.
|
|
145
|
+
*
|
|
146
|
+
* A token whose `typ` is present but WRONG — an OIDC ID token, `typ: JWT`,
|
|
147
|
+
* signed by the same AS key — is rejected regardless of this flag. The flag
|
|
148
|
+
* governs only whether a `typ`-LESS token is tolerated, which matters when
|
|
149
|
+
* the upstream AS predates #283 or is still draining tokens its previous
|
|
150
|
+
* build minted (up to one `ACCESS_TOKEN_LIFESPAN`, default 900s).
|
|
151
|
+
*
|
|
152
|
+
* Set it to `true` once the AS you point at is known to stamp `typ`.
|
|
153
|
+
*/
|
|
154
|
+
requireAccessTokenTyp?: boolean;
|
|
155
|
+
/**
|
|
156
|
+
* Injectable `fetch`. Defaults to the global `fetch`. Override for tests or
|
|
157
|
+
* to apply an SSRF-guarded / instrumented HTTP client.
|
|
158
|
+
*/
|
|
159
|
+
fetch?: FetchLike;
|
|
160
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for `@qauth-labs/resource-guard`.
|
|
3
|
+
*
|
|
4
|
+
* `resource-guard` is the resource-server (RS) side of the QAuth OAuth 2.1 stack.
|
|
5
|
+
* An MCP-server author drops it in to obtain spec-correct OAuth from a
|
|
6
|
+
* self-hosted QAuth authorization server (AS), per the MCP Authorization
|
|
7
|
+
* profile revision 2026-07-28 and the RFCs it references:
|
|
8
|
+
*
|
|
9
|
+
* - RFC 9728 — OAuth 2.0 Protected Resource Metadata (PRM)
|
|
10
|
+
* - RFC 8707 — Resource Indicators (audience binding; no token passthrough)
|
|
11
|
+
* - RFC 7662 — OAuth 2.0 Token Introspection
|
|
12
|
+
* - RFC 6750 §3 — Bearer token `WWW-Authenticate` challenges
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,17 +1,69 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qauth-labs/resource-guard",
|
|
3
|
-
"version": "0.0.0",
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
"version": "0.2.0-rc.0",
|
|
4
|
+
"description": "Resource-server SDK for QAuth: RFC 9728 protected-resource metadata, RFC 6750 challenges and audience-bound bearer-token validation for MCP servers and other OAuth 2.1 resource servers.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"oauth",
|
|
7
|
+
"oauth2.1",
|
|
8
|
+
"mcp",
|
|
9
|
+
"resource-server",
|
|
10
|
+
"rfc9728",
|
|
11
|
+
"bearer",
|
|
12
|
+
"jwt",
|
|
13
|
+
"qauth"
|
|
14
|
+
],
|
|
11
15
|
"license": "Apache-2.0",
|
|
12
|
-
"
|
|
16
|
+
"homepage": "https://docs.qauth.dev/integrate/mcp-quickstart/",
|
|
13
17
|
"repository": {
|
|
14
18
|
"type": "git",
|
|
15
|
-
"url": "git+https://github.com/qauth-labs/qauth.git"
|
|
16
|
-
|
|
17
|
-
}
|
|
19
|
+
"url": "git+https://github.com/qauth-labs/qauth.git",
|
|
20
|
+
"directory": "libs/sdk/resource-guard"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/qauth-labs/qauth/issues"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=22"
|
|
29
|
+
},
|
|
30
|
+
"main": "./dist/index.js",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./fastify": {
|
|
37
|
+
"types": "./dist/fastify.d.ts",
|
|
38
|
+
"import": "./dist/fastify.js"
|
|
39
|
+
},
|
|
40
|
+
"./package.json": "./package.json"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE"
|
|
46
|
+
],
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"fastify-plugin": "^6.0.1",
|
|
52
|
+
"jose": "^6.2.12"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"fastify": "^5.12.7"
|
|
56
|
+
},
|
|
57
|
+
"peerDependenciesMeta": {
|
|
58
|
+
"fastify": {
|
|
59
|
+
"optional": true
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@vitest/coverage-v8": "^5.0.5",
|
|
64
|
+
"@vitest/ui": "^5.0.5",
|
|
65
|
+
"fastify": "^5.12.7",
|
|
66
|
+
"vitest": "^5.0.5"
|
|
67
|
+
},
|
|
68
|
+
"types": "./dist/index.d.ts"
|
|
69
|
+
}
|