@dotdo/oauth 0.1.6 → 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/dist/index.d.ts CHANGED
@@ -45,8 +45,15 @@ export { createTestHelpers, generateLoginFormHtml } from './dev.js';
45
45
  export type { DevModeConfig, DevUser, TestHelpers } from './dev.js';
46
46
  export { MemoryOAuthStorage } from './storage.js';
47
47
  export type { OAuthStorage, ListOptions } from './storage.js';
48
+ export { DOSQLiteStorage } from './storage-do.js';
49
+ export type { SqlStorage, SqlStorageResult, OAuthUserWithStripe, SerializedSigningKeyRow } from './storage-do.js';
50
+ export { CollectionsOAuthStorage } from './storage-collections.js';
48
51
  export { generateCodeVerifier, generateCodeChallenge, verifyCodeChallenge, generatePkce, generateState, generateToken, generateAuthorizationCode, hashClientSecret, verifyClientSecret, base64UrlEncode, base64UrlDecode, constantTimeEqual, } from './pkce.js';
49
52
  export { verifyJWT, decodeJWT, isJWTExpired, clearJWKSCache } from './jwt.js';
50
53
  export type { JWTVerifyResult, JWTVerifyOptions, JWTHeader, JWTPayload } from './jwt.js';
54
+ export { SigningKeyManager, generateSigningKey, serializeSigningKey, deserializeSigningKey, exportPublicKeyToJWKS, exportKeysToJWKS, signAccessToken, } from './jwt-signing.js';
55
+ export type { SigningKey, SerializedSigningKey, JWKSPublicKey, JWKS, AccessTokenClaims, } from './jwt-signing.js';
56
+ export { ensureStripeCustomer, getStripeCustomer, linkStripeCustomer, handleStripeWebhook, verifyStripeWebhook, verifyStripeWebhookAsync, createStripeClient, } from './stripe.js';
57
+ export type { StripeCustomer, StripeSubscription, StripeWebhookEventType, StripeWebhookEvent, StripeStorage, StripeClient, OAuthUserWithStripe as StripeUser, } from './stripe.js';
51
58
  export type { OAuthUser, OAuthOrganization, OAuthClient, OAuthAuthorizationCode, OAuthAccessToken, OAuthRefreshToken, OAuthGrant, OAuthServerMetadata, OAuthResourceMetadata, TokenResponse, OAuthError, UpstreamOAuthConfig, } from './types.js';
52
59
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAGH,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAGrE,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AACnE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAGnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjD,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAG7D,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,aAAa,EACb,yBAAyB,EACzB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,iBAAiB,GAClB,MAAM,WAAW,CAAA;AAGlB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC7E,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAGxF,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,WAAW,EACX,sBAAsB,EACtB,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,EACV,mBAAmB,EACnB,qBAAqB,EACrB,aAAa,EACb,UAAU,EACV,mBAAmB,GACpB,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAGH,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAGrE,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AACnE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAGnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjD,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAG7D,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAA;AAGjH,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAGlE,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,aAAa,EACb,yBAAyB,EACzB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,iBAAiB,GAClB,MAAM,WAAW,CAAA;AAGlB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC7E,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAGxF,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,EAChB,eAAe,GAChB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,UAAU,EACV,oBAAoB,EACpB,aAAa,EACb,IAAI,EACJ,iBAAiB,GAClB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,aAAa,CAAA;AACpB,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,aAAa,EACb,YAAY,EACZ,mBAAmB,IAAI,UAAU,GAClC,MAAM,aAAa,CAAA;AAGpB,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,WAAW,EACX,sBAAsB,EACtB,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,EACV,mBAAmB,EACnB,qBAAqB,EACrB,aAAa,EACb,UAAU,EACV,mBAAmB,GACpB,MAAM,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -45,8 +45,16 @@ export { createOAuth21Server } from './server.js';
45
45
  export { createTestHelpers, generateLoginFormHtml } from './dev.js';
46
46
  // Storage
47
47
  export { MemoryOAuthStorage } from './storage.js';
48
+ // DO SQLite Storage (legacy - use CollectionsOAuthStorage instead)
49
+ export { DOSQLiteStorage } from './storage-do.js';
50
+ // Collections-based Storage (preferred - no migrations needed)
51
+ export { CollectionsOAuthStorage } from './storage-collections.js';
48
52
  // PKCE
