@mcp-z/oauth-google 1.0.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.
Files changed (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -0
  3. package/dist/cjs/index.d.cts +16 -0
  4. package/dist/cjs/index.d.ts +16 -0
  5. package/dist/cjs/index.js +112 -0
  6. package/dist/cjs/index.js.map +1 -0
  7. package/dist/cjs/lib/dcr-router.d.cts +44 -0
  8. package/dist/cjs/lib/dcr-router.d.ts +44 -0
  9. package/dist/cjs/lib/dcr-router.js +1189 -0
  10. package/dist/cjs/lib/dcr-router.js.map +1 -0
  11. package/dist/cjs/lib/dcr-utils.d.cts +160 -0
  12. package/dist/cjs/lib/dcr-utils.d.ts +160 -0
  13. package/dist/cjs/lib/dcr-utils.js +860 -0
  14. package/dist/cjs/lib/dcr-utils.js.map +1 -0
  15. package/dist/cjs/lib/dcr-verify.d.cts +53 -0
  16. package/dist/cjs/lib/dcr-verify.d.ts +53 -0
  17. package/dist/cjs/lib/dcr-verify.js +193 -0
  18. package/dist/cjs/lib/dcr-verify.js.map +1 -0
  19. package/dist/cjs/lib/fetch-with-timeout.d.cts +14 -0
  20. package/dist/cjs/lib/fetch-with-timeout.d.ts +14 -0
  21. package/dist/cjs/lib/fetch-with-timeout.js +257 -0
  22. package/dist/cjs/lib/fetch-with-timeout.js.map +1 -0
  23. package/dist/cjs/lib/token-verifier.d.cts +44 -0
  24. package/dist/cjs/lib/token-verifier.d.ts +44 -0
  25. package/dist/cjs/lib/token-verifier.js +253 -0
  26. package/dist/cjs/lib/token-verifier.js.map +1 -0
  27. package/dist/cjs/package.json +1 -0
  28. package/dist/cjs/providers/dcr.d.cts +107 -0
  29. package/dist/cjs/providers/dcr.d.ts +107 -0
  30. package/dist/cjs/providers/dcr.js +584 -0
  31. package/dist/cjs/providers/dcr.js.map +1 -0
  32. package/dist/cjs/providers/loopback-oauth.d.cts +119 -0
  33. package/dist/cjs/providers/loopback-oauth.d.ts +119 -0
  34. package/dist/cjs/providers/loopback-oauth.js +1334 -0
  35. package/dist/cjs/providers/loopback-oauth.js.map +1 -0
  36. package/dist/cjs/providers/service-account.d.cts +131 -0
  37. package/dist/cjs/providers/service-account.d.ts +131 -0
  38. package/dist/cjs/providers/service-account.js +800 -0
  39. package/dist/cjs/providers/service-account.js.map +1 -0
  40. package/dist/cjs/schemas/index.d.cts +20 -0
  41. package/dist/cjs/schemas/index.d.ts +20 -0
  42. package/dist/cjs/schemas/index.js +37 -0
  43. package/dist/cjs/schemas/index.js.map +1 -0
  44. package/dist/cjs/setup/config.d.cts +112 -0
  45. package/dist/cjs/setup/config.d.ts +112 -0
  46. package/dist/cjs/setup/config.js +236 -0
  47. package/dist/cjs/setup/config.js.map +1 -0
  48. package/dist/cjs/types.d.cts +173 -0
  49. package/dist/cjs/types.d.ts +173 -0
  50. package/dist/cjs/types.js +16 -0
  51. package/dist/cjs/types.js.map +1 -0
  52. package/dist/esm/index.d.ts +16 -0
  53. package/dist/esm/index.js +16 -0
  54. package/dist/esm/index.js.map +1 -0
  55. package/dist/esm/lib/dcr-router.d.ts +44 -0
  56. package/dist/esm/lib/dcr-router.js +515 -0
  57. package/dist/esm/lib/dcr-router.js.map +1 -0
  58. package/dist/esm/lib/dcr-utils.d.ts +160 -0
  59. package/dist/esm/lib/dcr-utils.js +270 -0
  60. package/dist/esm/lib/dcr-utils.js.map +1 -0
  61. package/dist/esm/lib/dcr-verify.d.ts +53 -0
  62. package/dist/esm/lib/dcr-verify.js +53 -0
  63. package/dist/esm/lib/dcr-verify.js.map +1 -0
  64. package/dist/esm/lib/fetch-with-timeout.d.ts +14 -0
  65. package/dist/esm/lib/fetch-with-timeout.js +30 -0
  66. package/dist/esm/lib/fetch-with-timeout.js.map +1 -0
  67. package/dist/esm/lib/token-verifier.d.ts +44 -0
  68. package/dist/esm/lib/token-verifier.js +53 -0
  69. package/dist/esm/lib/token-verifier.js.map +1 -0
  70. package/dist/esm/package.json +1 -0
  71. package/dist/esm/providers/dcr.d.ts +107 -0
  72. package/dist/esm/providers/dcr.js +242 -0
  73. package/dist/esm/providers/dcr.js.map +1 -0
  74. package/dist/esm/providers/loopback-oauth.d.ts +119 -0
  75. package/dist/esm/providers/loopback-oauth.js +639 -0
  76. package/dist/esm/providers/loopback-oauth.js.map +1 -0
  77. package/dist/esm/providers/service-account.d.ts +131 -0
  78. package/dist/esm/providers/service-account.js +353 -0
  79. package/dist/esm/providers/service-account.js.map +1 -0
  80. package/dist/esm/schemas/index.d.ts +20 -0
  81. package/dist/esm/schemas/index.js +18 -0
  82. package/dist/esm/schemas/index.js.map +1 -0
  83. package/dist/esm/setup/config.d.ts +112 -0
  84. package/dist/esm/setup/config.js +258 -0
  85. package/dist/esm/setup/config.js.map +1 -0
  86. package/dist/esm/types.d.ts +173 -0
  87. package/dist/esm/types.js +6 -0
  88. package/dist/esm/types.js.map +1 -0
  89. package/package.json +89 -0
@@ -0,0 +1,160 @@
1
+ /**
2
+ * DCR Storage Utilities
3
+ *
4
+ * Keyv-based storage utilities for Dynamic Client Registration.
5
+ * Follows @mcp-z/oauth pattern: single Keyv store with compound keys.
6
+ *
7
+ * Key Patterns:
8
+ * - dcr:client:{clientId} -> RegisteredClient
9
+ * - dcr:provider:{dcrToken} -> ProviderTokens
10
+ * - dcr:authcode:{code} -> AuthorizationCode
11
+ * - dcr:access:{token} -> AccessToken
12
+ * - dcr:refresh:{token} -> AccessToken
13
+ */
14
+ import type { DcrClientInformation, DcrClientMetadata, ProviderTokens } from '@mcp-z/oauth';
15
+ import type { Keyv } from 'keyv';
16
+ import type { AccessToken, AuthorizationCode, RegisteredClient } from '../types.js';
17
+ /**
18
+ * Register a new OAuth client (RFC 7591 Section 3.1)
19
+ *
20
+ * @param store - Keyv store for all DCR data
21
+ * @param metadata - Client registration metadata
22
+ * @returns Registered client with credentials
23
+ * @throws Error if validation fails
24
+ */
25
+ export declare function registerClient(store: Keyv, metadata: DcrClientMetadata): Promise<DcrClientInformation>;
26
+ /**
27
+ * Get a registered client by ID
28
+ *
29
+ * @param store - Keyv store for all DCR data
30
+ * @param clientId - Client identifier
31
+ * @returns Registered client or undefined if not found
32
+ */
33
+ export declare function getClient(store: Keyv, clientId: string): Promise<RegisteredClient | undefined>;
34
+ /**
35
+ * Validate client credentials
36
+ *
37
+ * @param store - Keyv store for all DCR data
38
+ * @param clientId - Client identifier
39
+ * @param clientSecret - Client secret
40
+ * @returns True if credentials are valid
41
+ */
42
+ export declare function validateClient(store: Keyv, clientId: string, clientSecret: string): Promise<boolean>;
43
+ /**
44
+ * Validate redirect URI for a client
45
+ *
46
+ * @param store - Keyv store for all DCR data
47
+ * @param clientId - Client identifier
48
+ * @param redirectUri - Redirect URI to validate
49
+ * @returns True if redirect URI is registered
50
+ */
51
+ export declare function validateRedirectUri(store: Keyv, clientId: string, redirectUri: string): Promise<boolean>;
52
+ /**
53
+ * List all registered clients (for debugging)
54
+ *
55
+ * Note: This method uses Keyv's iterator which may not be available on all storage adapters.
56
+ * For production use, consider maintaining a separate index of client IDs.
57
+ *
58
+ * @param store - Keyv store for all DCR data
59
+ * @returns Array of all registered clients
60
+ */
61
+ export declare function listClients(store: Keyv): Promise<RegisteredClient[]>;
62
+ /**
63
+ * Delete a registered client
64
+ *
65
+ * @param store - Keyv store for all DCR data
66
+ * @param clientId - Client identifier
67
+ */
68
+ export declare function deleteClient(store: Keyv, clientId: string): Promise<void>;
69
+ /**
70
+ * Store provider tokens for a DCR access token
71
+ *
72
+ * @param store - Keyv store for all DCR data
73
+ * @param dcrToken - DCR-issued access token (used as key)
74
+ * @param tokens - Google provider tokens (access, refresh, expiry)
75
+ */
76
+ export declare function setProviderTokens(store: Keyv, dcrToken: string, tokens: ProviderTokens): Promise<void>;
77
+ /**
78
+ * Retrieve provider tokens for a DCR access token
79
+ *
80
+ * @param store - Keyv store for all DCR data
81
+ * @param dcrToken - DCR-issued access token
82
+ * @returns Provider tokens or undefined if not found
83
+ */
84
+ export declare function getProviderTokens(store: Keyv, dcrToken: string): Promise<ProviderTokens | undefined>;
85
+ /**
86
+ * Delete provider tokens for a DCR access token
87
+ *
88
+ * @param store - Keyv store for all DCR data
89
+ * @param dcrToken - DCR-issued access token
90
+ */
91
+ export declare function deleteProviderTokens(store: Keyv, dcrToken: string): Promise<void>;
92
+ /**
93
+ * Store an authorization code
94
+ *
95
+ * @param store - Keyv store for all DCR data
96
+ * @param code - Authorization code
97
+ * @param authCode - Authorization code data
98
+ */
99
+ export declare function setAuthCode(store: Keyv, code: string, authCode: AuthorizationCode): Promise<void>;
100
+ /**
101
+ * Get an authorization code
102
+ *
103
+ * @param store - Keyv store for all DCR data
104
+ * @param code - Authorization code
105
+ * @returns Authorization code data or undefined if not found
106
+ */
107
+ export declare function getAuthCode(store: Keyv, code: string): Promise<AuthorizationCode | undefined>;
108
+ /**
109
+ * Delete an authorization code
110
+ *
111
+ * @param store - Keyv store for all DCR data
112
+ * @param code - Authorization code
113
+ */
114
+ export declare function deleteAuthCode(store: Keyv, code: string): Promise<void>;
115
+ /**
116
+ * Store an access token
117
+ *
118
+ * @param store - Keyv store for all DCR data
119
+ * @param token - Access token
120
+ * @param tokenData - Access token data
121
+ */
122
+ export declare function setAccessToken(store: Keyv, token: string, tokenData: AccessToken): Promise<void>;
123
+ /**
124
+ * Get an access token
125
+ *
126
+ * @param store - Keyv store for all DCR data
127
+ * @param token - Access token
128
+ * @returns Access token data or undefined if not found
129
+ */
130
+ export declare function getAccessToken(store: Keyv, token: string): Promise<AccessToken | undefined>;
131
+ /**
132
+ * Delete an access token
133
+ *
134
+ * @param store - Keyv store for all DCR data
135
+ * @param token - Access token
136
+ */
137
+ export declare function deleteAccessToken(store: Keyv, token: string): Promise<void>;
138
+ /**
139
+ * Store a refresh token
140
+ *
141
+ * @param store - Keyv store for all DCR data
142
+ * @param token - Refresh token
143
+ * @param tokenData - Access token data (contains refresh token context)
144
+ */
145
+ export declare function setRefreshToken(store: Keyv, token: string, tokenData: AccessToken): Promise<void>;
146
+ /**
147
+ * Get a refresh token
148
+ *
149
+ * @param store - Keyv store for all DCR data
150
+ * @param token - Refresh token
151
+ * @returns Access token data or undefined if not found
152
+ */
153
+ export declare function getRefreshToken(store: Keyv, token: string): Promise<AccessToken | undefined>;
154
+ /**
155
+ * Delete a refresh token
156
+ *
157
+ * @param store - Keyv store for all DCR data
158
+ * @param token - Refresh token
159
+ */
160
+ export declare function deleteRefreshToken(store: Keyv, token: string): Promise<void>;
@@ -0,0 +1,270 @@
1
+ /**
2
+ * DCR Storage Utilities
3
+ *
4
+ * Keyv-based storage utilities for Dynamic Client Registration.
5
+ * Follows @mcp-z/oauth pattern: single Keyv store with compound keys.
6
+ *
7
+ * Key Patterns:
8
+ * - dcr:client:{clientId} -> RegisteredClient
9
+ * - dcr:provider:{dcrToken} -> ProviderTokens
10
+ * - dcr:authcode:{code} -> AuthorizationCode
11
+ * - dcr:access:{token} -> AccessToken
12
+ * - dcr:refresh:{token} -> AccessToken
13
+ */ import { randomUUID } from 'crypto';
14
+ // ============================================================================
15
+ // Client Operations
16
+ // ============================================================================
17
+ /**
18
+ * Register a new OAuth client (RFC 7591 Section 3.1)
19
+ *
20
+ * @param store - Keyv store for all DCR data
21
+ * @param metadata - Client registration metadata
22
+ * @returns Registered client with credentials
23
+ * @throws Error if validation fails
24
+ */ export async function registerClient(store, metadata) {
25
+ var _metadata_grant_types, _metadata_response_types, _metadata_token_endpoint_auth_method;
26
+ // Validate redirect URIs (required per RFC 7591)
27
+ if (!metadata.redirect_uris || metadata.redirect_uris.length === 0) {
28
+ throw new Error('redirect_uris is required');
29
+ }
30
+ // Generate client credentials
31
+ const client_id = `dcr_${randomUUID()}`;
32
+ const client_secret = randomUUID();
33
+ // Default grant types and response types per RFC 7591 Section 2
34
+ const grant_types = (_metadata_grant_types = metadata.grant_types) !== null && _metadata_grant_types !== void 0 ? _metadata_grant_types : [
35
+ 'authorization_code',
36
+ 'refresh_token'
37
+ ];
38
+ const response_types = (_metadata_response_types = metadata.response_types) !== null && _metadata_response_types !== void 0 ? _metadata_response_types : [
39
+ 'code'
40
+ ];
41
+ // Build registered client - only include optional fields if they have values
42
+ const client = {
43
+ client_id,
44
+ client_secret,
45
+ client_id_issued_at: Math.floor(Date.now() / 1000),
46
+ client_secret_expires_at: 0,
47
+ redirect_uris: metadata.redirect_uris,
48
+ token_endpoint_auth_method: (_metadata_token_endpoint_auth_method = metadata.token_endpoint_auth_method) !== null && _metadata_token_endpoint_auth_method !== void 0 ? _metadata_token_endpoint_auth_method : 'client_secret_basic',
49
+ grant_types,
50
+ response_types,
51
+ ...metadata.client_name !== undefined && {
52
+ client_name: metadata.client_name
53
+ },
54
+ ...metadata.client_uri !== undefined && {
55
+ client_uri: metadata.client_uri
56
+ },
57
+ ...metadata.logo_uri !== undefined && {
58
+ logo_uri: metadata.logo_uri
59
+ },
60
+ ...metadata.scope !== undefined && {
61
+ scope: metadata.scope
62
+ },
63
+ ...metadata.contacts !== undefined && {
64
+ contacts: metadata.contacts
65
+ },
66
+ ...metadata.tos_uri !== undefined && {
67
+ tos_uri: metadata.tos_uri
68
+ },
69
+ ...metadata.policy_uri !== undefined && {
70
+ policy_uri: metadata.policy_uri
71
+ },
72
+ ...metadata.jwks_uri !== undefined && {
73
+ jwks_uri: metadata.jwks_uri
74
+ },
75
+ ...metadata.jwks !== undefined && {
76
+ jwks: metadata.jwks
77
+ },
78
+ ...metadata.software_id !== undefined && {
79
+ software_id: metadata.software_id
80
+ },
81
+ ...metadata.software_version !== undefined && {
82
+ software_version: metadata.software_version
83
+ },
84
+ created_at: Date.now()
85
+ };
86
+ // Store client
87
+ await store.set(`dcr:client:${client_id}`, client);
88
+ // Return client information (excluding internal created_at)
89
+ const { created_at, ...clientInfo } = client;
90
+ return clientInfo;
91
+ }
92
+ /**
93
+ * Get a registered client by ID
94
+ *
95
+ * @param store - Keyv store for all DCR data
96
+ * @param clientId - Client identifier
97
+ * @returns Registered client or undefined if not found
98
+ */ export async function getClient(store, clientId) {
99
+ return await store.get(`dcr:client:${clientId}`);
100
+ }
101
+ /**
102
+ * Validate client credentials
103
+ *
104
+ * @param store - Keyv store for all DCR data
105
+ * @param clientId - Client identifier
106
+ * @param clientSecret - Client secret
107
+ * @returns True if credentials are valid
108
+ */ export async function validateClient(store, clientId, clientSecret) {
109
+ const client = await getClient(store, clientId);
110
+ if (!client) return false;
111
+ return client.client_secret === clientSecret;
112
+ }
113
+ /**
114
+ * Validate redirect URI for a client
115
+ *
116
+ * @param store - Keyv store for all DCR data
117
+ * @param clientId - Client identifier
118
+ * @param redirectUri - Redirect URI to validate
119
+ * @returns True if redirect URI is registered
120
+ */ export async function validateRedirectUri(store, clientId, redirectUri) {
121
+ const client = await getClient(store, clientId);
122
+ if (!client || !client.redirect_uris) return false;
123
+ return client.redirect_uris.includes(redirectUri);
124
+ }
125
+ /**
126
+ * List all registered clients (for debugging)
127
+ *
128
+ * Note: This method uses Keyv's iterator which may not be available on all storage adapters.
129
+ * For production use, consider maintaining a separate index of client IDs.
130
+ *
131
+ * @param store - Keyv store for all DCR data
132
+ * @returns Array of all registered clients
133
+ */ export async function listClients(store) {
134
+ const clients = [];
135
+ // Check if iterator is available on the store
136
+ if (store.iterator) {
137
+ // Use iterator with namespace to iterate through dcr:client: keys
138
+ const iterator = store.iterator('dcr:client:');
139
+ for await (const [_key, value] of iterator){
140
+ if (value !== undefined) {
141
+ clients.push(value);
142
+ }
143
+ }
144
+ }
145
+ return clients;
146
+ }
147
+ /**
148
+ * Delete a registered client
149
+ *
150
+ * @param store - Keyv store for all DCR data
151
+ * @param clientId - Client identifier
152
+ */ export async function deleteClient(store, clientId) {
153
+ await store.delete(`dcr:client:${clientId}`);
154
+ }
155
+ // ============================================================================
156
+ // Provider Token Operations
157
+ // ============================================================================
158
+ /**
159
+ * Store provider tokens for a DCR access token
160
+ *
161
+ * @param store - Keyv store for all DCR data
162
+ * @param dcrToken - DCR-issued access token (used as key)
163
+ * @param tokens - Google provider tokens (access, refresh, expiry)
164
+ */ export async function setProviderTokens(store, dcrToken, tokens) {
165
+ await store.set(`dcr:provider:${dcrToken}`, tokens);
166
+ }
167
+ /**
168
+ * Retrieve provider tokens for a DCR access token
169
+ *
170
+ * @param store - Keyv store for all DCR data
171
+ * @param dcrToken - DCR-issued access token
172
+ * @returns Provider tokens or undefined if not found
173
+ */ export async function getProviderTokens(store, dcrToken) {
174
+ return await store.get(`dcr:provider:${dcrToken}`);
175
+ }
176
+ /**
177
+ * Delete provider tokens for a DCR access token
178
+ *
179
+ * @param store - Keyv store for all DCR data
180
+ * @param dcrToken - DCR-issued access token
181
+ */ export async function deleteProviderTokens(store, dcrToken) {
182
+ await store.delete(`dcr:provider:${dcrToken}`);
183
+ }
184
+ // ============================================================================
185
+ // Authorization Code Operations
186
+ // ============================================================================
187
+ /**
188
+ * Store an authorization code
189
+ *
190
+ * @param store - Keyv store for all DCR data
191
+ * @param code - Authorization code
192
+ * @param authCode - Authorization code data
193
+ */ export async function setAuthCode(store, code, authCode) {
194
+ await store.set(`dcr:authcode:${code}`, authCode);
195
+ }
196
+ /**
197
+ * Get an authorization code
198
+ *
199
+ * @param store - Keyv store for all DCR data
200
+ * @param code - Authorization code
201
+ * @returns Authorization code data or undefined if not found
202
+ */ export async function getAuthCode(store, code) {
203
+ return await store.get(`dcr:authcode:${code}`);
204
+ }
205
+ /**
206
+ * Delete an authorization code
207
+ *
208
+ * @param store - Keyv store for all DCR data
209
+ * @param code - Authorization code
210
+ */ export async function deleteAuthCode(store, code) {
211
+ await store.delete(`dcr:authcode:${code}`);
212
+ }
213
+ // ============================================================================
214
+ // Access Token Operations
215
+ // ============================================================================
216
+ /**
217
+ * Store an access token
218
+ *
219
+ * @param store - Keyv store for all DCR data
220
+ * @param token - Access token
221
+ * @param tokenData - Access token data
222
+ */ export async function setAccessToken(store, token, tokenData) {
223
+ await store.set(`dcr:access:${token}`, tokenData);
224
+ }
225
+ /**
226
+ * Get an access token
227
+ *
228
+ * @param store - Keyv store for all DCR data
229
+ * @param token - Access token
230
+ * @returns Access token data or undefined if not found
231
+ */ export async function getAccessToken(store, token) {
232
+ return await store.get(`dcr:access:${token}`);
233
+ }
234
+ /**
235
+ * Delete an access token
236
+ *
237
+ * @param store - Keyv store for all DCR data
238
+ * @param token - Access token
239
+ */ export async function deleteAccessToken(store, token) {
240
+ await store.delete(`dcr:access:${token}`);
241
+ }
242
+ // ============================================================================
243
+ // Refresh Token Operations
244
+ // ============================================================================
245
+ /**
246
+ * Store a refresh token
247
+ *
248
+ * @param store - Keyv store for all DCR data
249
+ * @param token - Refresh token
250
+ * @param tokenData - Access token data (contains refresh token context)
251
+ */ export async function setRefreshToken(store, token, tokenData) {
252
+ await store.set(`dcr:refresh:${token}`, tokenData);
253
+ }
254
+ /**
255
+ * Get a refresh token
256
+ *
257
+ * @param store - Keyv store for all DCR data
258
+ * @param token - Refresh token
259
+ * @returns Access token data or undefined if not found
260
+ */ export async function getRefreshToken(store, token) {
261
+ return await store.get(`dcr:refresh:${token}`);
262
+ }
263
+ /**
264
+ * Delete a refresh token
265
+ *
266
+ * @param store - Keyv store for all DCR data
267
+ * @param token - Refresh token
268
+ */ export async function deleteRefreshToken(store, token) {
269
+ await store.delete(`dcr:refresh:${token}`);
270
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth-google/src/lib/dcr-utils.ts"],"sourcesContent":["/**\n * DCR Storage Utilities\n *\n * Keyv-based storage utilities for Dynamic Client Registration.\n * Follows @mcp-z/oauth pattern: single Keyv store with compound keys.\n *\n * Key Patterns:\n * - dcr:client:{clientId} -> RegisteredClient\n * - dcr:provider:{dcrToken} -> ProviderTokens\n * - dcr:authcode:{code} -> AuthorizationCode\n * - dcr:access:{token} -> AccessToken\n * - dcr:refresh:{token} -> AccessToken\n */\n\nimport type { DcrClientInformation, DcrClientMetadata, ProviderTokens } from '@mcp-z/oauth';\nimport { randomUUID } from 'crypto';\nimport type { Keyv } from 'keyv';\nimport type { AccessToken, AuthorizationCode, RegisteredClient } from '../types.ts';\n\n// ============================================================================\n// Client Operations\n// ============================================================================\n\n/**\n * Register a new OAuth client (RFC 7591 Section 3.1)\n *\n * @param store - Keyv store for all DCR data\n * @param metadata - Client registration metadata\n * @returns Registered client with credentials\n * @throws Error if validation fails\n */\nexport async function registerClient(store: Keyv, metadata: DcrClientMetadata): Promise<DcrClientInformation> {\n // Validate redirect URIs (required per RFC 7591)\n if (!metadata.redirect_uris || metadata.redirect_uris.length === 0) {\n throw new Error('redirect_uris is required');\n }\n\n // Generate client credentials\n const client_id = `dcr_${randomUUID()}`;\n const client_secret = randomUUID();\n\n // Default grant types and response types per RFC 7591 Section 2\n const grant_types = metadata.grant_types ?? ['authorization_code', 'refresh_token'];\n const response_types = metadata.response_types ?? ['code'];\n\n // Build registered client - only include optional fields if they have values\n const client: RegisteredClient = {\n client_id,\n client_secret,\n client_id_issued_at: Math.floor(Date.now() / 1000),\n client_secret_expires_at: 0, // Never expires\n redirect_uris: metadata.redirect_uris,\n token_endpoint_auth_method: metadata.token_endpoint_auth_method ?? 'client_secret_basic',\n grant_types,\n response_types,\n ...(metadata.client_name !== undefined && { client_name: metadata.client_name }),\n ...(metadata.client_uri !== undefined && { client_uri: metadata.client_uri }),\n ...(metadata.logo_uri !== undefined && { logo_uri: metadata.logo_uri }),\n ...(metadata.scope !== undefined && { scope: metadata.scope }),\n ...(metadata.contacts !== undefined && { contacts: metadata.contacts }),\n ...(metadata.tos_uri !== undefined && { tos_uri: metadata.tos_uri }),\n ...(metadata.policy_uri !== undefined && { policy_uri: metadata.policy_uri }),\n ...(metadata.jwks_uri !== undefined && { jwks_uri: metadata.jwks_uri }),\n ...(metadata.jwks !== undefined && { jwks: metadata.jwks }),\n ...(metadata.software_id !== undefined && { software_id: metadata.software_id }),\n ...(metadata.software_version !== undefined && { software_version: metadata.software_version }),\n created_at: Date.now(),\n };\n\n // Store client\n await store.set(`dcr:client:${client_id}`, client);\n\n // Return client information (excluding internal created_at)\n const { created_at, ...clientInfo } = client;\n return clientInfo;\n}\n\n/**\n * Get a registered client by ID\n *\n * @param store - Keyv store for all DCR data\n * @param clientId - Client identifier\n * @returns Registered client or undefined if not found\n */\nexport async function getClient(store: Keyv, clientId: string): Promise<RegisteredClient | undefined> {\n return await store.get(`dcr:client:${clientId}`);\n}\n\n/**\n * Validate client credentials\n *\n * @param store - Keyv store for all DCR data\n * @param clientId - Client identifier\n * @param clientSecret - Client secret\n * @returns True if credentials are valid\n */\nexport async function validateClient(store: Keyv, clientId: string, clientSecret: string): Promise<boolean> {\n const client = await getClient(store, clientId);\n if (!client) return false;\n return client.client_secret === clientSecret;\n}\n\n/**\n * Validate redirect URI for a client\n *\n * @param store - Keyv store for all DCR data\n * @param clientId - Client identifier\n * @param redirectUri - Redirect URI to validate\n * @returns True if redirect URI is registered\n */\nexport async function validateRedirectUri(store: Keyv, clientId: string, redirectUri: string): Promise<boolean> {\n const client = await getClient(store, clientId);\n if (!client || !client.redirect_uris) return false;\n return client.redirect_uris.includes(redirectUri);\n}\n\n/**\n * List all registered clients (for debugging)\n *\n * Note: This method uses Keyv's iterator which may not be available on all storage adapters.\n * For production use, consider maintaining a separate index of client IDs.\n *\n * @param store - Keyv store for all DCR data\n * @returns Array of all registered clients\n */\nexport async function listClients(store: Keyv): Promise<RegisteredClient[]> {\n const clients: RegisteredClient[] = [];\n\n // Check if iterator is available on the store\n if (store.iterator) {\n // Use iterator with namespace to iterate through dcr:client: keys\n const iterator = store.iterator('dcr:client:');\n for await (const [_key, value] of iterator) {\n if (value !== undefined) {\n clients.push(value as RegisteredClient);\n }\n }\n }\n\n return clients;\n}\n\n/**\n * Delete a registered client\n *\n * @param store - Keyv store for all DCR data\n * @param clientId - Client identifier\n */\nexport async function deleteClient(store: Keyv, clientId: string): Promise<void> {\n await store.delete(`dcr:client:${clientId}`);\n}\n\n// ============================================================================\n// Provider Token Operations\n// ============================================================================\n\n/**\n * Store provider tokens for a DCR access token\n *\n * @param store - Keyv store for all DCR data\n * @param dcrToken - DCR-issued access token (used as key)\n * @param tokens - Google provider tokens (access, refresh, expiry)\n */\nexport async function setProviderTokens(store: Keyv, dcrToken: string, tokens: ProviderTokens): Promise<void> {\n await store.set(`dcr:provider:${dcrToken}`, tokens);\n}\n\n/**\n * Retrieve provider tokens for a DCR access token\n *\n * @param store - Keyv store for all DCR data\n * @param dcrToken - DCR-issued access token\n * @returns Provider tokens or undefined if not found\n */\nexport async function getProviderTokens(store: Keyv, dcrToken: string): Promise<ProviderTokens | undefined> {\n return await store.get(`dcr:provider:${dcrToken}`);\n}\n\n/**\n * Delete provider tokens for a DCR access token\n *\n * @param store - Keyv store for all DCR data\n * @param dcrToken - DCR-issued access token\n */\nexport async function deleteProviderTokens(store: Keyv, dcrToken: string): Promise<void> {\n await store.delete(`dcr:provider:${dcrToken}`);\n}\n\n// ============================================================================\n// Authorization Code Operations\n// ============================================================================\n\n/**\n * Store an authorization code\n *\n * @param store - Keyv store for all DCR data\n * @param code - Authorization code\n * @param authCode - Authorization code data\n */\nexport async function setAuthCode(store: Keyv, code: string, authCode: AuthorizationCode): Promise<void> {\n await store.set(`dcr:authcode:${code}`, authCode);\n}\n\n/**\n * Get an authorization code\n *\n * @param store - Keyv store for all DCR data\n * @param code - Authorization code\n * @returns Authorization code data or undefined if not found\n */\nexport async function getAuthCode(store: Keyv, code: string): Promise<AuthorizationCode | undefined> {\n return await store.get(`dcr:authcode:${code}`);\n}\n\n/**\n * Delete an authorization code\n *\n * @param store - Keyv store for all DCR data\n * @param code - Authorization code\n */\nexport async function deleteAuthCode(store: Keyv, code: string): Promise<void> {\n await store.delete(`dcr:authcode:${code}`);\n}\n\n// ============================================================================\n// Access Token Operations\n// ============================================================================\n\n/**\n * Store an access token\n *\n * @param store - Keyv store for all DCR data\n * @param token - Access token\n * @param tokenData - Access token data\n */\nexport async function setAccessToken(store: Keyv, token: string, tokenData: AccessToken): Promise<void> {\n await store.set(`dcr:access:${token}`, tokenData);\n}\n\n/**\n * Get an access token\n *\n * @param store - Keyv store for all DCR data\n * @param token - Access token\n * @returns Access token data or undefined if not found\n */\nexport async function getAccessToken(store: Keyv, token: string): Promise<AccessToken | undefined> {\n return await store.get(`dcr:access:${token}`);\n}\n\n/**\n * Delete an access token\n *\n * @param store - Keyv store for all DCR data\n * @param token - Access token\n */\nexport async function deleteAccessToken(store: Keyv, token: string): Promise<void> {\n await store.delete(`dcr:access:${token}`);\n}\n\n// ============================================================================\n// Refresh Token Operations\n// ============================================================================\n\n/**\n * Store a refresh token\n *\n * @param store - Keyv store for all DCR data\n * @param token - Refresh token\n * @param tokenData - Access token data (contains refresh token context)\n */\nexport async function setRefreshToken(store: Keyv, token: string, tokenData: AccessToken): Promise<void> {\n await store.set(`dcr:refresh:${token}`, tokenData);\n}\n\n/**\n * Get a refresh token\n *\n * @param store - Keyv store for all DCR data\n * @param token - Refresh token\n * @returns Access token data or undefined if not found\n */\nexport async function getRefreshToken(store: Keyv, token: string): Promise<AccessToken | undefined> {\n return await store.get(`dcr:refresh:${token}`);\n}\n\n/**\n * Delete a refresh token\n *\n * @param store - Keyv store for all DCR data\n * @param token - Refresh token\n */\nexport async function deleteRefreshToken(store: Keyv, token: string): Promise<void> {\n await store.delete(`dcr:refresh:${token}`);\n}\n"],"names":["randomUUID","registerClient","store","metadata","redirect_uris","length","Error","client_id","client_secret","grant_types","response_types","client","client_id_issued_at","Math","floor","Date","now","client_secret_expires_at","token_endpoint_auth_method","client_name","undefined","client_uri","logo_uri","scope","contacts","tos_uri","policy_uri","jwks_uri","jwks","software_id","software_version","created_at","set","clientInfo","getClient","clientId","get","validateClient","clientSecret","validateRedirectUri","redirectUri","includes","listClients","clients","iterator","_key","value","push","deleteClient","delete","setProviderTokens","dcrToken","tokens","getProviderTokens","deleteProviderTokens","setAuthCode","code","authCode","getAuthCode","deleteAuthCode","setAccessToken","token","tokenData","getAccessToken","deleteAccessToken","setRefreshToken","getRefreshToken","deleteRefreshToken"],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * DCR Token Verification Utilities
3
+ *
4
+ * Provides token verification for both self-hosted and external DCR modes:
5
+ * - Self-hosted: Verifies tokens against local DCR router (/oauth/verify)
6
+ * - External: Verifies tokens against Auth0/Stitch verification endpoint
7
+ */
8
+ import type { ProviderTokens } from '@mcp-z/oauth';
9
+ /**
10
+ * Verification result from DCR authorization server
11
+ */
12
+ export interface VerificationResult {
13
+ /** Bearer token that was verified */
14
+ token: string;
15
+ /** Client ID associated with the token */
16
+ clientId: string;
17
+ /** OAuth scopes granted to the token */
18
+ scopes: string[];
19
+ /** Token expiration timestamp (milliseconds since epoch) */
20
+ expiresAt: number;
21
+ /** Provider tokens (Google access/refresh tokens) */
22
+ providerTokens: ProviderTokens;
23
+ }
24
+ /**
25
+ * Verify bearer token against DCR authorization server
26
+ *
27
+ * Supports both self-hosted and external DCR modes by calling the
28
+ * /oauth/verify endpoint (or equivalent external URL).
29
+ *
30
+ * @param bearerToken - Bearer token to verify (without "Bearer " prefix)
31
+ * @param verifyUrl - Verification endpoint URL (self-hosted or external)
32
+ * @returns Verification result with provider tokens
33
+ * @throws Error if verification fails
34
+ *
35
+ * @example Self-hosted mode
36
+ * ```typescript
37
+ * const result = await verifyBearerToken(
38
+ * token,
39
+ * 'http://localhost:3456/oauth/verify'
40
+ * );
41
+ * const auth = provider.toAuth(result.providerTokens);
42
+ * ```
43
+ *
44
+ * @example External mode (Auth0/Stitch)
45
+ * ```typescript
46
+ * const result = await verifyBearerToken(
47
+ * token,
48
+ * 'https://auth.example.com/oauth/verify'
49
+ * );
50
+ * const auth = provider.toAuth(result.providerTokens);
51
+ * ```
52
+ */
53
+ export declare function verifyBearerToken(bearerToken: string, verifyUrl: string): Promise<VerificationResult>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * DCR Token Verification Utilities
3
+ *
4
+ * Provides token verification for both self-hosted and external DCR modes:
5
+ * - Self-hosted: Verifies tokens against local DCR router (/oauth/verify)
6
+ * - External: Verifies tokens against Auth0/Stitch verification endpoint
7
+ */ import { fetchWithTimeout } from './fetch-with-timeout.js';
8
+ /**
9
+ * Verify bearer token against DCR authorization server
10
+ *
11
+ * Supports both self-hosted and external DCR modes by calling the
12
+ * /oauth/verify endpoint (or equivalent external URL).
13
+ *
14
+ * @param bearerToken - Bearer token to verify (without "Bearer " prefix)
15
+ * @param verifyUrl - Verification endpoint URL (self-hosted or external)
16
+ * @returns Verification result with provider tokens
17
+ * @throws Error if verification fails
18
+ *
19
+ * @example Self-hosted mode
20
+ * ```typescript
21
+ * const result = await verifyBearerToken(
22
+ * token,
23
+ * 'http://localhost:3456/oauth/verify'
24
+ * );
25
+ * const auth = provider.toAuth(result.providerTokens);
26
+ * ```
27
+ *
28
+ * @example External mode (Auth0/Stitch)
29
+ * ```typescript
30
+ * const result = await verifyBearerToken(
31
+ * token,
32
+ * 'https://auth.example.com/oauth/verify'
33
+ * );
34
+ * const auth = provider.toAuth(result.providerTokens);
35
+ * ```
36
+ */ export async function verifyBearerToken(bearerToken, verifyUrl) {
37
+ const response = await fetchWithTimeout(verifyUrl, {
38
+ method: 'GET',
39
+ headers: {
40
+ Authorization: `Bearer ${bearerToken}`
41
+ }
42
+ });
43
+ if (!response.ok) {
44
+ const errorText = await response.text();
45
+ throw new Error(`Token verification failed: ${response.status} ${errorText}`);
46
+ }
47
+ const result = await response.json();
48
+ // Validate required fields
49
+ if (!result.providerTokens || !result.providerTokens.accessToken) {
50
+ throw new Error('Verification response missing required provider tokens');
51
+ }
52
+ return result;
53
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth-google/src/lib/dcr-verify.ts"],"sourcesContent":["/**\n * DCR Token Verification Utilities\n *\n * Provides token verification for both self-hosted and external DCR modes:\n * - Self-hosted: Verifies tokens against local DCR router (/oauth/verify)\n * - External: Verifies tokens against Auth0/Stitch verification endpoint\n */\n\nimport type { ProviderTokens } from '@mcp-z/oauth';\nimport { fetchWithTimeout } from './fetch-with-timeout.ts';\n\n/**\n * Verification result from DCR authorization server\n */\nexport interface VerificationResult {\n /** Bearer token that was verified */\n token: string;\n /** Client ID associated with the token */\n clientId: string;\n /** OAuth scopes granted to the token */\n scopes: string[];\n /** Token expiration timestamp (milliseconds since epoch) */\n expiresAt: number;\n /** Provider tokens (Google access/refresh tokens) */\n providerTokens: ProviderTokens;\n}\n\n/**\n * Verify bearer token against DCR authorization server\n *\n * Supports both self-hosted and external DCR modes by calling the\n * /oauth/verify endpoint (or equivalent external URL).\n *\n * @param bearerToken - Bearer token to verify (without \"Bearer \" prefix)\n * @param verifyUrl - Verification endpoint URL (self-hosted or external)\n * @returns Verification result with provider tokens\n * @throws Error if verification fails\n *\n * @example Self-hosted mode\n * ```typescript\n * const result = await verifyBearerToken(\n * token,\n * 'http://localhost:3456/oauth/verify'\n * );\n * const auth = provider.toAuth(result.providerTokens);\n * ```\n *\n * @example External mode (Auth0/Stitch)\n * ```typescript\n * const result = await verifyBearerToken(\n * token,\n * 'https://auth.example.com/oauth/verify'\n * );\n * const auth = provider.toAuth(result.providerTokens);\n * ```\n */\nexport async function verifyBearerToken(bearerToken: string, verifyUrl: string): Promise<VerificationResult> {\n const response = await fetchWithTimeout(verifyUrl, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${bearerToken}`,\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token verification failed: ${response.status} ${errorText}`);\n }\n\n const result = (await response.json()) as VerificationResult;\n\n // Validate required fields\n if (!result.providerTokens || !result.providerTokens.accessToken) {\n throw new Error('Verification response missing required provider tokens');\n }\n\n return result;\n}\n"],"names":["fetchWithTimeout","verifyBearerToken","bearerToken","verifyUrl","response","method","headers","Authorization","ok","errorText","text","Error","status","result","json","providerTokens","accessToken"],"mappings":"AAAA;;;;;;CAMC,GAGD,SAASA,gBAAgB,QAAQ,0BAA0B;AAkB3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BC,GACD,OAAO,eAAeC,kBAAkBC,WAAmB,EAAEC,SAAiB;IAC5E,MAAMC,WAAW,MAAMJ,iBAAiBG,WAAW;QACjDE,QAAQ;QACRC,SAAS;YACPC,eAAe,CAAC,OAAO,EAAEL,aAAa;QACxC;IACF;IAEA,IAAI,CAACE,SAASI,EAAE,EAAE;QAChB,MAAMC,YAAY,MAAML,SAASM,IAAI;QACrC,MAAM,IAAIC,MAAM,CAAC,2BAA2B,EAAEP,SAASQ,MAAM,CAAC,CAAC,EAAEH,WAAW;IAC9E;IAEA,MAAMI,SAAU,MAAMT,SAASU,IAAI;IAEnC,2BAA2B;IAC3B,IAAI,CAACD,OAAOE,cAAc,IAAI,CAACF,OAAOE,cAAc,CAACC,WAAW,EAAE;QAChE,MAAM,IAAIL,MAAM;IAClB;IAEA,OAAOE;AACT"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Fetch with timeout to prevent hanging requests
3
+ *
4
+ * This utility wraps the native fetch API with an AbortController to ensure
5
+ * requests don't hang indefinitely. This is critical for OAuth token operations
6
+ * where expired tokens might cause Google's servers to hang or respond slowly.
7
+ *
8
+ * @param url - URL to fetch
9
+ * @param options - Fetch options
10
+ * @param timeoutMs - Timeout in milliseconds (default: 30000)
11
+ * @returns Fetch response
12
+ * @throws Error if request times out
13
+ */
14
+ export declare function fetchWithTimeout(url: string, options?: RequestInit, timeoutMs?: number): Promise<Response>;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Fetch with timeout to prevent hanging requests
3
+ *
4
+ * This utility wraps the native fetch API with an AbortController to ensure
5
+ * requests don't hang indefinitely. This is critical for OAuth token operations
6
+ * where expired tokens might cause Google's servers to hang or respond slowly.
7
+ *
8
+ * @param url - URL to fetch
9
+ * @param options - Fetch options
10
+ * @param timeoutMs - Timeout in milliseconds (default: 30000)
11
+ * @returns Fetch response
12
+ * @throws Error if request times out
13
+ */ export async function fetchWithTimeout(url, options, timeoutMs = 30000) {
14
+ const controller = new AbortController();
15
+ const timeoutId = setTimeout(()=>controller.abort(), timeoutMs);
16
+ try {
17
+ const response = await fetch(url, {
18
+ ...options,
19
+ signal: controller.signal
20
+ });
21
+ clearTimeout(timeoutId);
22
+ return response;
23
+ } catch (error) {
24
+ clearTimeout(timeoutId);
25
+ if (error instanceof Error && error.name === 'AbortError') {
26
+ throw new Error(`Request timeout after ${timeoutMs}ms`);
27
+ }
28
+ throw error;
29
+ }
30
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth-google/src/lib/fetch-with-timeout.ts"],"sourcesContent":["/**\n * Fetch with timeout to prevent hanging requests\n *\n * This utility wraps the native fetch API with an AbortController to ensure\n * requests don't hang indefinitely. This is critical for OAuth token operations\n * where expired tokens might cause Google's servers to hang or respond slowly.\n *\n * @param url - URL to fetch\n * @param options - Fetch options\n * @param timeoutMs - Timeout in milliseconds (default: 30000)\n * @returns Fetch response\n * @throws Error if request times out\n */\nexport async function fetchWithTimeout(url: string, options?: RequestInit, timeoutMs = 30000): Promise<Response> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n });\n clearTimeout(timeoutId);\n return response;\n } catch (error) {\n clearTimeout(timeoutId);\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Request timeout after ${timeoutMs}ms`);\n }\n throw error;\n }\n}\n"],"names":["fetchWithTimeout","url","options","timeoutMs","controller","AbortController","timeoutId","setTimeout","abort","response","fetch","signal","clearTimeout","error","Error","name"],"mappings":"AAAA;;;;;;;;;;;;CAYC,GACD,OAAO,eAAeA,iBAAiBC,GAAW,EAAEC,OAAqB,EAAEC,YAAY,KAAK;IAC1F,MAAMC,aAAa,IAAIC;IACvB,MAAMC,YAAYC,WAAW,IAAMH,WAAWI,KAAK,IAAIL;IAEvD,IAAI;QACF,MAAMM,WAAW,MAAMC,MAAMT,KAAK;YAChC,GAAGC,OAAO;YACVS,QAAQP,WAAWO,MAAM;QAC3B;QACAC,aAAaN;QACb,OAAOG;IACT,EAAE,OAAOI,OAAO;QACdD,aAAaN;QACb,IAAIO,iBAAiBC,SAASD,MAAME,IAAI,KAAK,cAAc;YACzD,MAAM,IAAID,MAAM,CAAC,sBAAsB,EAAEX,UAAU,EAAE,CAAC;QACxD;QACA,MAAMU;IACR;AACF"}