@fresho/router 0.0.4 → 0.3.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/README.md CHANGED
@@ -214,6 +214,30 @@ Built-in middleware: `cors`, `errorHandler`, `logger`, `rateLimit`, `requestId`,
214
214
 
215
215
  See **[Middleware Documentation](docs/middleware.md)** for detailed usage.
216
216
 
217
+ ## Authentication Utilities
218
+
219
+ For standalone auth utilities (JWT signing/verification, Basic auth, OAuth), import from `@fresho/router/auth`:
220
+
221
+ ```typescript
222
+ import { jwtSign, jwtVerify, parseBasicAuth } from "@fresho/router/auth";
223
+
224
+ // Sign a JWT
225
+ const token = await jwtSign({ uid: "user-123" }, secret, { expiresIn: "1h" });
226
+
227
+ // Verify a JWT
228
+ const payload = await jwtVerify(token, secret);
229
+
230
+ // Parse Basic auth header
231
+ const creds = parseBasicAuth(request.headers.get("Authorization"));
232
+ ```
233
+
234
+ Available utilities:
235
+ - **JWT**: `jwtSign`, `jwtVerify`
236
+ - **Basic Auth**: `parseBasicAuth`, `encodeBasicAuth`, `extractBasicAuthToken`
237
+ - **OAuth**: `encodeOAuthState`, `decodeOAuthState`, `exchangeCode`, `refreshAccessToken`, `revokeToken`, `buildAuthorizationUrl`, `OAUTH_PROVIDERS`
238
+
239
+ See **[Authentication Documentation](docs/authentication.md)** for detailed usage.
240
+
217
241
  ## HTTP Client
218
242
 
219
243
  Generate a typed client for your API:
@@ -0,0 +1,327 @@
1
+ import { a as VerifyJwtOptions, i as SignJwtOptions, n as JwtPayload, o as jwtSign, r as JwtSecret, s as jwtVerify, t as JwtAlgorithm } from "../jwt-DHM_nSwL.mjs";
2
+
3
+ //#region src/auth/basic.d.ts
4
+
5
+ /**
6
+ * @fileoverview Basic HTTP authentication utilities.
7
+ *
8
+ * Provides functions for parsing and encoding Basic authentication credentials.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { parseBasicAuth, encodeBasicAuth } from '@fresho/router/auth';
13
+ *
14
+ * // Parse credentials from Authorization header
15
+ * const credentials = parseBasicAuth('Basic dXNlcm5hbWU6cGFzc3dvcmQ=');
16
+ * if (credentials) {
17
+ * console.log(credentials.username); // 'username'
18
+ * console.log(credentials.password); // 'password'
19
+ * }
20
+ *
21
+ * // Create an Authorization header value
22
+ * const header = encodeBasicAuth('username', 'password');
23
+ * // 'Basic dXNlcm5hbWU6cGFzc3dvcmQ='
24
+ * ```
25
+ */
26
+ /** Parsed Basic authentication credentials. */
27
+ interface BasicCredentials {
28
+ /** The username from the credentials. */
29
+ username: string;
30
+ /** The password from the credentials. */
31
+ password: string;
32
+ }
33
+ /**
34
+ * Parses Basic authentication credentials from an Authorization header value.
35
+ *
36
+ * @param authHeader - The full Authorization header value (e.g., 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=')
37
+ * @returns The parsed credentials, or null if parsing fails
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * const header = request.headers.get('Authorization');
42
+ * const credentials = parseBasicAuth(header);
43
+ * if (credentials) {
44
+ * // Verify credentials
45
+ * const isValid = await verifyPassword(credentials.username, credentials.password);
46
+ * }
47
+ * ```
48
+ */
49
+ declare function parseBasicAuth(authHeader: string | null): BasicCredentials | null;
50
+ /**
51
+ * Encodes credentials into a Basic authentication header value.
52
+ *
53
+ * @param username - The username
54
+ * @param password - The password
55
+ * @returns The Authorization header value (e.g., 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=')
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * const authHeader = encodeBasicAuth('admin', 'secret');
60
+ * fetch('/api/endpoint', {
61
+ * headers: { Authorization: authHeader }
62
+ * });
63
+ * ```
64
+ */
65
+ declare function encodeBasicAuth(username: string, password: string): string;
66
+ /**
67
+ * Extracts the raw base64-encoded credentials from an Authorization header.
68
+ *
69
+ * @param authHeader - The full Authorization header value
70
+ * @returns The base64 string, or null if not a Basic auth header
71
+ */
72
+ declare function extractBasicAuthToken(authHeader: string | null): string | null;
73
+ //#endregion
74
+ //#region src/auth/oauth.d.ts
75
+ /**
76
+ * @fileoverview OAuth 2.0 utilities for @fresho/router.
77
+ *
78
+ * Provides reusable primitives for implementing OAuth 2.0 Authorization Code flows.
79
+ * This implementation is designed as a confidential client suitable for server-side
80
+ * applications that can securely store client secrets.
81
+ *
82
+ * ## Design Decisions
83
+ *
84
+ * ### Why Not PKCE?
85
+ *
86
+ * PKCE (Proof Key for Code Exchange, RFC 7636) is a security extension designed to
87
+ * protect against authorization code interception attacks. However:
88
+ *
89
+ * - PKCE is **required** only for public clients (mobile apps, SPAs without backend)
90
+ * - PKCE is **optional** for confidential clients that can securely store secrets
91
+ * - These utilities assume a confidential client with a `client_secret`
92
+ * - The `client_secret` provides equivalent protection during token exchange
93
+ *
94
+ * ### HMAC-Signed State Parameter
95
+ *
96
+ * Instead of PKCE, we provide HMAC-signed state utilities that:
97
+ *
98
+ * 1. **Prevent CSRF attacks** - The signature is verified on callback
99
+ * 2. **Carry custom data** - Embed arbitrary data (e.g., user ID) in the state
100
+ * 3. **Include a nonce** - Random bytes prevent replay attacks
101
+ * 4. **Use Web Crypto API** - Works in Cloudflare Workers, Deno, and browsers
102
+ *
103
+ * ## Provider Compatibility
104
+ *
105
+ * This implementation pattern works with ~60-70% of OAuth 2.0 providers:
106
+ *
107
+ * | Provider | PKCE | Auth Method | Compatible |
108
+ * |------------|----------|-------------|------------|
109
+ * | Google | Optional | Body | ✅ Yes |
110
+ * | GitHub | None | Basic Auth | ✅ Minor tweak |
111
+ * | Slack | None | Body | ✅ Yes |
112
+ * | LinkedIn | None | Body | ✅ Yes |
113
+ * | Spotify | Optional | Basic Auth | ✅ Yes |
114
+ * | Discord | Optional | Body | ✅ Yes |
115
+ * | Dropbox | None | Basic Auth | ✅ Minor tweak |
116
+ * | Twitter | Required | - | ❌ Needs PKCE |
117
+ * | TikTok | Required | - | ❌ Needs PKCE |
118
+ *
119
+ * @see https://tools.ietf.org/html/rfc6749 - OAuth 2.0 Authorization Framework
120
+ * @see https://tools.ietf.org/html/rfc7636 - PKCE Extension
121
+ *
122
+ * @example
123
+ * ```typescript
124
+ * import { encodeOAuthState, decodeOAuthState, exchangeCode } from '@fresho/router/middleware';
125
+ *
126
+ * // In your authorize route:
127
+ * const state = await encodeOAuthState({ uid: 'user-123' }, SECRET);
128
+ * const authUrl = `https://provider.com/oauth/authorize?state=${state}&...`;
129
+ *
130
+ * // In your callback route:
131
+ * const data = await decodeOAuthState(state, SECRET);
132
+ * if (data) {
133
+ * const tokens = await exchangeCode(code, { ... });
134
+ * }
135
+ * ```
136
+ */
137
+ /** OAuth 2.0 token response as per RFC 6749. */
138
+ interface OAuthTokenResponse {
139
+ /** The access token issued by the authorization server. */
140
+ access_token: string;
141
+ /** The refresh token (optional, returned with offline access). */
142
+ refresh_token?: string;
143
+ /** The type of token (typically "Bearer"). */
144
+ token_type: string;
145
+ /** The lifetime in seconds of the access token. */
146
+ expires_in: number;
147
+ /** The scopes granted by the authorization server. */
148
+ scope?: string;
149
+ /** OpenID Connect ID token (if openid scope was requested). */
150
+ id_token?: string;
151
+ }
152
+ /** OAuth 2.0 provider configuration. */
153
+ interface OAuthProvider {
154
+ /** Authorization endpoint URL. */
155
+ authorizationUrl: string;
156
+ /** Token endpoint URL. */
157
+ tokenUrl: string;
158
+ /** Token revocation endpoint URL (optional). */
159
+ revokeUrl?: string;
160
+ /** User info endpoint URL (optional). */
161
+ userInfoUrl?: string;
162
+ }
163
+ /** Common OAuth 2.0 providers with their endpoints. */
164
+ declare const OAUTH_PROVIDERS: Record<string, OAuthProvider>;
165
+ /** Token endpoint authentication method. */
166
+ type TokenAuthMethod = 'body' | 'basic';
167
+ /** Configuration for token exchange. */
168
+ interface ExchangeCodeOptions {
169
+ /** Token endpoint URL. */
170
+ tokenUrl: string;
171
+ /** OAuth client ID. */
172
+ clientId: string;
173
+ /** OAuth client secret. */
174
+ clientSecret: string;
175
+ /** Redirect URI used in authorization request. */
176
+ redirectUri: string;
177
+ /** Authentication method (default: 'body'). */
178
+ authMethod?: TokenAuthMethod;
179
+ }
180
+ /** Configuration for token revocation. */
181
+ interface RevokeTokenOptions {
182
+ /** Revocation endpoint URL. */
183
+ revokeUrl: string;
184
+ /** Token to revoke. */
185
+ token: string;
186
+ /** Token type hint (optional, e.g., 'refresh_token' or 'access_token'). */
187
+ tokenTypeHint?: string;
188
+ /** OAuth client ID (for providers that require it). */
189
+ clientId?: string;
190
+ /** OAuth client secret (for providers that require it). */
191
+ clientSecret?: string;
192
+ /** Authentication method (default: 'body'). */
193
+ authMethod?: TokenAuthMethod;
194
+ }
195
+ /**
196
+ * Encodes arbitrary data into an HMAC-signed state parameter.
197
+ *
198
+ * The state is base64url-encoded and includes:
199
+ * - The JSON-serialized payload
200
+ * - A random nonce (12 bytes) for replay protection
201
+ * - An HMAC-SHA256 signature (truncated to 16 hex chars)
202
+ *
203
+ * @param data - Arbitrary data to encode in the state
204
+ * @param secret - Secret key for HMAC signing
205
+ * @returns Base64url-encoded state string
206
+ *
207
+ * @example
208
+ * ```typescript
209
+ * const state = await encodeOAuthState({ uid: 'user-123', returnTo: '/dashboard' }, SECRET);
210
+ * // Use in OAuth authorization URL
211
+ * ```
212
+ */
213
+ declare function encodeOAuthState(data: Record<string, unknown>, secret: string): Promise<string>;
214
+ /**
215
+ * Decodes and verifies an HMAC-signed state parameter.
216
+ *
217
+ * @param state - Base64url-encoded state string
218
+ * @param secret - Secret key for HMAC verification
219
+ * @returns The decoded data, or null if verification fails
220
+ *
221
+ * @example
222
+ * ```typescript
223
+ * const data = await decodeOAuthState(state, SECRET);
224
+ * if (data) {
225
+ * console.log(data.uid); // 'user-123'
226
+ * }
227
+ * ```
228
+ */
229
+ declare function decodeOAuthState<T extends Record<string, unknown> = Record<string, unknown>>(state: string, secret: string): Promise<T | null>;
230
+ /**
231
+ * Exchanges an authorization code for tokens.
232
+ *
233
+ * @param code - Authorization code from OAuth callback
234
+ * @param options - Token exchange configuration
235
+ * @returns Token response from the OAuth provider
236
+ * @throws Error if token exchange fails
237
+ *
238
+ * @example
239
+ * ```typescript
240
+ * const tokens = await exchangeCode(code, {
241
+ * tokenUrl: OAUTH_PROVIDERS.google.tokenUrl,
242
+ * clientId: env.CLIENT_ID,
243
+ * clientSecret: env.CLIENT_SECRET,
244
+ * redirectUri: 'https://example.com/callback',
245
+ * });
246
+ * ```
247
+ */
248
+ declare function exchangeCode(code: string, options: ExchangeCodeOptions): Promise<OAuthTokenResponse>;
249
+ /**
250
+ * Refreshes an access token using a refresh token.
251
+ *
252
+ * @param refreshToken - The refresh token
253
+ * @param options - Token exchange configuration (same as exchangeCode)
254
+ * @returns New token response from the OAuth provider
255
+ * @throws Error if token refresh fails
256
+ *
257
+ * @example
258
+ * ```typescript
259
+ * const tokens = await refreshAccessToken(refreshToken, {
260
+ * tokenUrl: OAUTH_PROVIDERS.google.tokenUrl,
261
+ * clientId: env.CLIENT_ID,
262
+ * clientSecret: env.CLIENT_SECRET,
263
+ * redirectUri: 'https://example.com/callback',
264
+ * });
265
+ * ```
266
+ */
267
+ declare function refreshAccessToken(refreshToken: string, options: Omit<ExchangeCodeOptions, 'redirectUri'>): Promise<OAuthTokenResponse>;
268
+ /**
269
+ * Revokes an OAuth token.
270
+ *
271
+ * @param options - Token revocation configuration
272
+ * @returns True if revocation succeeded
273
+ * @throws Error if revocation fails
274
+ *
275
+ * @example
276
+ * ```typescript
277
+ * await revokeToken({
278
+ * revokeUrl: OAUTH_PROVIDERS.google.revokeUrl!,
279
+ * token: refreshToken,
280
+ * tokenTypeHint: 'refresh_token',
281
+ * });
282
+ * ```
283
+ */
284
+ declare function revokeToken(options: RevokeTokenOptions): Promise<boolean>;
285
+ /** Options for building an authorization URL. */
286
+ interface AuthorizationUrlOptions {
287
+ /** Authorization endpoint URL. */
288
+ authorizationUrl: string;
289
+ /** OAuth client ID. */
290
+ clientId: string;
291
+ /** Redirect URI for OAuth callback. */
292
+ redirectUri: string;
293
+ /** Requested scopes. */
294
+ scopes: string[];
295
+ /** State parameter (use encodeOAuthState to generate). */
296
+ state: string;
297
+ /** Response type (default: 'code'). */
298
+ responseType?: string;
299
+ /** Additional provider-specific parameters. */
300
+ extraParams?: Record<string, string>;
301
+ }
302
+ /**
303
+ * Builds an OAuth authorization URL.
304
+ *
305
+ * @param options - Authorization URL configuration
306
+ * @returns The complete authorization URL
307
+ *
308
+ * @example
309
+ * ```typescript
310
+ * const state = await encodeOAuthState({ uid: 'user-123' }, SECRET);
311
+ * const authUrl = buildAuthorizationUrl({
312
+ * authorizationUrl: OAUTH_PROVIDERS.google.authorizationUrl,
313
+ * clientId: env.CLIENT_ID,
314
+ * redirectUri: 'https://example.com/callback',
315
+ * scopes: ['openid', 'email', 'profile'],
316
+ * state,
317
+ * extraParams: {
318
+ * access_type: 'offline',
319
+ * prompt: 'consent',
320
+ * },
321
+ * });
322
+ * ```
323
+ */
324
+ declare function buildAuthorizationUrl(options: AuthorizationUrlOptions): string;
325
+ //#endregion
326
+ export { type AuthorizationUrlOptions, type BasicCredentials, type ExchangeCodeOptions, type JwtAlgorithm, type JwtPayload, type JwtSecret, OAUTH_PROVIDERS, type OAuthProvider, type OAuthTokenResponse, type RevokeTokenOptions, type SignJwtOptions, type TokenAuthMethod, type VerifyJwtOptions, buildAuthorizationUrl, decodeOAuthState, encodeBasicAuth, encodeOAuthState, exchangeCode, extractBasicAuthToken, jwtSign, jwtVerify, parseBasicAuth, refreshAccessToken, revokeToken };
327
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/auth/basic.ts","../../src/auth/oauth.ts"],"sourcesContent":[],"mappings":";;;;;;;AA2BA;AA2BA;AAsCA;AAUA;;;;AClCA;AAgBA;AAYA;AA8CA;AAGA;AAcA;AAqCA;AAmDA;;;;;AAGU,UD/NO,gBAAA,CC+NP;EA6DY;EAEX,QAAA,EAAA,MAAA;EACA;EAAR,QAAA,EAAA,MAAA;;AAqDH;;;;;;AAqDA;AAkCA;AAuCA;;;;;;;;iBDvbgB,cAAA,6BAA2C;;;;;;;;;;;;;;;;iBAsC3C,eAAA;;;;;;;iBAUA,qBAAA;;;;;;AA3EhB;AA2BA;AAsCA;AAUA;;;;AClCA;AAgBA;AAYA;AA8CA;AAGA;AAcA;AAqCA;AAmDA;;;;;;AAgEA;;;;;AAwDA;;;;;;AAqDA;AAkCA;AAuCA;;;;;;;;;;;;;;;;;;;;;;;;;;;UAzaiB,kBAAA;;;;;;;;;;;;;;;UAgBA,aAAA;;;;;;;;;;;cAYJ,iBAAiB,eAAe;;KA8CjC,eAAA;;UAGK,mBAAA;;;;;;;;;;eAUF;;;UAIE,kBAAA;;;;;;;;;;;;eAYF;;;;;;;;;;;;;;;;;;;;iBAyBO,gBAAA,OACd,0CAEL;;;;;;;;;;;;;;;;iBAgDmB,2BAA2B,0BAA0B,yDAGxE,QAAQ;;;;;;;;;;;;;;;;;;;iBA6DW,YAAA,wBAEX,sBACR,QAAQ;;;;;;;;;;;;;;;;;;;iBAqDW,kBAAA,gCAEX,KAAK,sCACb,QAAQ;;;;;;;;;;;;;;;;;iBAkDW,WAAA,UAAqB,qBAAqB;;UAkC/C,uBAAA;;;;;;;;;;;;;;gBAcD;;;;;;;;;;;;;;;;;;;;;;;;iBAyBA,qBAAA,UAA+B"}
@@ -0,0 +1,279 @@
1
+ import { a as parseBasicAuth, i as extractBasicAuthToken, n as jwtVerify, r as encodeBasicAuth, t as jwtSign } from "../jwt-Dab9kOC2.mjs";
2
+
3
+ //#region src/auth/oauth.ts
4
+ /** Common OAuth 2.0 providers with their endpoints. */
5
+ const OAUTH_PROVIDERS = {
6
+ google: {
7
+ authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth",
8
+ tokenUrl: "https://oauth2.googleapis.com/token",
9
+ revokeUrl: "https://oauth2.googleapis.com/revoke",
10
+ userInfoUrl: "https://www.googleapis.com/oauth2/v3/userinfo"
11
+ },
12
+ github: {
13
+ authorizationUrl: "https://github.com/login/oauth/authorize",
14
+ tokenUrl: "https://github.com/login/oauth/access_token",
15
+ revokeUrl: void 0,
16
+ userInfoUrl: "https://api.github.com/user"
17
+ },
18
+ discord: {
19
+ authorizationUrl: "https://discord.com/oauth2/authorize",
20
+ tokenUrl: "https://discord.com/api/oauth2/token",
21
+ revokeUrl: "https://discord.com/api/oauth2/token/revoke",
22
+ userInfoUrl: "https://discord.com/api/users/@me"
23
+ },
24
+ slack: {
25
+ authorizationUrl: "https://slack.com/openid/connect/authorize",
26
+ tokenUrl: "https://slack.com/api/openid.connect.token",
27
+ revokeUrl: void 0,
28
+ userInfoUrl: void 0
29
+ },
30
+ linkedin: {
31
+ authorizationUrl: "https://www.linkedin.com/oauth/v2/authorization",
32
+ tokenUrl: "https://www.linkedin.com/oauth/v2/accessToken",
33
+ revokeUrl: void 0,
34
+ userInfoUrl: "https://api.linkedin.com/v2/userinfo"
35
+ },
36
+ spotify: {
37
+ authorizationUrl: "https://accounts.spotify.com/authorize",
38
+ tokenUrl: "https://accounts.spotify.com/api/token",
39
+ revokeUrl: void 0,
40
+ userInfoUrl: "https://api.spotify.com/v1/me"
41
+ },
42
+ dropbox: {
43
+ authorizationUrl: "https://www.dropbox.com/oauth2/authorize",
44
+ tokenUrl: "https://api.dropboxapi.com/oauth2/token",
45
+ revokeUrl: "https://api.dropboxapi.com/2/auth/token/revoke",
46
+ userInfoUrl: void 0
47
+ }
48
+ };
49
+ /**
50
+ * Encodes arbitrary data into an HMAC-signed state parameter.
51
+ *
52
+ * The state is base64url-encoded and includes:
53
+ * - The JSON-serialized payload
54
+ * - A random nonce (12 bytes) for replay protection
55
+ * - An HMAC-SHA256 signature (truncated to 16 hex chars)
56
+ *
57
+ * @param data - Arbitrary data to encode in the state
58
+ * @param secret - Secret key for HMAC signing
59
+ * @returns Base64url-encoded state string
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const state = await encodeOAuthState({ uid: 'user-123', returnTo: '/dashboard' }, SECRET);
64
+ * // Use in OAuth authorization URL
65
+ * ```
66
+ */
67
+ async function encodeOAuthState(data, secret) {
68
+ const nonceBytes = new Uint8Array(12);
69
+ crypto.getRandomValues(nonceBytes);
70
+ const nonce = btoa(String.fromCharCode(...nonceBytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
71
+ const payload = JSON.stringify({
72
+ ...data,
73
+ _nonce: nonce
74
+ });
75
+ const encoder = new TextEncoder();
76
+ const key = await crypto.subtle.importKey("raw", encoder.encode(secret), {
77
+ name: "HMAC",
78
+ hash: "SHA-256"
79
+ }, false, ["sign"]);
80
+ const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
81
+ const signatureHex = Array.from(new Uint8Array(signature)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 16);
82
+ const stateData = JSON.stringify({
83
+ payload,
84
+ sig: signatureHex
85
+ });
86
+ return btoa(stateData).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
87
+ }
88
+ /**
89
+ * Decodes and verifies an HMAC-signed state parameter.
90
+ *
91
+ * @param state - Base64url-encoded state string
92
+ * @param secret - Secret key for HMAC verification
93
+ * @returns The decoded data, or null if verification fails
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * const data = await decodeOAuthState(state, SECRET);
98
+ * if (data) {
99
+ * console.log(data.uid); // 'user-123'
100
+ * }
101
+ * ```
102
+ */
103
+ async function decodeOAuthState(state, secret) {
104
+ try {
105
+ const base64 = state.replace(/-/g, "+").replace(/_/g, "/");
106
+ const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
107
+ const { payload, sig } = JSON.parse(atob(padded));
108
+ const encoder = new TextEncoder();
109
+ const key = await crypto.subtle.importKey("raw", encoder.encode(secret), {
110
+ name: "HMAC",
111
+ hash: "SHA-256"
112
+ }, false, ["sign"]);
113
+ const expectedSignature = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
114
+ if (sig !== Array.from(new Uint8Array(expectedSignature)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 16)) return null;
115
+ const payloadData = JSON.parse(payload);
116
+ delete payloadData._nonce;
117
+ return payloadData;
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+ /**
123
+ * Exchanges an authorization code for tokens.
124
+ *
125
+ * @param code - Authorization code from OAuth callback
126
+ * @param options - Token exchange configuration
127
+ * @returns Token response from the OAuth provider
128
+ * @throws Error if token exchange fails
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * const tokens = await exchangeCode(code, {
133
+ * tokenUrl: OAUTH_PROVIDERS.google.tokenUrl,
134
+ * clientId: env.CLIENT_ID,
135
+ * clientSecret: env.CLIENT_SECRET,
136
+ * redirectUri: 'https://example.com/callback',
137
+ * });
138
+ * ```
139
+ */
140
+ async function exchangeCode(code, options) {
141
+ const { tokenUrl, clientId, clientSecret, redirectUri, authMethod = "body" } = options;
142
+ const body = new URLSearchParams({
143
+ grant_type: "authorization_code",
144
+ code,
145
+ redirect_uri: redirectUri
146
+ });
147
+ const headers = {
148
+ "Content-Type": "application/x-www-form-urlencoded",
149
+ Accept: "application/json"
150
+ };
151
+ if (authMethod === "basic") headers.Authorization = `Basic ${btoa(`${clientId}:${clientSecret}`)}`;
152
+ else {
153
+ body.set("client_id", clientId);
154
+ body.set("client_secret", clientSecret);
155
+ }
156
+ const response = await fetch(tokenUrl, {
157
+ method: "POST",
158
+ headers,
159
+ body: body.toString()
160
+ });
161
+ if (!response.ok) {
162
+ const errorText = await response.text();
163
+ throw new Error(`Token exchange failed: ${response.status} ${errorText}`);
164
+ }
165
+ return response.json();
166
+ }
167
+ /**
168
+ * Refreshes an access token using a refresh token.
169
+ *
170
+ * @param refreshToken - The refresh token
171
+ * @param options - Token exchange configuration (same as exchangeCode)
172
+ * @returns New token response from the OAuth provider
173
+ * @throws Error if token refresh fails
174
+ *
175
+ * @example
176
+ * ```typescript
177
+ * const tokens = await refreshAccessToken(refreshToken, {
178
+ * tokenUrl: OAUTH_PROVIDERS.google.tokenUrl,
179
+ * clientId: env.CLIENT_ID,
180
+ * clientSecret: env.CLIENT_SECRET,
181
+ * redirectUri: 'https://example.com/callback',
182
+ * });
183
+ * ```
184
+ */
185
+ async function refreshAccessToken(refreshToken, options) {
186
+ const { tokenUrl, clientId, clientSecret, authMethod = "body" } = options;
187
+ const body = new URLSearchParams({
188
+ grant_type: "refresh_token",
189
+ refresh_token: refreshToken
190
+ });
191
+ const headers = {
192
+ "Content-Type": "application/x-www-form-urlencoded",
193
+ Accept: "application/json"
194
+ };
195
+ if (authMethod === "basic") headers.Authorization = `Basic ${btoa(`${clientId}:${clientSecret}`)}`;
196
+ else {
197
+ body.set("client_id", clientId);
198
+ body.set("client_secret", clientSecret);
199
+ }
200
+ const response = await fetch(tokenUrl, {
201
+ method: "POST",
202
+ headers,
203
+ body: body.toString()
204
+ });
205
+ if (!response.ok) {
206
+ const errorText = await response.text();
207
+ throw new Error(`Token refresh failed: ${response.status} ${errorText}`);
208
+ }
209
+ return response.json();
210
+ }
211
+ /**
212
+ * Revokes an OAuth token.
213
+ *
214
+ * @param options - Token revocation configuration
215
+ * @returns True if revocation succeeded
216
+ * @throws Error if revocation fails
217
+ *
218
+ * @example
219
+ * ```typescript
220
+ * await revokeToken({
221
+ * revokeUrl: OAUTH_PROVIDERS.google.revokeUrl!,
222
+ * token: refreshToken,
223
+ * tokenTypeHint: 'refresh_token',
224
+ * });
225
+ * ```
226
+ */
227
+ async function revokeToken(options) {
228
+ const { revokeUrl, token, tokenTypeHint, clientId, clientSecret, authMethod = "body" } = options;
229
+ const body = new URLSearchParams({ token });
230
+ if (tokenTypeHint) body.set("token_type_hint", tokenTypeHint);
231
+ const headers = { "Content-Type": "application/x-www-form-urlencoded" };
232
+ if (authMethod === "basic" && clientId && clientSecret) headers.Authorization = `Basic ${btoa(`${clientId}:${clientSecret}`)}`;
233
+ else if (clientId && clientSecret) {
234
+ body.set("client_id", clientId);
235
+ body.set("client_secret", clientSecret);
236
+ }
237
+ return (await fetch(revokeUrl, {
238
+ method: "POST",
239
+ headers,
240
+ body: body.toString()
241
+ })).ok;
242
+ }
243
+ /**
244
+ * Builds an OAuth authorization URL.
245
+ *
246
+ * @param options - Authorization URL configuration
247
+ * @returns The complete authorization URL
248
+ *
249
+ * @example
250
+ * ```typescript
251
+ * const state = await encodeOAuthState({ uid: 'user-123' }, SECRET);
252
+ * const authUrl = buildAuthorizationUrl({
253
+ * authorizationUrl: OAUTH_PROVIDERS.google.authorizationUrl,
254
+ * clientId: env.CLIENT_ID,
255
+ * redirectUri: 'https://example.com/callback',
256
+ * scopes: ['openid', 'email', 'profile'],
257
+ * state,
258
+ * extraParams: {
259
+ * access_type: 'offline',
260
+ * prompt: 'consent',
261
+ * },
262
+ * });
263
+ * ```
264
+ */
265
+ function buildAuthorizationUrl(options) {
266
+ const { authorizationUrl, clientId, redirectUri, scopes, state, responseType = "code", extraParams = {} } = options;
267
+ return `${authorizationUrl}?${new URLSearchParams({
268
+ response_type: responseType,
269
+ client_id: clientId,
270
+ redirect_uri: redirectUri,
271
+ scope: scopes.join(" "),
272
+ state,
273
+ ...extraParams
274
+ }).toString()}`;
275
+ }
276
+
277
+ //#endregion
278
+ export { OAUTH_PROVIDERS, buildAuthorizationUrl, decodeOAuthState, encodeBasicAuth, encodeOAuthState, exchangeCode, extractBasicAuthToken, jwtSign, jwtVerify, parseBasicAuth, refreshAccessToken, revokeToken };
279
+ //# sourceMappingURL=index.mjs.map