49
53
  export { generateCodeVerifier, generateCodeChallenge, verifyCodeChallenge, generatePkce, generateState, generateToken, generateAuthorizationCode, hashClientSecret, verifyClientSecret, base64UrlEncode, base64UrlDecode, constantTimeEqual, } from './pkce.js';
50
54
  // JWT Verification
51
55
  export { verifyJWT, decodeJWT, isJWTExpired, clearJWKSCache } from './jwt.js';
56
+ // JWT Signing
57
+ export { SigningKeyManager, generateSigningKey, serializeSigningKey, deserializeSigningKey, exportPublicKeyToJWKS, exportKeysToJWKS, signAccessToken, } from './jwt-signing.js';
58
+ // Stripe Integration
59
+ export { ensureStripeCustomer, getStripeCustomer, linkStripeCustomer, handleStripeWebhook, verifyStripeWebhook, verifyStripeWebhookAsync, createStripeClient, } from './stripe.js';
52
60
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,SAAS;AACT,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAGjD,0BAA0B;AAC1B,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAGnE,UAAU;AACV,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAGjD,OAAO;AACP,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,aAAa,EACb,yBAAyB,EACzB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,iBAAiB,GAClB,MAAM,WAAW,CAAA;AAElB,mBAAmB;AACnB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,SAAS;AACT,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAGjD,0BAA0B;AAC1B,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAGnE,UAAU;AACV,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAGjD,mEAAmE;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAGjD,+DAA+D;AAC/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAElE,OAAO;AACP,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,aAAa,EACb,yBAAyB,EACzB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,iBAAiB,GAClB,MAAM,WAAW,CAAA;AAElB,mBAAmB;AACnB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAG7E,cAAc;AACd,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,EAChB,eAAe,GAChB,MAAM,kBAAkB,CAAA;AASzB,qBAAqB;AACrB,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,aAAa,CAAA"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * @dotdo/oauth - JWT Signing Key Management
3
+ *
4
+ * Manages RSA-2048 signing keys for JWT token issuance.
5
+ * Supports key generation, storage/retrieval, and JWKS export.
6
+ */
7
+ /**
8
+ * JWT Signing Key with public/private key pair
9
+ */
10
+ export interface SigningKey {
11
+ /** Key identifier */
12
+ kid: string;
13
+ /** Algorithm (always RS256) */
14
+ alg: 'RS256';
15
+ /** Private key for signing */
16
+ privateKey: CryptoKey;
17
+ /** Public key for verification */
18
+ publicKey: CryptoKey;
19
+ /** When the key was created */
20
+ createdAt: number;
21
+ }
22
+ /**
23
+ * JWKS format for public key exposure
24
+ */
25
+ export interface JWKSPublicKey {
26
+ kty: 'RSA';
27
+ kid: string;
28
+ use: 'sig';
29
+ alg: 'RS256';
30
+ n: string;
31
+ e: string;
32
+ }
33
+ /**
34
+ * JWKS document format
35
+ */
36
+ export interface JWKS {
37
+ keys: JWKSPublicKey[];
38
+ }
39
+ /**
40
+ * Serialized key for storage
41
+ */
42
+ export interface SerializedSigningKey {
43
+ kid: string;
44
+ alg: 'RS256';
45
+ privateKeyJwk: JsonWebKey;
46
+ publicKeyJwk: JsonWebKey;
47
+ createdAt: number;
48
+ }
49
+ /**
50
+ * JWT Claims for access tokens
51
+ */
52
+ export interface AccessTokenClaims {
53
+ /** Subject (user ID) */
54
+ sub: string;
55
+ /** Client ID */
56
+ client_id: string;
57
+ /** Scopes */
58
+ scope?: string;
59
+ /** Additional claims */
60
+ [key: string]: unknown;
61
+ }
62
+ /**
63
+ * Generate a new RSA-2048 signing key pair
64
+ */
65
+ export declare function generateSigningKey(kid?: string): Promise<SigningKey>;
66
+ /**
67
+ * Export a signing key to serializable format for storage
68
+ */
69
+ export declare function serializeSigningKey(key: SigningKey): Promise<SerializedSigningKey>;
70
+ /**
71
+ * Import a signing key from serialized format
72
+ */
73
+ export declare function deserializeSigningKey(serialized: SerializedSigningKey): Promise<SigningKey>;
74
+ /**
75
+ * Export public key to JWKS format
76
+ */
77
+ export declare function exportPublicKeyToJWKS(key: SigningKey): Promise<JWKSPublicKey>;
78
+ /**
79
+ * Export multiple keys to JWKS document
80
+ */
81
+ export declare function exportKeysToJWKS(keys: SigningKey[]): Promise<JWKS>;
82
+ /**
83
+ * Sign a JWT with the given claims
84
+ */
85
+ export declare function signAccessToken(key: SigningKey, claims: AccessTokenClaims, options: {
86
+ issuer: string;
87
+ audience?: string;
88
+ expiresIn?: number;
89
+ }): Promise<string>;
90
+ /**
91
+ * Signing Key Manager - handles key storage and rotation
92
+ */
93
+ export declare class SigningKeyManager {
94
+ private options;
95
+ private keys;
96
+ private currentKeyIndex;
97
+ constructor(options?: {
98
+ maxKeys?: number;
99
+ });
100
+ /**
101
+ * Get the current signing key, generating one if needed
102
+ */
103
+ getCurrentKey(): Promise<SigningKey>;
104
+ /**
105
+ * Get all keys (for JWKS endpoint)
106
+ */
107
+ getAllKeys(): SigningKey[];
108
+ /**
109
+ * Rotate to a new key
110
+ */
111
+ rotateKey(): Promise<SigningKey>;
112
+ /**
113
+ * Load keys from serialized format
114
+ */
115
+ loadKeys(serializedKeys: SerializedSigningKey[]): Promise<void>;
116
+ /**
117
+ * Export keys to serializable format
118
+ */
119
+ exportKeys(): Promise<SerializedSigningKey[]>;
120
+ /**
121
+ * Export to JWKS format
122
+ */
123
+ toJWKS(): Promise<JWKS>;
124
+ /**
125
+ * Sign an access token with the current key
126
+ */
127
+ signAccessToken(claims: AccessTokenClaims, options: {
128
+ issuer: string;
129
+ audience?: string;
130
+ expiresIn?: number;
131
+ }): Promise<string>;
132
+ }
133
+ //# sourceMappingURL=jwt-signing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt-signing.d.ts","sourceRoot":"","sources":["../src/jwt-signing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * @dotdo/oauth - JWT Signing Key Management
3
+ *
4
+ * Manages RSA-2048 signing keys for JWT token issuance.
5
+ * Supports key generation, storage/retrieval, and JWKS export.
6
+ */
7
+ import { base64UrlEncode } from './pkce.js';
8
+ /**
9
+ * Generate a new RSA-2048 signing key pair
10
+ */
11
+ export async function generateSigningKey(kid) {
12
+ const keyPair = await crypto.subtle.generateKey({
13
+ name: 'RSASSA-PKCS1-v1_5',
14
+ modulusLength: 2048,
15
+ publicExponent: new Uint8Array([1, 0, 1]),
16
+ hash: 'SHA-256',
17
+ }, true, // extractable
18
+ ['sign', 'verify']);
19
+ return {
20
+ kid: kid || `oauth-do-key-${Date.now()}`,
21
+ alg: 'RS256',
22
+ privateKey: keyPair.privateKey,
23
+ publicKey: keyPair.publicKey,
24
+ createdAt: Date.now(),
25
+ };
26
+ }
27
+ /**
28
+ * Export a signing key to serializable format for storage
29
+ */
30
+ export async function serializeSigningKey(key) {
31
+ const [privateKeyJwk, publicKeyJwk] = await Promise.all([
32
+ crypto.subtle.exportKey('jwk', key.privateKey),
33
+ crypto.subtle.exportKey('jwk', key.publicKey),
34
+ ]);
35
+ return {
36
+ kid: key.kid,
37
+ alg: key.alg,
38
+ privateKeyJwk,
39
+ publicKeyJwk,
40
+ createdAt: key.createdAt,
41
+ };
42
+ }
43
+ /**
44
+ * Import a signing key from serialized format
45
+ */
46
+ export async function deserializeSigningKey(serialized) {
47
+ const [privateKey, publicKey] = await Promise.all([
48
+ crypto.subtle.importKey('jwk', serialized.privateKeyJwk, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, true, ['sign']),
49
+ crypto.subtle.importKey('jwk', serialized.publicKeyJwk, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, true, ['verify']),
50
+ ]);
51
+ return {
52
+ kid: serialized.kid,
53
+ alg: serialized.alg,
54
+ privateKey,
55
+ publicKey,
56
+ createdAt: serialized.createdAt,
57
+ };
58
+ }
59
+ /**
60
+ * Export public key to JWKS format
61
+ */
62
+ export async function exportPublicKeyToJWKS(key) {
63
+ const jwk = await crypto.subtle.exportKey('jwk', key.publicKey);
64
+ return {
65
+ kty: 'RSA',
66
+ kid: key.kid,
67
+ use: 'sig',
68
+ alg: 'RS256',
69
+ n: jwk.n,
70
+ e: jwk.e,
71
+ };
72
+ }
73
+ /**
74
+ * Export multiple keys to JWKS document
75
+ */
76
+ export async function exportKeysToJWKS(keys) {
77
+ const publicKeys = await Promise.all(keys.map(exportPublicKeyToJWKS));
78
+ return { keys: publicKeys };
79
+ }
80
+ /**
81
+ * Sign a JWT with the given claims
82
+ */
83
+ export async function signAccessToken(key, claims, options) {
84
+ const { issuer, audience, expiresIn = 3600 } = options;
85
+ const now = Math.floor(Date.now() / 1000);
86
+ const header = {
87
+ alg: 'RS256',
88
+ typ: 'JWT',
89
+ kid: key.kid,
90
+ };
91
+ const payload = {
92
+ ...claims,
93
+ iss: issuer,
94
+ ...(audience && { aud: audience }),
95
+ iat: now,
96
+ exp: now + expiresIn,
97
+ };
98
+ const encoder = new TextEncoder();
99
+ const headerB64 = base64UrlEncode(encoder.encode(JSON.stringify(header)).buffer);
100
+ const payloadB64 = base64UrlEncode(encoder.encode(JSON.stringify(payload)).buffer);
101
+ const data = `${headerB64}.${payloadB64}`;
102
+ const signature = await crypto.subtle.sign({ name: 'RSASSA-PKCS1-v1_5' }, key.privateKey, encoder.encode(data));
103
+ const signatureB64 = base64UrlEncode(signature);
104
+ return `${data}.${signatureB64}`;
105
+ }
106
+ /**
107
+ * Signing Key Manager - handles key storage and rotation
108
+ */
109
+ export class SigningKeyManager {
110
+ options;
111
+ keys = [];
112
+ currentKeyIndex = 0;
113
+ constructor(options = {}) {
114
+ this.options = options;
115
+ this.options.maxKeys = options.maxKeys ?? 2;
116
+ }
117
+ /**
118
+ * Get the current signing key, generating one if needed
119
+ */
120
+ async getCurrentKey() {
121
+ if (this.keys.length === 0) {
122
+ const key = await generateSigningKey();
123
+ this.keys.push(key);
124
+ }
125
+ return this.keys[this.currentKeyIndex];
126
+ }
127
+ /**
128
+ * Get all keys (for JWKS endpoint)
129
+ */
130
+ getAllKeys() {
131
+ return [...this.keys];
132
+ }
133
+ /**
134
+ * Rotate to a new key
135
+ */
136
+ async rotateKey() {
137
+ const newKey = await generateSigningKey();
138
+ this.keys.push(newKey);
139
+ // Remove old keys if we exceed maxKeys
140
+ while (this.keys.length > this.options.maxKeys) {
141
+ this.keys.shift();
142
+ }
143
+ this.currentKeyIndex = this.keys.length - 1;
144
+ return newKey;
145
+ }
146
+ /**
147
+ * Load keys from serialized format
148
+ */
149
+ async loadKeys(serializedKeys) {
150
+ this.keys = await Promise.all(serializedKeys.map(deserializeSigningKey));
151
+ this.currentKeyIndex = this.keys.length - 1;
152
+ }
153
+ /**
154
+ * Export keys to serializable format
155
+ */
156
+ async exportKeys() {
157
+ return Promise.all(this.keys.map(serializeSigningKey));
158
+ }
159
+ /**
160
+ * Export to JWKS format
161
+ */
162
+ async toJWKS() {
163
+ return exportKeysToJWKS(this.keys);
164
+ }
165
+ /**
166
+ * Sign an access token with the current key
167
+ */
168
+ async signAccessToken(claims, options) {
169
+ const key = await this.getCurrentKey();
170
+ return signAccessToken(key, claims, options);
171
+ }
172
+ }
173
+ //# sourceMappingURL=jwt-signing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt-signing.js","sourceRoot":"","sources":["../src/jwt-signing.ts"],"names":[],"mappings":"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"}
package/dist/server.d.ts CHANGED
@@ -4,9 +4,11 @@
4
4
  * Creates a Hono app that implements OAuth 2.1 authorization server endpoints:
