@xivdyetools/auth 1.0.2 → 1.1.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 +171 -158
- package/dist/hmac.d.ts +6 -0
- package/dist/hmac.d.ts.map +1 -1
- package/dist/hmac.js +49 -7
- package/dist/hmac.js.map +1 -1
- package/dist/jwt.d.ts.map +1 -1
- package/dist/jwt.js +44 -51
- package/dist/jwt.js.map +1 -1
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +4 -2
- package/dist/timing.js.map +1 -1
- package/package.json +75 -71
- package/src/discord.test.ts +243 -243
- package/src/discord.ts +143 -143
- package/src/hmac.test.ts +339 -325
- package/src/hmac.ts +274 -222
- package/src/index.ts +54 -54
- package/src/jwt.test.ts +337 -337
- package/src/jwt.ts +248 -265
- package/src/timing.test.ts +114 -117
- package/src/timing.ts +86 -84
package/src/hmac.ts
CHANGED
|
@@ -1,222 +1,274 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HMAC Signing Utilities
|
|
3
|
-
*
|
|
4
|
-
* Provides HMAC-SHA256 signing and verification using the Web Crypto API.
|
|
5
|
-
* Used for JWT signing and bot request authentication.
|
|
6
|
-
*
|
|
7
|
-
* @module hmac
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
base64UrlEncodeBytes,
|
|
12
|
-
base64UrlDecodeBytes,
|
|
13
|
-
bytesToHex,
|
|
14
|
-
hexToBytes,
|
|
15
|
-
} from '@xivdyetools/crypto';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Options for bot signature verification
|
|
19
|
-
*/
|
|
20
|
-
export interface BotSignatureOptions {
|
|
21
|
-
/** Maximum age of signature in milliseconds (default: 5 minutes) */
|
|
22
|
-
maxAgeMs?: number;
|
|
23
|
-
/** Allowed clock skew in milliseconds (default: 1 minute) */
|
|
24
|
-
clockSkewMs?: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* @
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* @param
|
|
133
|
-
* @
|
|
134
|
-
*
|
|
135
|
-
* @
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
*
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
1
|
+
/**
|
|
2
|
+
* HMAC Signing Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides HMAC-SHA256 signing and verification using the Web Crypto API.
|
|
5
|
+
* Used for JWT signing and bot request authentication.
|
|
6
|
+
*
|
|
7
|
+
* @module hmac
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
base64UrlEncodeBytes,
|
|
12
|
+
base64UrlDecodeBytes,
|
|
13
|
+
bytesToHex,
|
|
14
|
+
hexToBytes,
|
|
15
|
+
} from '@xivdyetools/crypto';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options for bot signature verification
|
|
19
|
+
*/
|
|
20
|
+
export interface BotSignatureOptions {
|
|
21
|
+
/** Maximum age of signature in milliseconds (default: 5 minutes) */
|
|
22
|
+
maxAgeMs?: number;
|
|
23
|
+
/** Allowed clock skew in milliseconds (default: 1 minute) */
|
|
24
|
+
clockSkewMs?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// CryptoKey Cache (OPT-002)
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Module-level cache for CryptoKeys.
|
|
33
|
+
*
|
|
34
|
+
* In Cloudflare Workers, module-level state persists across requests within
|
|
35
|
+
* an isolate, making this safe and effective. Eliminates redundant
|
|
36
|
+
* `crypto.subtle.importKey()` calls when the same secret is reused.
|
|
37
|
+
*
|
|
38
|
+
* Cache key format: `${secret}:${usage}` — bounded to 10 entries max
|
|
39
|
+
* to prevent unbounded growth during key rotation.
|
|
40
|
+
*/
|
|
41
|
+
const cryptoKeyCache = new Map<string, CryptoKey>();
|
|
42
|
+
const CRYPTO_KEY_CACHE_MAX = 10;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get or create a cached CryptoKey for the given secret and usage.
|
|
46
|
+
* Exported for use by jwt.ts — not part of the public package API.
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
export async function getOrCreateHmacKey(
|
|
50
|
+
secret: string,
|
|
51
|
+
usage: 'sign' | 'verify' | 'both'
|
|
52
|
+
): Promise<CryptoKey> {
|
|
53
|
+
const cacheKey = `${secret}:${usage}`;
|
|
54
|
+
const cached = cryptoKeyCache.get(cacheKey);
|
|
55
|
+
if (cached) {
|
|
56
|
+
return cached;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const key = await createHmacKey(secret, usage);
|
|
60
|
+
|
|
61
|
+
// Evict oldest entries if cache is full (simple FIFO)
|
|
62
|
+
if (cryptoKeyCache.size >= CRYPTO_KEY_CACHE_MAX) {
|
|
63
|
+
const firstKey = cryptoKeyCache.keys().next().value;
|
|
64
|
+
if (firstKey !== undefined) {
|
|
65
|
+
cryptoKeyCache.delete(firstKey);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
cryptoKeyCache.set(cacheKey, key);
|
|
70
|
+
return key;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Create an HMAC-SHA256 CryptoKey from a secret string.
|
|
75
|
+
*
|
|
76
|
+
* @param secret - The secret string to use as key material
|
|
77
|
+
* @param usage - Key usage: 'sign', 'verify', or 'both'
|
|
78
|
+
* @returns CryptoKey for HMAC operations
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* const key = await createHmacKey(process.env.JWT_SECRET, 'verify');
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export async function createHmacKey(
|
|
86
|
+
secret: string,
|
|
87
|
+
usage: 'sign' | 'verify' | 'both' = 'both'
|
|
88
|
+
): Promise<CryptoKey> {
|
|
89
|
+
const encoder = new TextEncoder();
|
|
90
|
+
const keyData = encoder.encode(secret);
|
|
91
|
+
|
|
92
|
+
// FINDING-009: Enforce minimum key length for HMAC-SHA256 security
|
|
93
|
+
if (keyData.length < 32) {
|
|
94
|
+
throw new Error('HMAC secret must be at least 32 bytes (256 bits)');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const keyUsages: ('sign' | 'verify')[] =
|
|
98
|
+
usage === 'both' ? ['sign', 'verify'] : [usage];
|
|
99
|
+
|
|
100
|
+
return crypto.subtle.importKey(
|
|
101
|
+
'raw',
|
|
102
|
+
keyData,
|
|
103
|
+
{ name: 'HMAC', hash: 'SHA-256' },
|
|
104
|
+
false,
|
|
105
|
+
keyUsages
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Sign data with HMAC-SHA256 and return base64url-encoded signature.
|
|
111
|
+
*
|
|
112
|
+
* @param data - The data to sign
|
|
113
|
+
* @param secret - The secret key
|
|
114
|
+
* @returns Base64URL-encoded signature
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* const signature = await hmacSign('header.payload', jwtSecret);
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
export async function hmacSign(data: string, secret: string): Promise<string> {
|
|
122
|
+
const key = await getOrCreateHmacKey(secret, 'sign');
|
|
123
|
+
const encoder = new TextEncoder();
|
|
124
|
+
const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(data));
|
|
125
|
+
return base64UrlEncodeBytes(new Uint8Array(signature));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Sign data with HMAC-SHA256 and return hex-encoded signature.
|
|
130
|
+
*
|
|
131
|
+
* @param data - The data to sign
|
|
132
|
+
* @param secret - The secret key
|
|
133
|
+
* @returns Hex-encoded signature
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* const signature = await hmacSignHex('timestamp:userId:userName', secret);
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export async function hmacSignHex(
|
|
141
|
+
data: string,
|
|
142
|
+
secret: string
|
|
143
|
+
): Promise<string> {
|
|
144
|
+
const key = await getOrCreateHmacKey(secret, 'sign');
|
|
145
|
+
const encoder = new TextEncoder();
|
|
146
|
+
const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(data));
|
|
147
|
+
return bytesToHex(new Uint8Array(signature));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Verify HMAC-SHA256 signature (base64url-encoded).
|
|
152
|
+
*
|
|
153
|
+
* @param data - The original data that was signed
|
|
154
|
+
* @param signature - Base64URL-encoded signature to verify
|
|
155
|
+
* @param secret - The secret key
|
|
156
|
+
* @returns true if signature is valid
|
|
157
|
+
*/
|
|
158
|
+
export async function hmacVerify(
|
|
159
|
+
data: string,
|
|
160
|
+
signature: string,
|
|
161
|
+
secret: string
|
|
162
|
+
): Promise<boolean> {
|
|
163
|
+
try {
|
|
164
|
+
const key = await getOrCreateHmacKey(secret, 'verify');
|
|
165
|
+
const encoder = new TextEncoder();
|
|
166
|
+
const signatureBytes = base64UrlDecodeBytes(signature);
|
|
167
|
+
|
|
168
|
+
// Use crypto.subtle.verify() which is inherently timing-safe
|
|
169
|
+
return crypto.subtle.verify(
|
|
170
|
+
'HMAC',
|
|
171
|
+
key,
|
|
172
|
+
signatureBytes,
|
|
173
|
+
encoder.encode(data)
|
|
174
|
+
);
|
|
175
|
+
} catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Verify HMAC-SHA256 signature (hex-encoded).
|
|
182
|
+
*
|
|
183
|
+
* @param data - The original data that was signed
|
|
184
|
+
* @param signature - Hex-encoded signature to verify
|
|
185
|
+
* @param secret - The secret key
|
|
186
|
+
* @returns true if signature is valid
|
|
187
|
+
*/
|
|
188
|
+
export async function hmacVerifyHex(
|
|
189
|
+
data: string,
|
|
190
|
+
signature: string,
|
|
191
|
+
secret: string
|
|
192
|
+
): Promise<boolean> {
|
|
193
|
+
try {
|
|
194
|
+
const key = await getOrCreateHmacKey(secret, 'verify');
|
|
195
|
+
const encoder = new TextEncoder();
|
|
196
|
+
const signatureBytes = hexToBytes(signature);
|
|
197
|
+
|
|
198
|
+
return crypto.subtle.verify(
|
|
199
|
+
'HMAC',
|
|
200
|
+
key,
|
|
201
|
+
signatureBytes,
|
|
202
|
+
encoder.encode(data)
|
|
203
|
+
);
|
|
204
|
+
} catch {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Verify a bot request signature.
|
|
211
|
+
*
|
|
212
|
+
* Bot signatures use the format: `${timestamp}:${userDiscordId}:${userName}`
|
|
213
|
+
* Signatures are hex-encoded HMAC-SHA256.
|
|
214
|
+
*
|
|
215
|
+
* @param signature - Hex-encoded signature
|
|
216
|
+
* @param timestamp - Unix timestamp string (seconds)
|
|
217
|
+
* @param userDiscordId - Discord user ID
|
|
218
|
+
* @param userName - Discord username
|
|
219
|
+
* @param secret - The signing secret
|
|
220
|
+
* @param options - Verification options
|
|
221
|
+
* @returns true if signature is valid and not expired
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```typescript
|
|
225
|
+
* const isValid = await verifyBotSignature(
|
|
226
|
+
* request.headers.get('X-Signature'),
|
|
227
|
+
* request.headers.get('X-Timestamp'),
|
|
228
|
+
* request.headers.get('X-User-Id'),
|
|
229
|
+
* request.headers.get('X-User-Name'),
|
|
230
|
+
* env.BOT_SIGNING_SECRET
|
|
231
|
+
* );
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
export async function verifyBotSignature(
|
|
235
|
+
signature: string | undefined,
|
|
236
|
+
timestamp: string | undefined,
|
|
237
|
+
userDiscordId: string | undefined,
|
|
238
|
+
userName: string | undefined,
|
|
239
|
+
secret: string,
|
|
240
|
+
options: BotSignatureOptions = {}
|
|
241
|
+
): Promise<boolean> {
|
|
242
|
+
const { maxAgeMs = 5 * 60 * 1000, clockSkewMs = 60 * 1000 } = options;
|
|
243
|
+
|
|
244
|
+
// Validate required fields (signature and timestamp are required;
|
|
245
|
+
// userDiscordId and userName are optional for system-level bot requests)
|
|
246
|
+
if (!signature || !timestamp) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Validate timestamp format
|
|
251
|
+
const timestampNum = parseInt(timestamp, 10);
|
|
252
|
+
if (isNaN(timestampNum)) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Check timestamp age (with clock skew tolerance)
|
|
257
|
+
const now = Date.now();
|
|
258
|
+
const signatureTime = timestampNum * 1000; // Convert to milliseconds
|
|
259
|
+
const age = now - signatureTime;
|
|
260
|
+
|
|
261
|
+
// Reject if too old
|
|
262
|
+
if (age > maxAgeMs) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Reject if too far in the future (clock skew protection)
|
|
267
|
+
if (signatureTime > now + clockSkewMs) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Verify the signature
|
|
272
|
+
const message = `${timestamp}:${userDiscordId ?? ''}:${userName ?? ''}`;
|
|
273
|
+
return hmacVerifyHex(message, signature, secret);
|
|
274
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @xivdyetools/auth
|
|
3
|
-
*
|
|
4
|
-
* Shared authentication utilities for the xivdyetools ecosystem.
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```typescript
|
|
8
|
-
* import { verifyJWT, verifyDiscordRequest, timingSafeEqual } from '@xivdyetools/auth';
|
|
9
|
-
*
|
|
10
|
-
* // Verify JWT
|
|
11
|
-
* const payload = await verifyJWT(token, env.JWT_SECRET);
|
|
12
|
-
*
|
|
13
|
-
* // Verify Discord request
|
|
14
|
-
* const result = await verifyDiscordRequest(request, env.DISCORD_PUBLIC_KEY);
|
|
15
|
-
*
|
|
16
|
-
* // Timing-safe comparison
|
|
17
|
-
* const isValid = await timingSafeEqual(provided, expected);
|
|
18
|
-
* ```
|
|
19
|
-
*
|
|
20
|
-
* @module @xivdyetools/auth
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
// JWT utilities
|
|
24
|
-
export {
|
|
25
|
-
verifyJWT,
|
|
26
|
-
verifyJWTSignatureOnly,
|
|
27
|
-
decodeJWT,
|
|
28
|
-
isJWTExpired,
|
|
29
|
-
getJWTTimeToExpiry,
|
|
30
|
-
type JWTPayload,
|
|
31
|
-
} from './jwt.js';
|
|
32
|
-
|
|
33
|
-
// HMAC utilities
|
|
34
|
-
export {
|
|
35
|
-
createHmacKey,
|
|
36
|
-
hmacSign,
|
|
37
|
-
hmacSignHex,
|
|
38
|
-
hmacVerify,
|
|
39
|
-
hmacVerifyHex,
|
|
40
|
-
verifyBotSignature,
|
|
41
|
-
type BotSignatureOptions,
|
|
42
|
-
} from './hmac.js';
|
|
43
|
-
|
|
44
|
-
// Timing-safe utilities
|
|
45
|
-
export { timingSafeEqual, timingSafeEqualBytes } from './timing.js';
|
|
46
|
-
|
|
47
|
-
// Discord verification
|
|
48
|
-
export {
|
|
49
|
-
verifyDiscordRequest,
|
|
50
|
-
unauthorizedResponse,
|
|
51
|
-
badRequestResponse,
|
|
52
|
-
type DiscordVerificationResult,
|
|
53
|
-
type DiscordVerifyOptions,
|
|
54
|
-
} from './discord.js';
|
|
1
|
+
/**
|
|
2
|
+
* @xivdyetools/auth
|
|
3
|
+
*
|
|
4
|
+
* Shared authentication utilities for the xivdyetools ecosystem.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { verifyJWT, verifyDiscordRequest, timingSafeEqual } from '@xivdyetools/auth';
|
|
9
|
+
*
|
|
10
|
+
* // Verify JWT
|
|
11
|
+
* const payload = await verifyJWT(token, env.JWT_SECRET);
|
|
12
|
+
*
|
|
13
|
+
* // Verify Discord request
|
|
14
|
+
* const result = await verifyDiscordRequest(request, env.DISCORD_PUBLIC_KEY);
|
|
15
|
+
*
|
|
16
|
+
* // Timing-safe comparison
|
|
17
|
+
* const isValid = await timingSafeEqual(provided, expected);
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @module @xivdyetools/auth
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// JWT utilities
|
|
24
|
+
export {
|
|
25
|
+
verifyJWT,
|
|
26
|
+
verifyJWTSignatureOnly,
|
|
27
|
+
decodeJWT,
|
|
28
|
+
isJWTExpired,
|
|
29
|
+
getJWTTimeToExpiry,
|
|
30
|
+
type JWTPayload,
|
|
31
|
+
} from './jwt.js';
|
|
32
|
+
|
|
33
|
+
// HMAC utilities
|
|
34
|
+
export {
|
|
35
|
+
createHmacKey,
|
|
36
|
+
hmacSign,
|
|
37
|
+
hmacSignHex,
|
|
38
|
+
hmacVerify,
|
|
39
|
+
hmacVerifyHex,
|
|
40
|
+
verifyBotSignature,
|
|
41
|
+
type BotSignatureOptions,
|
|
42
|
+
} from './hmac.js';
|
|
43
|
+
|
|
44
|
+
// Timing-safe utilities
|
|
45
|
+
export { timingSafeEqual, timingSafeEqualBytes } from './timing.js';
|
|
46
|
+
|
|
47
|
+
// Discord verification
|
|
48
|
+
export {
|
|
49
|
+
verifyDiscordRequest,
|
|
50
|
+
unauthorizedResponse,
|
|
51
|
+
badRequestResponse,
|
|
52
|
+
type DiscordVerificationResult,
|
|
53
|
+
type DiscordVerifyOptions,
|
|
54
|
+
} from './discord.js';
|