@dereekb/oauth-resource 14.4.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 +21 -0
- package/README.md +185 -0
- package/express/index.d.ts +1 -0
- package/express/index.esm.js +321 -0
- package/express/package.json +29 -0
- package/express/src/index.d.ts +1 -0
- package/express/src/lib/bearer.middleware.d.ts +78 -0
- package/express/src/lib/index.d.ts +2 -0
- package/express/src/lib/well-known.router.d.ts +35 -0
- package/firebase/index.d.ts +1 -0
- package/firebase/index.esm.js +1924 -0
- package/firebase/package.json +27 -0
- package/firebase/src/index.d.ts +1 -0
- package/firebase/src/lib/firestore/firestore.sdk-identity.d.ts +165 -0
- package/firebase/src/lib/firestore/index.d.ts +1 -0
- package/firebase/src/lib/index.d.ts +2 -0
- package/firebase/src/lib/session/firebase-client.config.d.ts +86 -0
- package/firebase/src/lib/session/firebase-user-session.d.ts +223 -0
- package/firebase/src/lib/session/firebase-user-session.pool.d.ts +168 -0
- package/firebase/src/lib/session/firestore-session.cache.d.ts +79 -0
- package/firebase/src/lib/session/firestore-session.client.d.ts +149 -0
- package/firebase/src/lib/session/index.d.ts +5 -0
- package/index.d.ts +1 -0
- package/index.esm.js +1066 -0
- package/package.json +53 -0
- package/src/index.d.ts +1 -0
- package/src/lib/auth/index.d.ts +1 -0
- package/src/lib/auth/oauth.resource.auth.d.ts +55 -0
- package/src/lib/challenge/bearer.challenge.d.ts +73 -0
- package/src/lib/challenge/index.d.ts +1 -0
- package/src/lib/error/index.d.ts +1 -0
- package/src/lib/error/oauth.resource.error.d.ts +76 -0
- package/src/lib/index.d.ts +6 -0
- package/src/lib/issuer/index.d.ts +1 -0
- package/src/lib/issuer/issuer.profile.d.ts +98 -0
- package/src/lib/metadata/index.d.ts +1 -0
- package/src/lib/metadata/protected-resource.metadata.d.ts +64 -0
- package/src/lib/verify/index.d.ts +1 -0
- package/src/lib/verify/verify.bearer.d.ts +95 -0
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dereekb/oauth-resource",
|
|
3
|
+
"version": "14.4.0",
|
|
4
|
+
"sideEffects": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./express": {
|
|
8
|
+
"types": "./express/index.d.ts",
|
|
9
|
+
"import": "./express/index.esm.js",
|
|
10
|
+
"default": "./express/index.esm.js"
|
|
11
|
+
},
|
|
12
|
+
"./firebase": {
|
|
13
|
+
"types": "./firebase/index.d.ts",
|
|
14
|
+
"import": "./firebase/index.esm.js",
|
|
15
|
+
"default": "./firebase/index.esm.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json",
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./index.d.ts",
|
|
20
|
+
"import": "./index.esm.js",
|
|
21
|
+
"default": "./index.esm.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@dereekb/firebase": "14.4.0",
|
|
26
|
+
"@dereekb/util": "14.4.0",
|
|
27
|
+
"cors": "^2.8.6",
|
|
28
|
+
"express": "^5.2.1",
|
|
29
|
+
"firebase": "^12.18.0",
|
|
30
|
+
"jose": "^6.2.12",
|
|
31
|
+
"make-error": "^1.3.6"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"supertest": "^7.2.2"
|
|
35
|
+
},
|
|
36
|
+
"peerDependenciesMeta": {
|
|
37
|
+
"@dereekb/firebase": {
|
|
38
|
+
"optional": true
|
|
39
|
+
},
|
|
40
|
+
"cors": {
|
|
41
|
+
"optional": true
|
|
42
|
+
},
|
|
43
|
+
"express": {
|
|
44
|
+
"optional": true
|
|
45
|
+
},
|
|
46
|
+
"firebase": {
|
|
47
|
+
"optional": true
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"module": "./index.esm.js",
|
|
51
|
+
"main": "./index.esm.js",
|
|
52
|
+
"types": "./index.d.ts"
|
|
53
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './oauth.resource.auth';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type UnixDateTimeSecondsNumber } from '@dereekb/util';
|
|
2
|
+
import { type VerifiedBearer } from '../verify/verify.bearer';
|
|
3
|
+
/**
|
|
4
|
+
* The verified caller attached to a request by a resource server's bearer middleware.
|
|
5
|
+
*
|
|
6
|
+
* Structurally identical to the MCP TypeScript SDK's `AuthInfo` (`@modelcontextprotocol/server`),
|
|
7
|
+
* so an MCP host can assign one to `req.auth` and the SDK's `toNodeHandler` forwards it to the
|
|
8
|
+
* handler as `authInfo` with no cast. The type is declared locally on purpose: taking an
|
|
9
|
+
* SDK peer dependency just to borrow a five-field interface would pull the whole MCP install
|
|
10
|
+
* graph into a service that only needs to check a signature.
|
|
11
|
+
*/
|
|
12
|
+
export interface OAuthResourceAuthInfo {
|
|
13
|
+
/**
|
|
14
|
+
* The raw bearer token as presented.
|
|
15
|
+
*/
|
|
16
|
+
readonly token: string;
|
|
17
|
+
/**
|
|
18
|
+
* The OAuth client the token was issued to (`client_id`), or a stand-in when the token carries
|
|
19
|
+
* no client (see {@link oauthResourceAuthInfoForVerifiedBearer}).
|
|
20
|
+
*/
|
|
21
|
+
readonly clientId: string;
|
|
22
|
+
/**
|
|
23
|
+
* Scopes granted on the token, split out of the space-delimited `scope` claim.
|
|
24
|
+
*/
|
|
25
|
+
readonly scopes: string[];
|
|
26
|
+
/**
|
|
27
|
+
* Token expiry, as unix epoch seconds (the `exp` claim).
|
|
28
|
+
*/
|
|
29
|
+
readonly expiresAt?: UnixDateTimeSecondsNumber;
|
|
30
|
+
/**
|
|
31
|
+
* Anything else the resource server wants to carry through to its handlers.
|
|
32
|
+
*/
|
|
33
|
+
readonly extra?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Fallback `clientId` used for a verified Firebase ID token, which names no OAuth client.
|
|
37
|
+
*/
|
|
38
|
+
export declare const FIREBASE_AUTH_INFO_CLIENT_ID = "firebase";
|
|
39
|
+
/**
|
|
40
|
+
* Splits a space-delimited OAuth `scope` claim into its entries.
|
|
41
|
+
*
|
|
42
|
+
* @param scope - The raw `scope` claim value, if any.
|
|
43
|
+
* @returns The scope entries, empty when the claim is absent or not a string.
|
|
44
|
+
*/
|
|
45
|
+
export declare function scopesFromScopeClaim(scope: unknown): string[];
|
|
46
|
+
/**
|
|
47
|
+
* Converts a {@link VerifiedBearer} into the {@link OAuthResourceAuthInfo} attached to the request.
|
|
48
|
+
*
|
|
49
|
+
* A token with no `client_id` claim is not from an OAuth client: a Firebase ID token reports
|
|
50
|
+
* {@link FIREBASE_AUTH_INFO_CLIENT_ID}, and any other issuer falls back to the subject.
|
|
51
|
+
*
|
|
52
|
+
* @param verified - The verified bearer token.
|
|
53
|
+
* @returns The auth info.
|
|
54
|
+
*/
|
|
55
|
+
export declare function oauthResourceAuthInfoForVerifiedBearer(verified: VerifiedBearer): OAuthResourceAuthInfo;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type Maybe } from '@dereekb/util';
|
|
2
|
+
import { type OAuthResourceErrorCode } from '../error/oauth.resource.error';
|
|
3
|
+
/**
|
|
4
|
+
* The RFC 6750 §3 `error` codes a resource server emits on a `WWW-Authenticate: Bearer` challenge.
|
|
5
|
+
*/
|
|
6
|
+
export type BearerChallengeErrorCode = 'invalid_request' | 'invalid_token' | 'insufficient_scope';
|
|
7
|
+
export interface BuildBearerChallengeConfig {
|
|
8
|
+
/**
|
|
9
|
+
* The RFC 6750 `error` token. Any string is accepted so a consumer can emit a code this union
|
|
10
|
+
* does not enumerate.
|
|
11
|
+
*/
|
|
12
|
+
readonly error: BearerChallengeErrorCode | string;
|
|
13
|
+
/**
|
|
14
|
+
* Protection space name. Omitted by default — a resource server that serves exactly one
|
|
15
|
+
* protection space does not need it.
|
|
16
|
+
*/
|
|
17
|
+
readonly realm?: Maybe<string>;
|
|
18
|
+
/**
|
|
19
|
+
* Absolute URL of the RFC 9728 protected-resource metadata document, so a client can discover
|
|
20
|
+
* the authorization server from the challenge alone rather than relying on origin-rooted
|
|
21
|
+
* path-walkback (which 404s whenever the resource is not mounted at the origin root).
|
|
22
|
+
*/
|
|
23
|
+
readonly resourceMetadataUrl?: Maybe<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Human-readable explanation (`error_description`).
|
|
26
|
+
*/
|
|
27
|
+
readonly errorDescription?: Maybe<string>;
|
|
28
|
+
/**
|
|
29
|
+
* Space-delimited scopes required for the request, meaningful with `insufficient_scope`.
|
|
30
|
+
*/
|
|
31
|
+
readonly scope?: Maybe<string>;
|
|
32
|
+
}
|
|
33
|
+
export interface BearerChallengeErrorCodeConfig {
|
|
34
|
+
/**
|
|
35
|
+
* The failure's code.
|
|
36
|
+
*/
|
|
37
|
+
readonly code: OAuthResourceErrorCode;
|
|
38
|
+
/**
|
|
39
|
+
* Whether the request presented a bearer token at all.
|
|
40
|
+
*/
|
|
41
|
+
readonly hadToken: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Builds the `WWW-Authenticate: Bearer ...` challenge string emitted alongside a 401 / 403 on an
|
|
45
|
+
* OAuth-protected route.
|
|
46
|
+
*
|
|
47
|
+
* Per RFC 6750 §3 / RFC 7235, auth-params are comma-separated. Parameter order is not significant
|
|
48
|
+
* to a client; this emits `realm`, `resource_metadata`, `error`, `error_description`, `scope`.
|
|
49
|
+
*
|
|
50
|
+
* @param config - The error token plus the optional realm / metadata / description / scope params.
|
|
51
|
+
* @returns The header value, e.g. `Bearer resource_metadata="…", error="invalid_token"`.
|
|
52
|
+
*/
|
|
53
|
+
export declare function buildBearerChallenge(config: BuildBearerChallengeConfig): string;
|
|
54
|
+
/**
|
|
55
|
+
* Selects the RFC 6750 §3 `error` token for a failure: `insufficient_scope` for a policy (403)
|
|
56
|
+
* failure, `invalid_token` when a token was presented but failed, and `invalid_request` when the
|
|
57
|
+
* request carried no token at all.
|
|
58
|
+
*
|
|
59
|
+
* @param config - The failure code and whether a token was presented.
|
|
60
|
+
* @returns The RFC 6750 error token.
|
|
61
|
+
*/
|
|
62
|
+
export declare function bearerChallengeErrorForCode(config: BearerChallengeErrorCodeConfig): BearerChallengeErrorCode;
|
|
63
|
+
/**
|
|
64
|
+
* The `Authorization` header scheme prefix a bearer token is presented with.
|
|
65
|
+
*/
|
|
66
|
+
export declare const BEARER_AUTHORIZATION_PREFIX = "Bearer ";
|
|
67
|
+
/**
|
|
68
|
+
* Reads the raw bearer token out of an `Authorization` header value.
|
|
69
|
+
*
|
|
70
|
+
* @param header - The raw `Authorization` header value, if any.
|
|
71
|
+
* @returns The token, or `undefined` when the header is absent, uses another scheme, or is empty.
|
|
72
|
+
*/
|
|
73
|
+
export declare function readBearerToken(header: Maybe<string>): string | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './bearer.challenge';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './oauth.resource.error';
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type CodedError, type Maybe } from '@dereekb/util';
|
|
2
|
+
import { BaseError } from 'make-error';
|
|
3
|
+
/**
|
|
4
|
+
* Stable error code raised by the resource-server verification layer.
|
|
5
|
+
*
|
|
6
|
+
* `unauthorized` maps to a 401 (the token is missing / malformed / untrusted / invalid) and
|
|
7
|
+
* `forbidden` to a 403 (the token verified but failed a policy gate). The distinction is what
|
|
8
|
+
* selects the RFC 6750 challenge's `error` token — see {@link bearerChallengeErrorForCode}.
|
|
9
|
+
*/
|
|
10
|
+
export type OAuthResourceErrorCode = 'unauthorized' | 'forbidden';
|
|
11
|
+
/**
|
|
12
|
+
* HTTP status paired with each {@link OAuthResourceErrorCode}.
|
|
13
|
+
*/
|
|
14
|
+
export declare const OAUTH_RESOURCE_ERROR_STATUS_CODES: Readonly<Record<OAuthResourceErrorCode, number>>;
|
|
15
|
+
export interface OAuthResourceErrorInput {
|
|
16
|
+
readonly code: OAuthResourceErrorCode;
|
|
17
|
+
readonly message: string;
|
|
18
|
+
/**
|
|
19
|
+
* HTTP status. Defaults to the status paired with `code` in {@link OAUTH_RESOURCE_ERROR_STATUS_CODES}.
|
|
20
|
+
*/
|
|
21
|
+
readonly status?: Maybe<number>;
|
|
22
|
+
/**
|
|
23
|
+
* Extra machine-readable context carried onto the error envelope.
|
|
24
|
+
*/
|
|
25
|
+
readonly details?: Maybe<Record<string, unknown>>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The default JSON body emitted for a rejected request.
|
|
29
|
+
*/
|
|
30
|
+
export interface OAuthResourceErrorEnvelope {
|
|
31
|
+
readonly error: {
|
|
32
|
+
readonly code: OAuthResourceErrorCode;
|
|
33
|
+
readonly message: string;
|
|
34
|
+
readonly details?: Record<string, unknown>;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Error raised when a bearer token fails verification or a policy gate.
|
|
39
|
+
*
|
|
40
|
+
* Consumers that already have their own API error type do not have to catch and re-wrap this one:
|
|
41
|
+
* pass an {@link OAuthResourceErrorFactory} on the verification options and the verifier throws
|
|
42
|
+
* that type instead.
|
|
43
|
+
*/
|
|
44
|
+
export declare class OAuthResourceError extends BaseError implements CodedError {
|
|
45
|
+
readonly code: OAuthResourceErrorCode;
|
|
46
|
+
readonly status: number;
|
|
47
|
+
readonly details?: Record<string, unknown>;
|
|
48
|
+
constructor(input: OAuthResourceErrorInput);
|
|
49
|
+
/**
|
|
50
|
+
* Builds the default JSON error body for this error.
|
|
51
|
+
*
|
|
52
|
+
* @returns The error envelope.
|
|
53
|
+
*/
|
|
54
|
+
toEnvelope(): OAuthResourceErrorEnvelope;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Creates the error thrown when verification fails.
|
|
58
|
+
*
|
|
59
|
+
* The seam that keeps this package error-type agnostic: supply one to map every rejection onto an
|
|
60
|
+
* app's own error type (and therefore its own response envelope).
|
|
61
|
+
*/
|
|
62
|
+
export type OAuthResourceErrorFactory = (input: OAuthResourceErrorInput) => Error;
|
|
63
|
+
/**
|
|
64
|
+
* Default {@link OAuthResourceErrorFactory}, producing an {@link OAuthResourceError}.
|
|
65
|
+
*
|
|
66
|
+
* @param input - The code, message, status, and details of the failure.
|
|
67
|
+
* @returns The error to throw.
|
|
68
|
+
*/
|
|
69
|
+
export declare const defaultOAuthResourceErrorFactory: OAuthResourceErrorFactory;
|
|
70
|
+
/**
|
|
71
|
+
* Returns true when the input is an {@link OAuthResourceError}.
|
|
72
|
+
*
|
|
73
|
+
* @param error - The value to test.
|
|
74
|
+
* @returns Whether the value is an {@link OAuthResourceError}.
|
|
75
|
+
*/
|
|
76
|
+
export declare function isOAuthResourceError(error: unknown): error is OAuthResourceError;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './issuer.profile';
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { type Maybe } from '@dereekb/util';
|
|
2
|
+
import { type JWTVerifyGetKey } from 'jose';
|
|
3
|
+
/**
|
|
4
|
+
* Which family a trusted issuer belongs to.
|
|
5
|
+
*
|
|
6
|
+
* `firebase` is a Firebase Auth ID token issuer (`https://securetoken.google.com/<projectId>`);
|
|
7
|
+
* `oidc` is any OAuth 2.0 / OpenID Connect authorization server — a dbx-components
|
|
8
|
+
* `@dereekb/firebase-server/oidc` provider, Auth0, Okta, and so on.
|
|
9
|
+
*/
|
|
10
|
+
export type IssuerProfileKind = 'firebase' | 'oidc';
|
|
11
|
+
/**
|
|
12
|
+
* One trusted token issuer: how to recognize its tokens (`iss`), which
|
|
13
|
+
* audiences it may name, and how to fetch its verification keys.
|
|
14
|
+
*/
|
|
15
|
+
export interface IssuerProfile {
|
|
16
|
+
readonly kind: IssuerProfileKind;
|
|
17
|
+
readonly issuer: string;
|
|
18
|
+
readonly audiences: readonly string[];
|
|
19
|
+
/**
|
|
20
|
+
* Resolves the JWKS key getter. Lazy so a discovery failure at boot does
|
|
21
|
+
* not take the service down — it surfaces as a 401 on the request instead.
|
|
22
|
+
*/
|
|
23
|
+
readonly getKey: () => Promise<JWTVerifyGetKey>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Trusted issuer profiles, keyed by their `iss` value. The `iss` claim on an incoming token
|
|
27
|
+
* selects the profile that verifies it.
|
|
28
|
+
*/
|
|
29
|
+
export type IssuerProfiles = ReadonlyMap<string, IssuerProfile>;
|
|
30
|
+
/**
|
|
31
|
+
* Google's public JWKS for Firebase ID tokens (the `securetoken` signer).
|
|
32
|
+
*/
|
|
33
|
+
export declare const FIREBASE_SECURETOKEN_JWKS_URL = "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com";
|
|
34
|
+
/**
|
|
35
|
+
* Path appended to an issuer to read its OpenID Connect discovery document.
|
|
36
|
+
*/
|
|
37
|
+
export declare const OPENID_CONFIGURATION_PATH = "/.well-known/openid-configuration";
|
|
38
|
+
/**
|
|
39
|
+
* Builds the `iss` value Firebase stamps on a project's ID tokens.
|
|
40
|
+
*
|
|
41
|
+
* @param projectId - The Firebase project id.
|
|
42
|
+
* @returns The issuer URL.
|
|
43
|
+
*/
|
|
44
|
+
export declare function firebaseIssuerForProject(projectId: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* An OIDC issuer to trust. The string form takes the shared `audiences` and discovers its JWKS;
|
|
47
|
+
* the object form overrides either.
|
|
48
|
+
*/
|
|
49
|
+
export type BuildIssuerProfilesOidcIssuerInput = string | OidcIssuerProfileConfig;
|
|
50
|
+
export interface OidcIssuerProfileConfig {
|
|
51
|
+
readonly issuer: string;
|
|
52
|
+
/**
|
|
53
|
+
* Audiences accepted on this issuer's tokens. Defaults to {@link BuildIssuerProfilesConfig.audiences}.
|
|
54
|
+
*/
|
|
55
|
+
readonly audiences?: Maybe<readonly string[]>;
|
|
56
|
+
/**
|
|
57
|
+
* Overrides how this issuer's verification keys are resolved, bypassing discovery.
|
|
58
|
+
*
|
|
59
|
+
* This is the seam the whole design rests on: a test injects `createLocalJWKSet()`, and an
|
|
60
|
+
* authorization server verifying its OWN tokens in-process injects its local key set rather
|
|
61
|
+
* than making an HTTP call back to itself.
|
|
62
|
+
*/
|
|
63
|
+
readonly getKey?: Maybe<() => Promise<JWTVerifyGetKey>>;
|
|
64
|
+
}
|
|
65
|
+
export interface BuildIssuerProfilesConfig {
|
|
66
|
+
/**
|
|
67
|
+
* Firebase project ids whose ID tokens are trusted. Each contributes a `firebase` profile whose
|
|
68
|
+
* only accepted audience is the project id itself.
|
|
69
|
+
*/
|
|
70
|
+
readonly firebaseProjectIds?: Maybe<readonly string[]>;
|
|
71
|
+
/**
|
|
72
|
+
* OAuth/OIDC issuers to trust.
|
|
73
|
+
*/
|
|
74
|
+
readonly oidcIssuers?: Maybe<readonly BuildIssuerProfilesOidcIssuerInput[]>;
|
|
75
|
+
/**
|
|
76
|
+
* Audiences accepted on OIDC tokens — this resource server's identity. Typically its origin
|
|
77
|
+
* plus any RFC 8707 resource-indicator URLs pointed at it.
|
|
78
|
+
*/
|
|
79
|
+
readonly audiences?: Maybe<readonly string[]>;
|
|
80
|
+
/**
|
|
81
|
+
* JWKS URL used for the Firebase leg. Defaults to {@link FIREBASE_SECURETOKEN_JWKS_URL}.
|
|
82
|
+
*/
|
|
83
|
+
readonly firebaseJwksUrl?: Maybe<string>;
|
|
84
|
+
/**
|
|
85
|
+
* `fetch` used for OIDC discovery; injected in specs. Defaults to the global.
|
|
86
|
+
*/
|
|
87
|
+
readonly fetch?: Maybe<typeof fetch>;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Builds the issuer → profile map the bearer verifier dispatches on: one
|
|
91
|
+
* Firebase profile per trusted project id (sharing Google's JWKS) and one
|
|
92
|
+
* OIDC profile per trusted issuer (JWKS discovered from
|
|
93
|
+
* `{iss}/.well-known/openid-configuration`, falling back to `{iss}/jwks`).
|
|
94
|
+
*
|
|
95
|
+
* @param config - The trusted project ids / issuers / audiences + optional fetch.
|
|
96
|
+
* @returns Profiles keyed by their `iss` value.
|
|
97
|
+
*/
|
|
98
|
+
export declare function buildIssuerProfiles(config: BuildIssuerProfilesConfig): IssuerProfiles;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './protected-resource.metadata';
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type Maybe } from '@dereekb/util';
|
|
2
|
+
/**
|
|
3
|
+
* RFC 9728 §3 well-known path for OAuth 2.0 protected-resource metadata.
|
|
4
|
+
*/
|
|
5
|
+
export declare const OAUTH_PROTECTED_RESOURCE_PATH = "/.well-known/oauth-protected-resource";
|
|
6
|
+
/**
|
|
7
|
+
* Builds the RFC 9728 §3.1 path-suffixed well-known path for a resource — the form MCP clients
|
|
8
|
+
* try first (e.g. `/.well-known/oauth-protected-resource/mcp` for a resource served at `/mcp`).
|
|
9
|
+
*
|
|
10
|
+
* @param resourcePath - The resource's path on the origin, with or without a leading slash.
|
|
11
|
+
* @returns The path-suffixed well-known path.
|
|
12
|
+
*/
|
|
13
|
+
export declare function oauthProtectedResourcePathForResource(resourcePath: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* RFC 9728 protected-resource metadata document.
|
|
16
|
+
*
|
|
17
|
+
* Snake-cased because it is the wire document, served verbatim.
|
|
18
|
+
*/
|
|
19
|
+
export interface OAuthProtectedResourceMetadata {
|
|
20
|
+
readonly resource: string;
|
|
21
|
+
readonly authorization_servers: readonly string[];
|
|
22
|
+
readonly scopes_supported: readonly string[];
|
|
23
|
+
readonly bearer_methods_supported: readonly string[];
|
|
24
|
+
readonly resource_documentation?: string;
|
|
25
|
+
readonly resource_name?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface BuildProtectedResourceMetadataConfig {
|
|
28
|
+
/**
|
|
29
|
+
* The resource identifier clients pass as the RFC 8707 `resource` parameter, e.g.
|
|
30
|
+
* `https://db.example.com/mcp`.
|
|
31
|
+
*/
|
|
32
|
+
readonly resource: string;
|
|
33
|
+
/**
|
|
34
|
+
* Issuers of the authorization servers that may issue tokens for this resource.
|
|
35
|
+
*/
|
|
36
|
+
readonly authorizationServers: readonly string[];
|
|
37
|
+
/**
|
|
38
|
+
* Scopes this resource understands, advertised to clients so they can request them up-front.
|
|
39
|
+
*/
|
|
40
|
+
readonly scopesSupported?: Maybe<readonly string[]>;
|
|
41
|
+
/**
|
|
42
|
+
* How a bearer token may be presented. Defaults to `['header']` — the only method this package's
|
|
43
|
+
* middleware reads.
|
|
44
|
+
*/
|
|
45
|
+
readonly bearerMethodsSupported?: Maybe<readonly string[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Human-readable name of the resource.
|
|
48
|
+
*/
|
|
49
|
+
readonly resourceName?: Maybe<string>;
|
|
50
|
+
/**
|
|
51
|
+
* URL of human-readable documentation for the resource.
|
|
52
|
+
*/
|
|
53
|
+
readonly resourceDocumentation?: Maybe<string>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Builds the RFC 9728 protected-resource metadata document.
|
|
57
|
+
*
|
|
58
|
+
* Hand-rolled deliberately: the MCP SDK's builder also wants the authorization-server metadata
|
|
59
|
+
* document, which a resource server never hosts — it points at one instead.
|
|
60
|
+
*
|
|
61
|
+
* @param config - The resource identifier, its authorization servers, and the advertised scopes.
|
|
62
|
+
* @returns The metadata document.
|
|
63
|
+
*/
|
|
64
|
+
export declare function buildProtectedResourceMetadata(config: BuildProtectedResourceMetadataConfig): OAuthProtectedResourceMetadata;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './verify.bearer';
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { type Maybe, type PromiseOrValue, type Seconds } from '@dereekb/util';
|
|
2
|
+
import { type JWTPayload } from 'jose';
|
|
3
|
+
import { type OAuthResourceErrorFactory } from '../error/oauth.resource.error';
|
|
4
|
+
import { type IssuerProfile, type IssuerProfileKind, type IssuerProfiles } from '../issuer/issuer.profile';
|
|
5
|
+
/**
|
|
6
|
+
* A verified bearer token: which issuer profile matched, the subject, and
|
|
7
|
+
* the full claim set.
|
|
8
|
+
*/
|
|
9
|
+
export interface VerifiedBearer {
|
|
10
|
+
readonly kind: IssuerProfileKind;
|
|
11
|
+
readonly issuer: string;
|
|
12
|
+
readonly subject: string;
|
|
13
|
+
readonly claims: JWTPayload;
|
|
14
|
+
readonly token: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Additional policy applied to a token that already passed signature + standard claim validation.
|
|
18
|
+
*
|
|
19
|
+
* Returning `false` rejects the token as `forbidden` (403 / `insufficient_scope`), not
|
|
20
|
+
* `unauthorized` — the token is valid, the caller is simply not allowed.
|
|
21
|
+
*/
|
|
22
|
+
export type VerifyBearerClaimPredicate = (claims: JWTPayload, profile: IssuerProfile) => PromiseOrValue<boolean>;
|
|
23
|
+
export interface VerifyBearerOptions {
|
|
24
|
+
readonly profiles: IssuerProfiles;
|
|
25
|
+
/**
|
|
26
|
+
* Accept unsigned (`alg: none`) Firebase emulator ID tokens. Never set this in production —
|
|
27
|
+
* an unsigned token proves nothing.
|
|
28
|
+
*/
|
|
29
|
+
readonly firebaseEmulator?: Maybe<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* Claims that must be present and truthy on the token, e.g. an app's onboarded flag. A missing
|
|
32
|
+
* claim rejects with `forbidden`.
|
|
33
|
+
*/
|
|
34
|
+
readonly requiredClaims?: Maybe<readonly string[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Arbitrary additional policy gate, evaluated after {@link requiredClaims}.
|
|
37
|
+
*/
|
|
38
|
+
readonly claimPredicate?: Maybe<VerifyBearerClaimPredicate>;
|
|
39
|
+
/**
|
|
40
|
+
* Which issuer kinds {@link requiredClaims} / {@link claimPredicate} apply to.
|
|
41
|
+
*
|
|
42
|
+
* Defaults to {@link DEFAULT_BEARER_CLAIM_GATE_KINDS} (`['firebase']`): app-account claims live
|
|
43
|
+
* on a Firebase ID token, while an OAuth access token from the authorization server carries
|
|
44
|
+
* scopes instead and is gated by scope, not by account claims.
|
|
45
|
+
*/
|
|
46
|
+
readonly claimGateKinds?: Maybe<readonly IssuerProfileKind[]>;
|
|
47
|
+
/**
|
|
48
|
+
* Signing algorithms accepted. Defaults to {@link BEARER_ALGORITHMS}.
|
|
49
|
+
*/
|
|
50
|
+
readonly algorithms?: Maybe<readonly string[]>;
|
|
51
|
+
/**
|
|
52
|
+
* Leeway applied to `exp` / `nbf` / `iat` / `auth_time`. Defaults to
|
|
53
|
+
* {@link BEARER_CLOCK_TOLERANCE_SECONDS}.
|
|
54
|
+
*/
|
|
55
|
+
readonly clockToleranceSeconds?: Maybe<Seconds>;
|
|
56
|
+
/**
|
|
57
|
+
* Builds the thrown error, letting a consumer surface its own API error type.
|
|
58
|
+
* Defaults to {@link defaultOAuthResourceErrorFactory}.
|
|
59
|
+
*/
|
|
60
|
+
readonly errorFactory?: Maybe<OAuthResourceErrorFactory>;
|
|
61
|
+
/**
|
|
62
|
+
* Clock source in seconds; injected in specs.
|
|
63
|
+
*/
|
|
64
|
+
readonly nowSeconds?: Maybe<() => number>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Leeway applied to `exp` / `nbf` / `iat` / `auth_time`.
|
|
68
|
+
*/
|
|
69
|
+
export declare const BEARER_CLOCK_TOLERANCE_SECONDS: Seconds;
|
|
70
|
+
/**
|
|
71
|
+
* The signing algorithms accepted by default (Firebase and oidc-provider both sign RS256).
|
|
72
|
+
*/
|
|
73
|
+
export declare const BEARER_ALGORITHMS: readonly string[];
|
|
74
|
+
/**
|
|
75
|
+
* Issuer kinds the claim gates apply to by default.
|
|
76
|
+
*/
|
|
77
|
+
export declare const DEFAULT_BEARER_CLAIM_GATE_KINDS: readonly IssuerProfileKind[];
|
|
78
|
+
/**
|
|
79
|
+
* Verifies a bearer JWT against the trusted issuer profiles: the `iss`
|
|
80
|
+
* claim selects the profile, the profile's JWKS verifies the RS256
|
|
81
|
+
* signature, and jose enforces `iss` / `aud` / `exp` / `nbf` / `iat` with a
|
|
82
|
+
* 60 s tolerance. Firebase tokens additionally get the `auth_time` sanity
|
|
83
|
+
* check and the optional claim gates. Under the Firebase emulator an
|
|
84
|
+
* unsigned (`alg: none`) token is accepted after the same claim checks —
|
|
85
|
+
* the emulator never signs, and the flag is never set in production.
|
|
86
|
+
*
|
|
87
|
+
* Scopes are deliberately NOT enforced here: a resource server's scope policy is per-route, so it
|
|
88
|
+
* belongs to the caller (see the Express adapter's `requiredScopes`).
|
|
89
|
+
*
|
|
90
|
+
* @param token - The raw bearer token.
|
|
91
|
+
* @param options - Trusted profiles + policy.
|
|
92
|
+
* @returns The verified token.
|
|
93
|
+
* @throws {OAuthResourceError} `unauthorized` for any malformed / untrusted / invalid token; `forbidden` when a claim gate fails. Replaceable via {@link VerifyBearerOptions.errorFactory}.
|
|
94
|
+
*/
|
|
95
|
+
export declare function verifyBearerJwt(token: string, options: VerifyBearerOptions): Promise<VerifiedBearer>;
|