5
5
  * - /.well-known/oauth-authorization-server (RFC 8414)
6
6
  * - /.well-known/oauth-protected-resource (draft-ietf-oauth-resource-metadata)
7
+ * - /.well-known/jwks.json (JWKS endpoint)
7
8
  * - /authorize (authorization endpoint)
8
9
  * - /callback (upstream OAuth callback)
9
10
  * - /token (token endpoint)
11
+ * - /introspect (token introspection - RFC 7662)
10
12
  * - /register (dynamic client registration - RFC 7591)
11
13
  * - /revoke (token revocation - RFC 7009)
12
14
  *
@@ -18,6 +20,7 @@ import { Hono } from 'hono';
18
20
  import type { OAuthStorage } from './storage.js';
19
21
  import type { OAuthUser, UpstreamOAuthConfig } from './types.js';
20
22
  import { type DevModeConfig, type TestHelpers } from './dev.js';
23
+ import type { SigningKeyManager } from './jwt-signing.js';
21
24
  /**
22
25
  * Configuration for the OAuth 2.1 server
23
26
  */
@@ -46,13 +49,26 @@ export interface OAuth21ServerConfig {
46
49
  debug?: boolean;
47
50
  /** Allowed CORS origins (default: issuer origin only in production, '*' in dev mode) */
48
51
  allowedOrigins?: string[];
52
+ /**
53
+ * Signing key manager for JWT access tokens (optional)
54
+ * If provided, access tokens will be signed JWTs instead of opaque tokens.
55
+ * This enables the JWKS and introspection endpoints.
56
+ */
57
+ signingKeyManager?: SigningKeyManager;
58
+ /**
59
+ * Use JWT access tokens instead of opaque tokens (default: false)
60
+ * Requires signingKeyManager to be set, or will auto-create one in memory.
61
+ */
62
+ useJwtAccessTokens?: boolean;
49
63
  }
