@fresho/router 0.1.0 → 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 +24 -0
- package/dist/auth/index.d.mts +327 -0
- package/dist/auth/index.d.mts.map +1 -0
- package/dist/auth/index.mjs +279 -0
- package/dist/auth/index.mjs.map +1 -0
- package/dist/index.d.mts +51 -97
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +110 -53
- package/dist/index.mjs.map +1 -1
- package/dist/jwt-DHM_nSwL.d.mts +124 -0
- package/dist/jwt-DHM_nSwL.d.mts.map +1 -0
- package/dist/jwt-Dab9kOC2.mjs +208 -0
- package/dist/jwt-Dab9kOC2.mjs.map +1 -0
- package/dist/middleware/index.d.mts +2 -99
- package/dist/middleware/index.d.mts.map +1 -1
- package/dist/middleware/index.mjs +11 -150
- package/dist/middleware/index.mjs.map +1 -1
- package/dist/{middleware-CqtYOc6D.d.mts → middleware-Bxq0LHLS.d.mts} +1 -1
- package/dist/{middleware-CqtYOc6D.d.mts.map → middleware-Bxq0LHLS.d.mts.map} +1 -1
- package/dist/{middleware-BpFh0XrR.mjs → middleware-XMbIevSv.mjs} +1 -1
- package/dist/{middleware-BpFh0XrR.mjs.map → middleware-XMbIevSv.mjs.map} +1 -1
- package/docs/authentication.md +29 -2
- package/docs/middleware.md +10 -1
- package/package.json +6 -2
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
//#region src/auth/basic.ts
|
|
2
|
+
/**
|
|
3
|
+
* Parses Basic authentication credentials from an Authorization header value.
|
|
4
|
+
*
|
|
5
|
+
* @param authHeader - The full Authorization header value (e.g., 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=')
|
|
6
|
+
* @returns The parsed credentials, or null if parsing fails
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const header = request.headers.get('Authorization');
|
|
11
|
+
* const credentials = parseBasicAuth(header);
|
|
12
|
+
* if (credentials) {
|
|
13
|
+
* // Verify credentials
|
|
14
|
+
* const isValid = await verifyPassword(credentials.username, credentials.password);
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
function parseBasicAuth(authHeader) {
|
|
19
|
+
if (!authHeader?.startsWith("Basic ")) return null;
|
|
20
|
+
try {
|
|
21
|
+
const encoded = authHeader.slice(6);
|
|
22
|
+
const decoded = atob(encoded);
|
|
23
|
+
const colonIndex = decoded.indexOf(":");
|
|
24
|
+
if (colonIndex === -1) return null;
|
|
25
|
+
return {
|
|
26
|
+
username: decoded.substring(0, colonIndex),
|
|
27
|
+
password: decoded.substring(colonIndex + 1)
|
|
28
|
+
};
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Encodes credentials into a Basic authentication header value.
|
|
35
|
+
*
|
|
36
|
+
* @param username - The username
|
|
37
|
+
* @param password - The password
|
|
38
|
+
* @returns The Authorization header value (e.g., 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=')
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const authHeader = encodeBasicAuth('admin', 'secret');
|
|
43
|
+
* fetch('/api/endpoint', {
|
|
44
|
+
* headers: { Authorization: authHeader }
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
function encodeBasicAuth(username, password) {
|
|
49
|
+
return `Basic ${btoa(`${username}:${password}`)}`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Extracts the raw base64-encoded credentials from an Authorization header.
|
|
53
|
+
*
|
|
54
|
+
* @param authHeader - The full Authorization header value
|
|
55
|
+
* @returns The base64 string, or null if not a Basic auth header
|
|
56
|
+
*/
|
|
57
|
+
function extractBasicAuthToken(authHeader) {
|
|
58
|
+
if (!authHeader?.startsWith("Basic ")) return null;
|
|
59
|
+
return authHeader.slice(6);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/auth/jwt.ts
|
|
64
|
+
/** Maps JWT algorithm names to Web Crypto hash names. */
|
|
65
|
+
const ALGORITHM_MAP = {
|
|
66
|
+
HS256: "SHA-256",
|
|
67
|
+
HS384: "SHA-384",
|
|
68
|
+
HS512: "SHA-512"
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Parses a duration string (e.g., '1h', '7d', '30m') to seconds.
|
|
72
|
+
*/
|
|
73
|
+
function parseDuration(duration) {
|
|
74
|
+
if (typeof duration === "number") return duration;
|
|
75
|
+
const match = duration.match(/^(\d+)\s*(s|m|h|d|w)$/i);
|
|
76
|
+
if (!match) throw new Error(`Invalid duration format: ${duration}. Use formats like '1h', '7d', '30m', '60s'.`);
|
|
77
|
+
const value = parseInt(match[1], 10);
|
|
78
|
+
const unit = match[2].toLowerCase();
|
|
79
|
+
switch (unit) {
|
|
80
|
+
case "s": return value;
|
|
81
|
+
case "m": return value * 60;
|
|
82
|
+
case "h": return value * 60 * 60;
|
|
83
|
+
case "d": return value * 60 * 60 * 24;
|
|
84
|
+
case "w": return value * 60 * 60 * 24 * 7;
|
|
85
|
+
default: throw new Error(`Unknown duration unit: ${unit}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Base64url encodes a string or Uint8Array.
|
|
90
|
+
*/
|
|
91
|
+
function base64urlEncode(data) {
|
|
92
|
+
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
93
|
+
const binary = String.fromCharCode(...bytes);
|
|
94
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Signs a JWT token using Web Crypto API.
|
|
98
|
+
*
|
|
99
|
+
* Uses only Web Crypto API, compatible with Cloudflare Workers, Deno, and browsers.
|
|
100
|
+
*
|
|
101
|
+
* @param payload - The JWT payload (custom claims)
|
|
102
|
+
* @param secret - The signing secret (string, ArrayBuffer, or CryptoKey)
|
|
103
|
+
* @param options - Optional signing options (algorithm, expiresIn, issuer, etc.)
|
|
104
|
+
* @returns The signed JWT token string
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* // Basic usage (HS256 by default)
|
|
109
|
+
* const token = await jwtSign(
|
|
110
|
+
* { uid: 'user-123', role: 'admin' },
|
|
111
|
+
* 'your-secret-key',
|
|
112
|
+
* { expiresIn: '1h' }
|
|
113
|
+
* );
|
|
114
|
+
*
|
|
115
|
+
* // With HS512 algorithm
|
|
116
|
+
* const token = await jwtSign(
|
|
117
|
+
* { uid: 'user-123' },
|
|
118
|
+
* process.env.JWT_SECRET,
|
|
119
|
+
* { algorithm: 'HS512', expiresIn: '7d' }
|
|
120
|
+
* );
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
async function jwtSign(payload, secret, options = {}) {
|
|
124
|
+
const algorithm = options.algorithm || "HS256";
|
|
125
|
+
const hashAlg = ALGORITHM_MAP[algorithm];
|
|
126
|
+
const now = options.issuedAt ? options.issuedAt instanceof Date ? Math.floor(options.issuedAt.getTime() / 1e3) : options.issuedAt : Math.floor(Date.now() / 1e3);
|
|
127
|
+
const finalPayload = {
|
|
128
|
+
...payload,
|
|
129
|
+
iat: now
|
|
130
|
+
};
|
|
131
|
+
if (options.expiresIn !== void 0) finalPayload.exp = now + parseDuration(options.expiresIn);
|
|
132
|
+
if (options.notBefore !== void 0) finalPayload.nbf = now + parseDuration(options.notBefore);
|
|
133
|
+
if (options.issuer !== void 0) finalPayload.iss = options.issuer;
|
|
134
|
+
if (options.audience !== void 0) finalPayload.aud = options.audience;
|
|
135
|
+
if (options.subject !== void 0) finalPayload.sub = options.subject;
|
|
136
|
+
const header = {
|
|
137
|
+
alg: algorithm,
|
|
138
|
+
typ: "JWT"
|
|
139
|
+
};
|
|
140
|
+
const data = `${base64urlEncode(JSON.stringify(header))}.${base64urlEncode(JSON.stringify(finalPayload))}`;
|
|
141
|
+
let key;
|
|
142
|
+
if (secret instanceof CryptoKey) key = secret;
|
|
143
|
+
else {
|
|
144
|
+
const keyData = typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
|
|
145
|
+
key = await crypto.subtle.importKey("raw", keyData, {
|
|
146
|
+
name: "HMAC",
|
|
147
|
+
hash: hashAlg
|
|
148
|
+
}, false, ["sign"]);
|
|
149
|
+
}
|
|
150
|
+
const signatureBuffer = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(data));
|
|
151
|
+
return `${data}.${base64urlEncode(new Uint8Array(signatureBuffer))}`;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Verifies a JWT token using Web Crypto API.
|
|
155
|
+
*
|
|
156
|
+
* Uses only Web Crypto API, compatible with Cloudflare Workers, Deno, and browsers.
|
|
157
|
+
*
|
|
158
|
+
* @param token - The JWT token string to verify
|
|
159
|
+
* @param secret - The secret used to sign the token (string, ArrayBuffer, or CryptoKey)
|
|
160
|
+
* @param options - Optional verification options
|
|
161
|
+
* @returns The decoded JWT payload
|
|
162
|
+
* @throws Error if the token is invalid, expired, or not yet valid
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* import { jwtVerify } from '@fresho/router/auth';
|
|
167
|
+
*
|
|
168
|
+
* try {
|
|
169
|
+
* const payload = await jwtVerify(token, 'your-secret-key');
|
|
170
|
+
* console.log(payload.sub); // user ID
|
|
171
|
+
* } catch (error) {
|
|
172
|
+
* console.error('Invalid token:', error.message);
|
|
173
|
+
* }
|
|
174
|
+
*
|
|
175
|
+
* // With specific algorithms
|
|
176
|
+
* const payload = await jwtVerify(token, secret, { algorithms: ['HS512'] });
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
async function jwtVerify(token, secret, options = {}) {
|
|
180
|
+
const algorithms = options.algorithms || ["HS256"];
|
|
181
|
+
const parts = token.split(".");
|
|
182
|
+
if (parts.length !== 3) throw new Error("Invalid JWT format");
|
|
183
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
184
|
+
const header = JSON.parse(atob(headerB64.replace(/-/g, "+").replace(/_/g, "/")));
|
|
185
|
+
if (!algorithms.includes(header.alg)) throw new Error(`Unsupported algorithm: ${header.alg}`);
|
|
186
|
+
const hashAlg = ALGORITHM_MAP[header.alg];
|
|
187
|
+
if (!hashAlg) throw new Error(`Unknown algorithm: ${header.alg}`);
|
|
188
|
+
const payload = JSON.parse(atob(payloadB64.replace(/-/g, "+").replace(/_/g, "/")));
|
|
189
|
+
const data = `${headerB64}.${payloadB64}`;
|
|
190
|
+
const signature = Uint8Array.from(atob(signatureB64.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));
|
|
191
|
+
let key;
|
|
192
|
+
if (secret instanceof CryptoKey) key = secret;
|
|
193
|
+
else {
|
|
194
|
+
const keyData = typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
|
|
195
|
+
key = await crypto.subtle.importKey("raw", keyData, {
|
|
196
|
+
name: "HMAC",
|
|
197
|
+
hash: hashAlg
|
|
198
|
+
}, false, ["verify"]);
|
|
199
|
+
}
|
|
200
|
+
if (!await crypto.subtle.verify("HMAC", key, signature, new TextEncoder().encode(data))) throw new Error("Invalid signature");
|
|
201
|
+
if (payload.exp && payload.exp < Date.now() / 1e3) throw new Error("Token expired");
|
|
202
|
+
if (payload.nbf && payload.nbf > Date.now() / 1e3) throw new Error("Token not yet valid");
|
|
203
|
+
return payload;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
//#endregion
|
|
207
|
+
export { parseBasicAuth as a, extractBasicAuthToken as i, jwtVerify as n, encodeBasicAuth as r, jwtSign as t };
|
|
208
|
+
//# sourceMappingURL=jwt-Dab9kOC2.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt-Dab9kOC2.mjs","names":["ALGORITHM_MAP: Record<JwtAlgorithm, string>","finalPayload: JwtPayload","key: CryptoKey"],"sources":["../src/auth/basic.ts","../src/auth/jwt.ts"],"sourcesContent":["/**\n * @fileoverview Basic HTTP authentication utilities.\n *\n * Provides functions for parsing and encoding Basic authentication credentials.\n *\n * @example\n * ```typescript\n * import { parseBasicAuth, encodeBasicAuth } from '@fresho/router/auth';\n *\n * // Parse credentials from Authorization header\n * const credentials = parseBasicAuth('Basic dXNlcm5hbWU6cGFzc3dvcmQ=');\n * if (credentials) {\n * console.log(credentials.username); // 'username'\n * console.log(credentials.password); // 'password'\n * }\n *\n * // Create an Authorization header value\n * const header = encodeBasicAuth('username', 'password');\n * // 'Basic dXNlcm5hbWU6cGFzc3dvcmQ='\n * ```\n */\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/** Parsed Basic authentication credentials. */\nexport interface BasicCredentials {\n /** The username from the credentials. */\n username: string;\n /** The password from the credentials. */\n password: string;\n}\n\n// =============================================================================\n// Public API\n// =============================================================================\n\n/**\n * Parses Basic authentication credentials from an Authorization header value.\n *\n * @param authHeader - The full Authorization header value (e.g., 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=')\n * @returns The parsed credentials, or null if parsing fails\n *\n * @example\n * ```typescript\n * const header = request.headers.get('Authorization');\n * const credentials = parseBasicAuth(header);\n * if (credentials) {\n * // Verify credentials\n * const isValid = await verifyPassword(credentials.username, credentials.password);\n * }\n * ```\n */\nexport function parseBasicAuth(authHeader: string | null): BasicCredentials | null {\n if (!authHeader?.startsWith('Basic ')) {\n return null;\n }\n\n try {\n const encoded = authHeader.slice(6);\n const decoded = atob(encoded);\n const colonIndex = decoded.indexOf(':');\n\n if (colonIndex === -1) {\n return null;\n }\n\n return {\n username: decoded.substring(0, colonIndex),\n password: decoded.substring(colonIndex + 1),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Encodes credentials into a Basic authentication header value.\n *\n * @param username - The username\n * @param password - The password\n * @returns The Authorization header value (e.g., 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=')\n *\n * @example\n * ```typescript\n * const authHeader = encodeBasicAuth('admin', 'secret');\n * fetch('/api/endpoint', {\n * headers: { Authorization: authHeader }\n * });\n * ```\n */\nexport function encodeBasicAuth(username: string, password: string): string {\n return `Basic ${btoa(`${username}:${password}`)}`;\n}\n\n/**\n * Extracts the raw base64-encoded credentials from an Authorization header.\n *\n * @param authHeader - The full Authorization header value\n * @returns The base64 string, or null if not a Basic auth header\n */\nexport function extractBasicAuthToken(authHeader: string | null): string | null {\n if (!authHeader?.startsWith('Basic ')) {\n return null;\n }\n return authHeader.slice(6);\n}\n","/**\n * @fileoverview JWT signing and verification utilities.\n *\n * Provides functions for creating and validating JSON Web Tokens using\n * the Web Crypto API. Compatible with Cloudflare Workers, Deno, and browsers.\n *\n * @example\n * ```typescript\n * import { jwtSign, jwtVerify } from '@fresho/router/auth';\n *\n * // Sign a token\n * const token = await jwtSign(\n * { uid: 'user-123', role: 'admin' },\n * 'your-secret-key',\n * { expiresIn: '1h' }\n * );\n *\n * // Verify a token\n * const payload = await jwtVerify(token, 'your-secret-key');\n * console.log(payload.uid); // 'user-123'\n * ```\n */\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/** JWT payload type. */\nexport interface JwtPayload {\n /** Subject (user ID). */\n sub?: string;\n /** Issued at timestamp. */\n iat?: number;\n /** Expiration timestamp. */\n exp?: number;\n /** Not before timestamp. */\n nbf?: number;\n /** Issuer. */\n iss?: string;\n /** Audience. */\n aud?: string | string[];\n /** Additional claims. */\n [key: string]: unknown;\n}\n\n/** Supported HMAC algorithms for JWT signing/verification. */\nexport type JwtAlgorithm = 'HS256' | 'HS384' | 'HS512';\n\n/** JWT secret value. */\nexport type JwtSecret = string | ArrayBuffer | CryptoKey;\n\n/** Options for signing a JWT. */\nexport interface SignJwtOptions {\n /** Signing algorithm (default: 'HS256'). */\n algorithm?: JwtAlgorithm;\n /** Expiration time (e.g., '1h', '7d', '30m', or seconds as number). */\n expiresIn?: string | number;\n /** Not before time (e.g., '5m', or seconds as number). */\n notBefore?: string | number;\n /** Issuer claim. */\n issuer?: string;\n /** Audience claim. */\n audience?: string | string[];\n /** Subject claim. */\n subject?: string;\n /** Custom issued-at timestamp (defaults to now). */\n issuedAt?: number | Date;\n}\n\n/** Options for verifying a JWT. */\nexport interface VerifyJwtOptions {\n /** Allowed algorithms (default: ['HS256']). */\n algorithms?: JwtAlgorithm[];\n}\n\n// =============================================================================\n// Internal Helpers\n// =============================================================================\n\n/** Maps JWT algorithm names to Web Crypto hash names. */\nconst ALGORITHM_MAP: Record<JwtAlgorithm, string> = {\n HS256: 'SHA-256',\n HS384: 'SHA-384',\n HS512: 'SHA-512',\n};\n\n/**\n * Parses a duration string (e.g., '1h', '7d', '30m') to seconds.\n */\nfunction parseDuration(duration: string | number): number {\n if (typeof duration === 'number') return duration;\n\n const match = duration.match(/^(\\d+)\\s*(s|m|h|d|w)$/i);\n if (!match) {\n throw new Error(\n `Invalid duration format: ${duration}. Use formats like '1h', '7d', '30m', '60s'.`,\n );\n }\n\n const value = parseInt(match[1], 10);\n const unit = match[2].toLowerCase();\n\n switch (unit) {\n case 's':\n return value;\n case 'm':\n return value * 60;\n case 'h':\n return value * 60 * 60;\n case 'd':\n return value * 60 * 60 * 24;\n case 'w':\n return value * 60 * 60 * 24 * 7;\n default:\n throw new Error(`Unknown duration unit: ${unit}`);\n }\n}\n\n/**\n * Base64url encodes a string or Uint8Array.\n */\nfunction base64urlEncode(data: string | Uint8Array): string {\n const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;\n const binary = String.fromCharCode(...bytes);\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n// =============================================================================\n// Public API\n// =============================================================================\n\n/**\n * Signs a JWT token using Web Crypto API.\n *\n * Uses only Web Crypto API, compatible with Cloudflare Workers, Deno, and browsers.\n *\n * @param payload - The JWT payload (custom claims)\n * @param secret - The signing secret (string, ArrayBuffer, or CryptoKey)\n * @param options - Optional signing options (algorithm, expiresIn, issuer, etc.)\n * @returns The signed JWT token string\n *\n * @example\n * ```typescript\n * // Basic usage (HS256 by default)\n * const token = await jwtSign(\n * { uid: 'user-123', role: 'admin' },\n * 'your-secret-key',\n * { expiresIn: '1h' }\n * );\n *\n * // With HS512 algorithm\n * const token = await jwtSign(\n * { uid: 'user-123' },\n * process.env.JWT_SECRET,\n * { algorithm: 'HS512', expiresIn: '7d' }\n * );\n * ```\n */\nexport async function jwtSign(\n payload: Record<string, unknown>,\n secret: JwtSecret,\n options: SignJwtOptions = {},\n): Promise<string> {\n const algorithm = options.algorithm || 'HS256';\n const hashAlg = ALGORITHM_MAP[algorithm];\n\n const now = options.issuedAt\n ? options.issuedAt instanceof Date\n ? Math.floor(options.issuedAt.getTime() / 1000)\n : options.issuedAt\n : Math.floor(Date.now() / 1000);\n\n // Build the final payload with registered claims.\n const finalPayload: JwtPayload = {\n ...payload,\n iat: now,\n };\n\n if (options.expiresIn !== undefined) {\n finalPayload.exp = now + parseDuration(options.expiresIn);\n }\n\n if (options.notBefore !== undefined) {\n finalPayload.nbf = now + parseDuration(options.notBefore);\n }\n\n if (options.issuer !== undefined) {\n finalPayload.iss = options.issuer;\n }\n\n if (options.audience !== undefined) {\n finalPayload.aud = options.audience;\n }\n\n if (options.subject !== undefined) {\n finalPayload.sub = options.subject;\n }\n\n // Create header.\n const header = { alg: algorithm, typ: 'JWT' };\n\n // Encode header and payload.\n const headerB64 = base64urlEncode(JSON.stringify(header));\n const payloadB64 = base64urlEncode(JSON.stringify(finalPayload));\n const data = `${headerB64}.${payloadB64}`;\n\n // Import key for signing.\n let key: CryptoKey;\n if (secret instanceof CryptoKey) {\n key = secret;\n } else {\n const keyData = typeof secret === 'string' ? new TextEncoder().encode(secret) : secret;\n key = await crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: hashAlg }, false, [\n 'sign',\n ]);\n }\n\n // Sign the data.\n const signatureBuffer = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(data));\n\n const signatureB64 = base64urlEncode(new Uint8Array(signatureBuffer));\n\n return `${data}.${signatureB64}`;\n}\n\n/**\n * Verifies a JWT token using Web Crypto API.\n *\n * Uses only Web Crypto API, compatible with Cloudflare Workers, Deno, and browsers.\n *\n * @param token - The JWT token string to verify\n * @param secret - The secret used to sign the token (string, ArrayBuffer, or CryptoKey)\n * @param options - Optional verification options\n * @returns The decoded JWT payload\n * @throws Error if the token is invalid, expired, or not yet valid\n *\n * @example\n * ```typescript\n * import { jwtVerify } from '@fresho/router/auth';\n *\n * try {\n * const payload = await jwtVerify(token, 'your-secret-key');\n * console.log(payload.sub); // user ID\n * } catch (error) {\n * console.error('Invalid token:', error.message);\n * }\n *\n * // With specific algorithms\n * const payload = await jwtVerify(token, secret, { algorithms: ['HS512'] });\n * ```\n */\nexport async function jwtVerify(\n token: string,\n secret: JwtSecret,\n options: VerifyJwtOptions = {},\n): Promise<JwtPayload> {\n const algorithms = options.algorithms || ['HS256'];\n\n const parts = token.split('.');\n if (parts.length !== 3) {\n throw new Error('Invalid JWT format');\n }\n\n const [headerB64, payloadB64, signatureB64] = parts;\n\n // Decode header.\n const header = JSON.parse(atob(headerB64.replace(/-/g, '+').replace(/_/g, '/')));\n if (!algorithms.includes(header.alg)) {\n throw new Error(`Unsupported algorithm: ${header.alg}`);\n }\n\n // Get the hash algorithm for the JWT algorithm.\n const hashAlg = ALGORITHM_MAP[header.alg as JwtAlgorithm];\n if (!hashAlg) {\n throw new Error(`Unknown algorithm: ${header.alg}`);\n }\n\n // Decode payload.\n const payload = JSON.parse(atob(payloadB64.replace(/-/g, '+').replace(/_/g, '/')));\n\n // Verify signature using Web Crypto API.\n const data = `${headerB64}.${payloadB64}`;\n const signature = Uint8Array.from(atob(signatureB64.replace(/-/g, '+').replace(/_/g, '/')), (c) =>\n c.charCodeAt(0),\n );\n\n let key: CryptoKey;\n if (secret instanceof CryptoKey) {\n key = secret;\n } else {\n const keyData = typeof secret === 'string' ? new TextEncoder().encode(secret) : secret;\n key = await crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: hashAlg }, false, [\n 'verify',\n ]);\n }\n\n const valid = await crypto.subtle.verify('HMAC', key, signature, new TextEncoder().encode(data));\n\n if (!valid) {\n throw new Error('Invalid signature');\n }\n\n // Check expiration.\n if (payload.exp && payload.exp < Date.now() / 1000) {\n throw new Error('Token expired');\n }\n\n // Check not before.\n if (payload.nbf && payload.nbf > Date.now() / 1000) {\n throw new Error('Token not yet valid');\n }\n\n return payload as JwtPayload;\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { i as
|
|
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
|
+
import { i as compose, n as MiddlewareContext, t as Middleware } from "../middleware-Bxq0LHLS.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/middleware/auth.d.ts
|
|
4
5
|
|
|
@@ -27,49 +28,6 @@ interface BasicAuthOptions {
|
|
|
27
28
|
* ```
|
|
28
29
|
*/
|
|
29
30
|
declare function basicAuth(options: BasicAuthOptions): Middleware;
|
|
30
|
-
/** JWT payload type. */
|
|
31
|
-
interface JwtPayload {
|
|
32
|
-
/** Subject (user ID). */
|
|
33
|
-
sub?: string;
|
|
34
|
-
/** Issued at timestamp. */
|
|
35
|
-
iat?: number;
|
|
36
|
-
/** Expiration timestamp. */
|
|
37
|
-
exp?: number;
|
|
38
|
-
/** Not before timestamp. */
|
|
39
|
-
nbf?: number;
|
|
40
|
-
/** Issuer. */
|
|
41
|
-
iss?: string;
|
|
42
|
-
/** Audience. */
|
|
43
|
-
aud?: string | string[];
|
|
44
|
-
/** Additional claims. */
|
|
45
|
-
[key: string]: unknown;
|
|
46
|
-
}
|
|
47
|
-
/** Supported HMAC algorithms for JWT signing/verification. */
|
|
48
|
-
type JwtAlgorithm = 'HS256' | 'HS384' | 'HS512';
|
|
49
|
-
/** Options for signing a JWT. */
|
|
50
|
-
interface SignJwtOptions {
|
|
51
|
-
/** Signing algorithm (default: 'HS256'). */
|
|
52
|
-
algorithm?: JwtAlgorithm;
|
|
53
|
-
/** Expiration time (e.g., '1h', '7d', '30m', or seconds as number). */
|
|
54
|
-
expiresIn?: string | number;
|
|
55
|
-
/** Not before time (e.g., '5m', or seconds as number). */
|
|
56
|
-
notBefore?: string | number;
|
|
57
|
-
/** Issuer claim. */
|
|
58
|
-
issuer?: string;
|
|
59
|
-
/** Audience claim. */
|
|
60
|
-
audience?: string | string[];
|
|
61
|
-
/** Subject claim. */
|
|
62
|
-
subject?: string;
|
|
63
|
-
/** Custom issued-at timestamp (defaults to now). */
|
|
64
|
-
issuedAt?: number | Date;
|
|
65
|
-
}
|
|
66
|
-
/** JWT secret value. */
|
|
67
|
-
type JwtSecret = string | ArrayBuffer | CryptoKey;
|
|
68
|
-
/** Options for verifying a JWT. */
|
|
69
|
-
interface VerifyJwtOptions {
|
|
70
|
-
/** Allowed algorithms (default: ['HS256']). */
|
|
71
|
-
algorithms?: JwtAlgorithm[];
|
|
72
|
-
}
|
|
73
31
|
/** JWT authentication middleware configuration. */
|
|
74
32
|
interface JwtAuthOptions<Ctx = {}> {
|
|
75
33
|
/** Secret key or function to get the secret from context. */
|
|
@@ -84,61 +42,6 @@ interface JwtAuthOptions<Ctx = {}> {
|
|
|
84
42
|
/** Function to extract token from request (default: Authorization header or 'token' cookie). */
|
|
85
43
|
getToken?: (request: Request) => string | null;
|
|
86
44
|
}
|
|
87
|
-
/**
|
|
88
|
-
* Verifies a JWT token using Web Crypto API.
|
|
89
|
-
*
|
|
90
|
-
* Uses only Web Crypto API, compatible with Cloudflare Workers, Deno, and browsers.
|
|
91
|
-
*
|
|
92
|
-
* @param token - The JWT token string to verify
|
|
93
|
-
* @param secret - The secret used to sign the token (string, ArrayBuffer, or CryptoKey)
|
|
94
|
-
* @param options - Optional verification options
|
|
95
|
-
* @returns The decoded JWT payload
|
|
96
|
-
* @throws Error if the token is invalid, expired, or not yet valid
|
|
97
|
-
*
|
|
98
|
-
* @example
|
|
99
|
-
* ```typescript
|
|
100
|
-
* import { jwtVerify } from '@fresho/router/middleware';
|
|
101
|
-
*
|
|
102
|
-
* try {
|
|
103
|
-
* const payload = await jwtVerify(token, 'your-secret-key');
|
|
104
|
-
* console.log(payload.sub); // user ID
|
|
105
|
-
* } catch (error) {
|
|
106
|
-
* console.error('Invalid token:', error.message);
|
|
107
|
-
* }
|
|
108
|
-
*
|
|
109
|
-
* // With specific algorithms
|
|
110
|
-
* const payload = await jwtVerify(token, secret, { algorithms: ['HS512'] });
|
|
111
|
-
* ```
|
|
112
|
-
*/
|
|
113
|
-
declare function jwtVerify(token: string, secret: string | ArrayBuffer | CryptoKey, options?: VerifyJwtOptions): Promise<JwtPayload>;
|
|
114
|
-
/**
|
|
115
|
-
* Signs a JWT token using Web Crypto API.
|
|
116
|
-
*
|
|
117
|
-
* Uses only Web Crypto API, compatible with Cloudflare Workers, Deno, and browsers.
|
|
118
|
-
*
|
|
119
|
-
* @param payload - The JWT payload (custom claims)
|
|
120
|
-
* @param secret - The signing secret (string, ArrayBuffer, or CryptoKey)
|
|
121
|
-
* @param options - Optional signing options (algorithm, expiresIn, issuer, etc.)
|
|
122
|
-
* @returns The signed JWT token string
|
|
123
|
-
*
|
|
124
|
-
* @example
|
|
125
|
-
* ```typescript
|
|
126
|
-
* // Basic usage (HS256 by default)
|
|
127
|
-
* const token = await jwtSign(
|
|
128
|
-
* { uid: 'user-123', role: 'admin' },
|
|
129
|
-
* 'your-secret-key',
|
|
130
|
-
* { expiresIn: '1h' }
|
|
131
|
-
* );
|
|
132
|
-
*
|
|
133
|
-
* // With HS512 algorithm
|
|
134
|
-
* const token = await jwtSign(
|
|
135
|
-
* { uid: 'user-123' },
|
|
136
|
-
* process.env.JWT_SECRET,
|
|
137
|
-
* { algorithm: 'HS512', expiresIn: '7d' }
|
|
138
|
-
* );
|
|
139
|
-
* ```
|
|
140
|
-
*/
|
|
141
|
-
declare function jwtSign(payload: Record<string, unknown>, secret: JwtSecret, options?: SignJwtOptions): Promise<string>;
|
|
142
45
|
/**
|
|
143
46
|
* Creates a JWT authentication middleware.
|
|
144
47
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/middleware/auth.ts","../../src/middleware/common.ts","../../src/middleware/cors.ts"],"sourcesContent":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/middleware/auth.ts","../../src/middleware/common.ts","../../src/middleware/cors.ts"],"sourcesContent":[],"mappings":"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"}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { t as compose } from "../middleware-
|
|
1
|
+
import { t as compose } from "../middleware-XMbIevSv.mjs";
|
|
2
|
+
import { a as parseBasicAuth, n as jwtVerify, t as jwtSign } from "../jwt-Dab9kOC2.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/middleware/auth.ts
|
|
4
5
|
/**
|
|
6
|
+
* @fileoverview Authentication middleware for @fresho/router.
|
|
7
|
+
*
|
|
8
|
+
* Provides middleware wrappers for Basic HTTP, JWT, and Bearer token authentication.
|
|
9
|
+
* The underlying authentication utilities are in `@fresho/router/auth`.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
5
12
|
* Creates a Basic HTTP authentication middleware.
|
|
6
13
|
*
|
|
7
14
|
* @example
|
|
@@ -22,18 +29,13 @@ function basicAuth(options) {
|
|
|
22
29
|
const realm = options.realm || "Secure Area";
|
|
23
30
|
return async (context, next) => {
|
|
24
31
|
const { request } = context;
|
|
25
|
-
const
|
|
26
|
-
if (!
|
|
32
|
+
const credentials = parseBasicAuth(request.headers.get("Authorization"));
|
|
33
|
+
if (!credentials) return new Response("Unauthorized", {
|
|
27
34
|
status: 401,
|
|
28
35
|
headers: { "WWW-Authenticate": `Basic realm="${realm}", charset="UTF-8"` }
|
|
29
36
|
});
|
|
30
37
|
try {
|
|
31
|
-
const
|
|
32
|
-
const colonIndex = credentials.indexOf(":");
|
|
33
|
-
if (colonIndex === -1) throw new Error("Invalid credentials format");
|
|
34
|
-
const username = credentials.substring(0, colonIndex);
|
|
35
|
-
const password = credentials.substring(colonIndex + 1);
|
|
36
|
-
const claims = await options.verify(username, password, context);
|
|
38
|
+
const claims = await options.verify(credentials.username, credentials.password, context);
|
|
37
39
|
if (claims === null) return new Response("Invalid credentials", {
|
|
38
40
|
status: 401,
|
|
39
41
|
headers: { "WWW-Authenticate": `Basic realm="${realm}", charset="UTF-8"` }
|
|
@@ -48,147 +50,6 @@ function basicAuth(options) {
|
|
|
48
50
|
}
|
|
49
51
|
};
|
|
50
52
|
}
|
|
51
|
-
/** Maps JWT algorithm names to Web Crypto hash names. */
|
|
52
|
-
const ALGORITHM_MAP = {
|
|
53
|
-
HS256: "SHA-256",
|
|
54
|
-
HS384: "SHA-384",
|
|
55
|
-
HS512: "SHA-512"
|
|
56
|
-
};
|
|
57
|
-
/**
|
|
58
|
-
* Verifies a JWT token using Web Crypto API.
|
|
59
|
-
*
|
|
60
|
-
* Uses only Web Crypto API, compatible with Cloudflare Workers, Deno, and browsers.
|
|
61
|
-
*
|
|
62
|
-
* @param token - The JWT token string to verify
|
|
63
|
-
* @param secret - The secret used to sign the token (string, ArrayBuffer, or CryptoKey)
|
|
64
|
-
* @param options - Optional verification options
|
|
65
|
-
* @returns The decoded JWT payload
|
|
66
|
-
* @throws Error if the token is invalid, expired, or not yet valid
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* ```typescript
|
|
70
|
-
* import { jwtVerify } from '@fresho/router/middleware';
|
|
71
|
-
*
|
|
72
|
-
* try {
|
|
73
|
-
* const payload = await jwtVerify(token, 'your-secret-key');
|
|
74
|
-
* console.log(payload.sub); // user ID
|
|
75
|
-
* } catch (error) {
|
|
76
|
-
* console.error('Invalid token:', error.message);
|
|
77
|
-
* }
|
|
78
|
-
*
|
|
79
|
-
* // With specific algorithms
|
|
80
|
-
* const payload = await jwtVerify(token, secret, { algorithms: ['HS512'] });
|
|
81
|
-
* ```
|
|
82
|
-
*/
|
|
83
|
-
async function jwtVerify(token, secret, options = {}) {
|
|
84
|
-
const algorithms = options.algorithms || ["HS256"];
|
|
85
|
-
const parts = token.split(".");
|
|
86
|
-
if (parts.length !== 3) throw new Error("Invalid JWT format");
|
|
87
|
-
const [headerB64, payloadB64, signatureB64] = parts;
|
|
88
|
-
const header = JSON.parse(atob(headerB64.replace(/-/g, "+").replace(/_/g, "/")));
|
|
89
|
-
if (!algorithms.includes(header.alg)) throw new Error(`Unsupported algorithm: ${header.alg}`);
|
|
90
|
-
const hashAlg = ALGORITHM_MAP[header.alg];
|
|
91
|
-
if (!hashAlg) throw new Error(`Unknown algorithm: ${header.alg}`);
|
|
92
|
-
const payload = JSON.parse(atob(payloadB64.replace(/-/g, "+").replace(/_/g, "/")));
|
|
93
|
-
const data = `${headerB64}.${payloadB64}`;
|
|
94
|
-
const signature = Uint8Array.from(atob(signatureB64.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));
|
|
95
|
-
let key;
|
|
96
|
-
if (secret instanceof CryptoKey) key = secret;
|
|
97
|
-
else {
|
|
98
|
-
const keyData = typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
|
|
99
|
-
key = await crypto.subtle.importKey("raw", keyData, {
|
|
100
|
-
name: "HMAC",
|
|
101
|
-
hash: hashAlg
|
|
102
|
-
}, false, ["verify"]);
|
|
103
|
-
}
|
|
104
|
-
if (!await crypto.subtle.verify("HMAC", key, signature, new TextEncoder().encode(data))) throw new Error("Invalid signature");
|
|
105
|
-
if (payload.exp && payload.exp < Date.now() / 1e3) throw new Error("Token expired");
|
|
106
|
-
if (payload.nbf && payload.nbf > Date.now() / 1e3) throw new Error("Token not yet valid");
|
|
107
|
-
return payload;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Parses a duration string (e.g., '1h', '7d', '30m') to seconds.
|
|
111
|
-
*/
|
|
112
|
-
function parseDuration(duration) {
|
|
113
|
-
if (typeof duration === "number") return duration;
|
|
114
|
-
const match = duration.match(/^(\d+)\s*(s|m|h|d|w)$/i);
|
|
115
|
-
if (!match) throw new Error(`Invalid duration format: ${duration}. Use formats like '1h', '7d', '30m', '60s'.`);
|
|
116
|
-
const value = parseInt(match[1], 10);
|
|
117
|
-
const unit = match[2].toLowerCase();
|
|
118
|
-
switch (unit) {
|
|
119
|
-
case "s": return value;
|
|
120
|
-
case "m": return value * 60;
|
|
121
|
-
case "h": return value * 60 * 60;
|
|
122
|
-
case "d": return value * 60 * 60 * 24;
|
|
123
|
-
case "w": return value * 60 * 60 * 24 * 7;
|
|
124
|
-
default: throw new Error(`Unknown duration unit: ${unit}`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Base64url encodes a string or Uint8Array.
|
|
129
|
-
*/
|
|
130
|
-
function base64urlEncode(data) {
|
|
131
|
-
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
132
|
-
const binary = String.fromCharCode(...bytes);
|
|
133
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Signs a JWT token using Web Crypto API.
|
|
137
|
-
*
|
|
138
|
-
* Uses only Web Crypto API, compatible with Cloudflare Workers, Deno, and browsers.
|
|
139
|
-
*
|
|
140
|
-
* @param payload - The JWT payload (custom claims)
|
|
141
|
-
* @param secret - The signing secret (string, ArrayBuffer, or CryptoKey)
|
|
142
|
-
* @param options - Optional signing options (algorithm, expiresIn, issuer, etc.)
|
|
143
|
-
* @returns The signed JWT token string
|
|
144
|
-
*
|
|
145
|
-
* @example
|
|
146
|
-
* ```typescript
|
|
147
|
-
* // Basic usage (HS256 by default)
|
|
148
|
-
* const token = await jwtSign(
|
|
149
|
-
* { uid: 'user-123', role: 'admin' },
|
|
150
|
-
* 'your-secret-key',
|
|
151
|
-
* { expiresIn: '1h' }
|
|
152
|
-
* );
|
|
153
|
-
*
|
|
154
|
-
* // With HS512 algorithm
|
|
155
|
-
* const token = await jwtSign(
|
|
156
|
-
* { uid: 'user-123' },
|
|
157
|
-
* process.env.JWT_SECRET,
|
|
158
|
-
* { algorithm: 'HS512', expiresIn: '7d' }
|
|
159
|
-
* );
|
|
160
|
-
* ```
|
|
161
|
-
*/
|
|
162
|
-
async function jwtSign(payload, secret, options = {}) {
|
|
163
|
-
const algorithm = options.algorithm || "HS256";
|
|
164
|
-
const hashAlg = ALGORITHM_MAP[algorithm];
|
|
165
|
-
const now = options.issuedAt ? options.issuedAt instanceof Date ? Math.floor(options.issuedAt.getTime() / 1e3) : options.issuedAt : Math.floor(Date.now() / 1e3);
|
|
166
|
-
const finalPayload = {
|
|
167
|
-
...payload,
|
|
168
|
-
iat: now
|
|
169
|
-
};
|
|
170
|
-
if (options.expiresIn !== void 0) finalPayload.exp = now + parseDuration(options.expiresIn);
|
|
171
|
-
if (options.notBefore !== void 0) finalPayload.nbf = now + parseDuration(options.notBefore);
|
|
172
|
-
if (options.issuer !== void 0) finalPayload.iss = options.issuer;
|
|
173
|
-
if (options.audience !== void 0) finalPayload.aud = options.audience;
|
|
174
|
-
if (options.subject !== void 0) finalPayload.sub = options.subject;
|
|
175
|
-
const header = {
|
|
176
|
-
alg: algorithm,
|
|
177
|
-
typ: "JWT"
|
|
178
|
-
};
|
|
179
|
-
const data = `${base64urlEncode(JSON.stringify(header))}.${base64urlEncode(JSON.stringify(finalPayload))}`;
|
|
180
|
-
let key;
|
|
181
|
-
if (secret instanceof CryptoKey) key = secret;
|
|
182
|
-
else {
|
|
183
|
-
const keyData = typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
|
|
184
|
-
key = await crypto.subtle.importKey("raw", keyData, {
|
|
185
|
-
name: "HMAC",
|
|
186
|
-
hash: hashAlg
|
|
187
|
-
}, false, ["sign"]);
|
|
188
|
-
}
|
|
189
|
-
const signatureBuffer = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(data));
|
|
190
|
-
return `${data}.${base64urlEncode(new Uint8Array(signatureBuffer))}`;
|
|
191
|
-
}
|
|
192
53
|
/**
|
|
193
54
|
* Creates a JWT authentication middleware.
|
|
194
55
|
*
|