50
64
  /**
51
- * Extended Hono app with test helpers
65
+ * Extended Hono app with test helpers and signing key manager
52
66
  */
53
67
  export interface OAuth21Server extends Hono {
54
68
  /** Test helpers for E2E testing (only available in devMode) */
55
69
  testHelpers?: TestHelpers;
70
+ /** Signing key manager (available if useJwtAccessTokens is enabled) */
71
+ signingKeyManager?: SigningKeyManager;
56
72
  }
57
73
  /**
58
74
  * Create an OAuth 2.1 server as a Hono app
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAG3B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,KAAK,EAIV,SAAS,EAGT,mBAAmB,EACpB,MAAM,YAAY,CAAA;AASnB,OAAO,EACL,KAAK,aAAa,EAElB,KAAK,WAAW,EAGjB,MAAM,UAAU,CAAA;AAEjB;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,OAAO,EAAE,YAAY,CAAA;IACrB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,mBAAmB,CAAA;IAC9B,mEAAmE;IACnE,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,uBAAuB;IACvB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,yCAAyC;IACzC,yBAAyB,CAAC,EAAE,OAAO,CAAA;IACnC,oDAAoD;IACpD,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/D,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,wFAAwF;IACxF,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,aAAc,SAAQ,IAAI;IACzC,+DAA+D;IAC/D,WAAW,CAAC,EAAE,WAAW,CAAA;CAC1B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa,CAokB9E;AAyeD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC7E,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAG3B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,KAAK,EAIV,SAAS,EAGT,mBAAmB,EACpB,MAAM,YAAY,CAAA;AASnB,OAAO,EACL,KAAK,aAAa,EAElB,KAAK,WAAW,EAGjB,MAAM,UAAU,CAAA;AACjB,OAAO,KAAK,EAAE,iBAAiB,EAAqB,MAAM,kBAAkB,CAAA;AAG5E;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,OAAO,EAAE,YAAY,CAAA;IACrB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,mBAAmB,CAAA;IAC9B,mEAAmE;IACnE,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,uBAAuB;IACvB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,yCAAyC;IACzC,yBAAyB,CAAC,EAAE,OAAO,CAAA;IACnC,oDAAoD;IACpD,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/D,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,wFAAwF;IACxF,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,aAAc,SAAQ,IAAI;IACzC,+DAA+D;IAC/D,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,uEAAuE;IACvE,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;CACtC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa,CA2+B9E;AA2iBD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC7E,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA"}