@xivdyetools/auth 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.
- package/dist/discord.d.ts +65 -0
- package/dist/discord.d.ts.map +1 -0
- package/dist/discord.js +107 -0
- package/dist/discord.js.map +1 -0
- package/dist/hmac.d.ts +101 -0
- package/dist/hmac.d.ts.map +1 -0
- package/dist/hmac.js +153 -0
- package/dist/hmac.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/jwt.d.ts +110 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +194 -0
- package/dist/jwt.js.map +1 -0
- package/dist/timing.d.ts +34 -0
- package/dist/timing.d.ts.map +1 -0
- package/dist/timing.js +77 -0
- package/dist/timing.js.map +1 -0
- package/package.json +71 -0
- package/src/discord.test.ts +243 -0
- package/src/discord.ts +143 -0
- package/src/hmac.test.ts +325 -0
- package/src/hmac.ts +213 -0
- package/src/index.ts +54 -0
- package/src/jwt.test.ts +337 -0
- package/src/jwt.ts +267 -0
- package/src/timing.test.ts +117 -0
- package/src/timing.ts +84 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord Request Verification
|
|
3
|
+
*
|
|
4
|
+
* Wraps the `discord-interactions` library's Ed25519 signature verification
|
|
5
|
+
* with additional security checks (body size limits, header validation).
|
|
6
|
+
*
|
|
7
|
+
* @see https://discord.com/developers/docs/interactions/receiving-and-responding#security-and-authorization
|
|
8
|
+
* @module discord
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Result of Discord request verification
|
|
12
|
+
*/
|
|
13
|
+
export interface DiscordVerificationResult {
|
|
14
|
+
/** Whether the signature is valid */
|
|
15
|
+
isValid: boolean;
|
|
16
|
+
/** The raw request body (needed for parsing after verification) */
|
|
17
|
+
body: string;
|
|
18
|
+
/** Error message if verification failed */
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Options for Discord verification
|
|
23
|
+
*/
|
|
24
|
+
export interface DiscordVerifyOptions {
|
|
25
|
+
/** Maximum request body size in bytes (default: 100KB) */
|
|
26
|
+
maxBodySize?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Verify that a request came from Discord using Ed25519 signature verification.
|
|
30
|
+
*
|
|
31
|
+
* Security features:
|
|
32
|
+
* - Content-Length header check (before reading body)
|
|
33
|
+
* - Actual body size validation (Content-Length can be spoofed)
|
|
34
|
+
* - Required header validation (X-Signature-Ed25519, X-Signature-Timestamp)
|
|
35
|
+
*
|
|
36
|
+
* @param request - The incoming HTTP request
|
|
37
|
+
* @param publicKey - Your Discord application's public key
|
|
38
|
+
* @param options - Verification options
|
|
39
|
+
* @returns Verification result with the request body
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const result = await verifyDiscordRequest(request, env.DISCORD_PUBLIC_KEY);
|
|
44
|
+
* if (!result.isValid) {
|
|
45
|
+
* return new Response(result.error, { status: 401 });
|
|
46
|
+
* }
|
|
47
|
+
* const interaction = JSON.parse(result.body);
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function verifyDiscordRequest(request: Request, publicKey: string, options?: DiscordVerifyOptions): Promise<DiscordVerificationResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Creates a 401 Unauthorized response for failed verification.
|
|
53
|
+
*
|
|
54
|
+
* @param message - Error message (default: 'Invalid request signature')
|
|
55
|
+
* @returns Response object
|
|
56
|
+
*/
|
|
57
|
+
export declare function unauthorizedResponse(message?: string): Response;
|
|
58
|
+
/**
|
|
59
|
+
* Creates a 400 Bad Request response.
|
|
60
|
+
*
|
|
61
|
+
* @param message - Error message
|
|
62
|
+
* @returns Response object
|
|
63
|
+
*/
|
|
64
|
+
export declare function badRequestResponse(message: string): Response;
|
|
65
|
+
//# sourceMappingURL=discord.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord.d.ts","sourceRoot":"","sources":["../src/discord.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,qCAAqC;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAKD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,yBAAyB,CAAC,CAqDpC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,SAA8B,GACpC,QAAQ,CAKV;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAK5D"}
|
package/dist/discord.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord Request Verification
|
|
3
|
+
*
|
|
4
|
+
* Wraps the `discord-interactions` library's Ed25519 signature verification
|
|
5
|
+
* with additional security checks (body size limits, header validation).
|
|
6
|
+
*
|
|
7
|
+
* @see https://discord.com/developers/docs/interactions/receiving-and-responding#security-and-authorization
|
|
8
|
+
* @module discord
|
|
9
|
+
*/
|
|
10
|
+
import { verifyKey } from 'discord-interactions';
|
|
11
|
+
/** Default maximum body size (100KB) */
|
|
12
|
+
const DEFAULT_MAX_BODY_SIZE = 100_000;
|
|
13
|
+
/**
|
|
14
|
+
* Verify that a request came from Discord using Ed25519 signature verification.
|
|
15
|
+
*
|
|
16
|
+
* Security features:
|
|
17
|
+
* - Content-Length header check (before reading body)
|
|
18
|
+
* - Actual body size validation (Content-Length can be spoofed)
|
|
19
|
+
* - Required header validation (X-Signature-Ed25519, X-Signature-Timestamp)
|
|
20
|
+
*
|
|
21
|
+
* @param request - The incoming HTTP request
|
|
22
|
+
* @param publicKey - Your Discord application's public key
|
|
23
|
+
* @param options - Verification options
|
|
24
|
+
* @returns Verification result with the request body
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const result = await verifyDiscordRequest(request, env.DISCORD_PUBLIC_KEY);
|
|
29
|
+
* if (!result.isValid) {
|
|
30
|
+
* return new Response(result.error, { status: 401 });
|
|
31
|
+
* }
|
|
32
|
+
* const interaction = JSON.parse(result.body);
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export async function verifyDiscordRequest(request, publicKey, options = {}) {
|
|
36
|
+
const maxBodySize = options.maxBodySize ?? DEFAULT_MAX_BODY_SIZE;
|
|
37
|
+
// Check Content-Length header first (if present) to reject obviously large requests
|
|
38
|
+
const contentLength = request.headers.get('Content-Length');
|
|
39
|
+
if (contentLength && parseInt(contentLength, 10) > maxBodySize) {
|
|
40
|
+
return {
|
|
41
|
+
isValid: false,
|
|
42
|
+
body: '',
|
|
43
|
+
error: 'Request body too large',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Get required headers
|
|
47
|
+
const signature = request.headers.get('X-Signature-Ed25519');
|
|
48
|
+
const timestamp = request.headers.get('X-Signature-Timestamp');
|
|
49
|
+
if (!signature || !timestamp) {
|
|
50
|
+
return {
|
|
51
|
+
isValid: false,
|
|
52
|
+
body: '',
|
|
53
|
+
error: 'Missing signature headers',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// Get the raw body
|
|
57
|
+
const body = await request.text();
|
|
58
|
+
// Verify actual body size (Content-Length can be spoofed)
|
|
59
|
+
if (body.length > maxBodySize) {
|
|
60
|
+
return {
|
|
61
|
+
isValid: false,
|
|
62
|
+
body: '',
|
|
63
|
+
error: 'Request body too large',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// Verify the signature using discord-interactions library
|
|
67
|
+
try {
|
|
68
|
+
const isValid = await verifyKey(body, signature, timestamp, publicKey);
|
|
69
|
+
return {
|
|
70
|
+
isValid,
|
|
71
|
+
body,
|
|
72
|
+
error: isValid ? undefined : 'Invalid signature',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
return {
|
|
77
|
+
isValid: false,
|
|
78
|
+
body,
|
|
79
|
+
error: error instanceof Error ? error.message : 'Verification failed',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Creates a 401 Unauthorized response for failed verification.
|
|
85
|
+
*
|
|
86
|
+
* @param message - Error message (default: 'Invalid request signature')
|
|
87
|
+
* @returns Response object
|
|
88
|
+
*/
|
|
89
|
+
export function unauthorizedResponse(message = 'Invalid request signature') {
|
|
90
|
+
return new Response(JSON.stringify({ error: message }), {
|
|
91
|
+
status: 401,
|
|
92
|
+
headers: { 'Content-Type': 'application/json' },
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Creates a 400 Bad Request response.
|
|
97
|
+
*
|
|
98
|
+
* @param message - Error message
|
|
99
|
+
* @returns Response object
|
|
100
|
+
*/
|
|
101
|
+
export function badRequestResponse(message) {
|
|
102
|
+
return new Response(JSON.stringify({ error: message }), {
|
|
103
|
+
status: 400,
|
|
104
|
+
headers: { 'Content-Type': 'application/json' },
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=discord.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord.js","sourceRoot":"","sources":["../src/discord.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAsBjD,wCAAwC;AACxC,MAAM,qBAAqB,GAAG,OAAO,CAAC;AAEtC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAgB,EAChB,SAAiB,EACjB,UAAgC,EAAE;IAElC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,qBAAqB,CAAC;IAEjE,oFAAoF;IACpF,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC5D,IAAI,aAAa,IAAI,QAAQ,CAAC,aAAa,EAAE,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC;QAC/D,OAAO;YACL,OAAO,EAAE,KAAK;YACd,IAAI,EAAE,EAAE;YACR,KAAK,EAAE,wBAAwB;SAChC,CAAC;IACJ,CAAC;IAED,uBAAuB;IACvB,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;IAE/D,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,EAAE,CAAC;QAC7B,OAAO;YACL,OAAO,EAAE,KAAK;YACd,IAAI,EAAE,EAAE;YACR,KAAK,EAAE,2BAA2B;SACnC,CAAC;IACJ,CAAC;IAED,mBAAmB;IACnB,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;IAElC,0DAA0D;IAC1D,IAAI,IAAI,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC;QAC9B,OAAO;YACL,OAAO,EAAE,KAAK;YACd,IAAI,EAAE,EAAE;YACR,KAAK,EAAE,wBAAwB;SAChC,CAAC;IACJ,CAAC;IAED,0DAA0D;IAC1D,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;QAEvE,OAAO;YACL,OAAO;YACP,IAAI;YACJ,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,mBAAmB;SACjD,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,KAAK;YACd,IAAI;YACJ,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,qBAAqB;SACtE,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAO,GAAG,2BAA2B;IAErC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE;QACtD,MAAM,EAAE,GAAG;QACX,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE;QACtD,MAAM,EAAE,GAAG;QACX,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAC;AACL,CAAC"}
|
package/dist/hmac.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
* Options for bot signature verification
|
|
11
|
+
*/
|
|
12
|
+
export interface BotSignatureOptions {
|
|
13
|
+
/** Maximum age of signature in milliseconds (default: 5 minutes) */
|
|
14
|
+
maxAgeMs?: number;
|
|
15
|
+
/** Allowed clock skew in milliseconds (default: 1 minute) */
|
|
16
|
+
clockSkewMs?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create an HMAC-SHA256 CryptoKey from a secret string.
|
|
20
|
+
*
|
|
21
|
+
* @param secret - The secret string to use as key material
|
|
22
|
+
* @param usage - Key usage: 'sign', 'verify', or 'both'
|
|
23
|
+
* @returns CryptoKey for HMAC operations
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const key = await createHmacKey(process.env.JWT_SECRET, 'verify');
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function createHmacKey(secret: string, usage?: 'sign' | 'verify' | 'both'): Promise<CryptoKey>;
|
|
31
|
+
/**
|
|
32
|
+
* Sign data with HMAC-SHA256 and return base64url-encoded signature.
|
|
33
|
+
*
|
|
34
|
+
* @param data - The data to sign
|
|
35
|
+
* @param secret - The secret key
|
|
36
|
+
* @returns Base64URL-encoded signature
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const signature = await hmacSign('header.payload', jwtSecret);
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function hmacSign(data: string, secret: string): Promise<string>;
|
|
44
|
+
/**
|
|
45
|
+
* Sign data with HMAC-SHA256 and return hex-encoded signature.
|
|
46
|
+
*
|
|
47
|
+
* @param data - The data to sign
|
|
48
|
+
* @param secret - The secret key
|
|
49
|
+
* @returns Hex-encoded signature
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const signature = await hmacSignHex('timestamp:userId:userName', secret);
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare function hmacSignHex(data: string, secret: string): Promise<string>;
|
|
57
|
+
/**
|
|
58
|
+
* Verify HMAC-SHA256 signature (base64url-encoded).
|
|
59
|
+
*
|
|
60
|
+
* @param data - The original data that was signed
|
|
61
|
+
* @param signature - Base64URL-encoded signature to verify
|
|
62
|
+
* @param secret - The secret key
|
|
63
|
+
* @returns true if signature is valid
|
|
64
|
+
*/
|
|
65
|
+
export declare function hmacVerify(data: string, signature: string, secret: string): Promise<boolean>;
|
|
66
|
+
/**
|
|
67
|
+
* Verify HMAC-SHA256 signature (hex-encoded).
|
|
68
|
+
*
|
|
69
|
+
* @param data - The original data that was signed
|
|
70
|
+
* @param signature - Hex-encoded signature to verify
|
|
71
|
+
* @param secret - The secret key
|
|
72
|
+
* @returns true if signature is valid
|
|
73
|
+
*/
|
|
74
|
+
export declare function hmacVerifyHex(data: string, signature: string, secret: string): Promise<boolean>;
|
|
75
|
+
/**
|
|
76
|
+
* Verify a bot request signature.
|
|
77
|
+
*
|
|
78
|
+
* Bot signatures use the format: `${timestamp}:${userDiscordId}:${userName}`
|
|
79
|
+
* Signatures are hex-encoded HMAC-SHA256.
|
|
80
|
+
*
|
|
81
|
+
* @param signature - Hex-encoded signature
|
|
82
|
+
* @param timestamp - Unix timestamp string (seconds)
|
|
83
|
+
* @param userDiscordId - Discord user ID
|
|
84
|
+
* @param userName - Discord username
|
|
85
|
+
* @param secret - The signing secret
|
|
86
|
+
* @param options - Verification options
|
|
87
|
+
* @returns true if signature is valid and not expired
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* const isValid = await verifyBotSignature(
|
|
92
|
+
* request.headers.get('X-Signature'),
|
|
93
|
+
* request.headers.get('X-Timestamp'),
|
|
94
|
+
* request.headers.get('X-User-Id'),
|
|
95
|
+
* request.headers.get('X-User-Name'),
|
|
96
|
+
* env.BOT_SIGNING_SECRET
|
|
97
|
+
* );
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export declare function verifyBotSignature(signature: string | undefined, timestamp: string | undefined, userDiscordId: string | undefined, userName: string | undefined, secret: string, options?: BotSignatureOptions): Promise<boolean>;
|
|
101
|
+
//# sourceMappingURL=hmac.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hmac.d.ts","sourceRoot":"","sources":["../src/hmac.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAM,GAAG,QAAQ,GAAG,MAAe,GACzC,OAAO,CAAC,SAAS,CAAC,CAcpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAQlB;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAelB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,OAAO,CAAC,CAgClB"}
|
package/dist/hmac.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
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
|
+
import { base64UrlEncodeBytes, bytesToHex, hexToBytes, } from '@xivdyetools/crypto';
|
|
10
|
+
/**
|
|
11
|
+
* Create an HMAC-SHA256 CryptoKey from a secret string.
|
|
12
|
+
*
|
|
13
|
+
* @param secret - The secret string to use as key material
|
|
14
|
+
* @param usage - Key usage: 'sign', 'verify', or 'both'
|
|
15
|
+
* @returns CryptoKey for HMAC operations
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const key = await createHmacKey(process.env.JWT_SECRET, 'verify');
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export async function createHmacKey(secret, usage = 'both') {
|
|
23
|
+
const encoder = new TextEncoder();
|
|
24
|
+
const keyData = encoder.encode(secret);
|
|
25
|
+
const keyUsages = usage === 'both' ? ['sign', 'verify'] : [usage];
|
|
26
|
+
return crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, keyUsages);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Sign data with HMAC-SHA256 and return base64url-encoded signature.
|
|
30
|
+
*
|
|
31
|
+
* @param data - The data to sign
|
|
32
|
+
* @param secret - The secret key
|
|
33
|
+
* @returns Base64URL-encoded signature
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* const signature = await hmacSign('header.payload', jwtSecret);
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export async function hmacSign(data, secret) {
|
|
41
|
+
const key = await createHmacKey(secret, 'sign');
|
|
42
|
+
const encoder = new TextEncoder();
|
|
43
|
+
const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(data));
|
|
44
|
+
return base64UrlEncodeBytes(new Uint8Array(signature));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Sign data with HMAC-SHA256 and return hex-encoded signature.
|
|
48
|
+
*
|
|
49
|
+
* @param data - The data to sign
|
|
50
|
+
* @param secret - The secret key
|
|
51
|
+
* @returns Hex-encoded signature
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* const signature = await hmacSignHex('timestamp:userId:userName', secret);
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export async function hmacSignHex(data, secret) {
|
|
59
|
+
const key = await createHmacKey(secret, 'sign');
|
|
60
|
+
const encoder = new TextEncoder();
|
|
61
|
+
const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(data));
|
|
62
|
+
return bytesToHex(new Uint8Array(signature));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Verify HMAC-SHA256 signature (base64url-encoded).
|
|
66
|
+
*
|
|
67
|
+
* @param data - The original data that was signed
|
|
68
|
+
* @param signature - Base64URL-encoded signature to verify
|
|
69
|
+
* @param secret - The secret key
|
|
70
|
+
* @returns true if signature is valid
|
|
71
|
+
*/
|
|
72
|
+
export async function hmacVerify(data, signature, secret) {
|
|
73
|
+
try {
|
|
74
|
+
const expectedSignature = await hmacSign(data, secret);
|
|
75
|
+
// Use timing-safe comparison
|
|
76
|
+
return expectedSignature === signature;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Verify HMAC-SHA256 signature (hex-encoded).
|
|
84
|
+
*
|
|
85
|
+
* @param data - The original data that was signed
|
|
86
|
+
* @param signature - Hex-encoded signature to verify
|
|
87
|
+
* @param secret - The secret key
|
|
88
|
+
* @returns true if signature is valid
|
|
89
|
+
*/
|
|
90
|
+
export async function hmacVerifyHex(data, signature, secret) {
|
|
91
|
+
try {
|
|
92
|
+
const key = await createHmacKey(secret, 'verify');
|
|
93
|
+
const encoder = new TextEncoder();
|
|
94
|
+
const signatureBytes = hexToBytes(signature);
|
|
95
|
+
return crypto.subtle.verify('HMAC', key, signatureBytes, encoder.encode(data));
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Verify a bot request signature.
|
|
103
|
+
*
|
|
104
|
+
* Bot signatures use the format: `${timestamp}:${userDiscordId}:${userName}`
|
|
105
|
+
* Signatures are hex-encoded HMAC-SHA256.
|
|
106
|
+
*
|
|
107
|
+
* @param signature - Hex-encoded signature
|
|
108
|
+
* @param timestamp - Unix timestamp string (seconds)
|
|
109
|
+
* @param userDiscordId - Discord user ID
|
|
110
|
+
* @param userName - Discord username
|
|
111
|
+
* @param secret - The signing secret
|
|
112
|
+
* @param options - Verification options
|
|
113
|
+
* @returns true if signature is valid and not expired
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* const isValid = await verifyBotSignature(
|
|
118
|
+
* request.headers.get('X-Signature'),
|
|
119
|
+
* request.headers.get('X-Timestamp'),
|
|
120
|
+
* request.headers.get('X-User-Id'),
|
|
121
|
+
* request.headers.get('X-User-Name'),
|
|
122
|
+
* env.BOT_SIGNING_SECRET
|
|
123
|
+
* );
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export async function verifyBotSignature(signature, timestamp, userDiscordId, userName, secret, options = {}) {
|
|
127
|
+
const { maxAgeMs = 5 * 60 * 1000, clockSkewMs = 60 * 1000 } = options;
|
|
128
|
+
// Validate required fields
|
|
129
|
+
if (!signature || !timestamp || !userDiscordId || !userName) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
// Validate timestamp format
|
|
133
|
+
const timestampNum = parseInt(timestamp, 10);
|
|
134
|
+
if (isNaN(timestampNum)) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
// Check timestamp age (with clock skew tolerance)
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
const signatureTime = timestampNum * 1000; // Convert to milliseconds
|
|
140
|
+
const age = now - signatureTime;
|
|
141
|
+
// Reject if too old
|
|
142
|
+
if (age > maxAgeMs) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
// Reject if too far in the future (clock skew protection)
|
|
146
|
+
if (signatureTime > now + clockSkewMs) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
// Verify the signature
|
|
150
|
+
const message = `${timestamp}:${userDiscordId}:${userName}`;
|
|
151
|
+
return hmacVerifyHex(message, signature, secret);
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=hmac.js.map
|
package/dist/hmac.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hmac.js","sourceRoot":"","sources":["../src/hmac.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
export { verifyJWT, verifyJWTSignatureOnly, decodeJWT, isJWTExpired, getJWTTimeToExpiry, type JWTPayload, } from './jwt.js';
|
|
23
|
+
export { createHmacKey, hmacSign, hmacSignHex, hmacVerify, hmacVerifyHex, verifyBotSignature, type BotSignatureOptions, } from './hmac.js';
|
|
24
|
+
export { timingSafeEqual, timingSafeEqualBytes } from './timing.js';
|
|
25
|
+
export { verifyDiscordRequest, unauthorizedResponse, badRequestResponse, type DiscordVerificationResult, type DiscordVerifyOptions, } from './discord.js';
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EACL,SAAS,EACT,sBAAsB,EACtB,SAAS,EACT,YAAY,EACZ,kBAAkB,EAClB,KAAK,UAAU,GAChB,MAAM,UAAU,CAAC;AAGlB,OAAO,EACL,aAAa,EACb,QAAQ,EACR,WAAW,EACX,UAAU,EACV,aAAa,EACb,kBAAkB,EAClB,KAAK,mBAAmB,GACzB,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGpE,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,GAC1B,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
// JWT utilities
|
|
23
|
+
export { verifyJWT, verifyJWTSignatureOnly, decodeJWT, isJWTExpired, getJWTTimeToExpiry, } from './jwt.js';
|
|
24
|
+
// HMAC utilities
|
|
25
|
+
export { createHmacKey, hmacSign, hmacSignHex, hmacVerify, hmacVerifyHex, verifyBotSignature, } from './hmac.js';
|
|
26
|
+
// Timing-safe utilities
|
|
27
|
+
export { timingSafeEqual, timingSafeEqualBytes } from './timing.js';
|
|
28
|
+
// Discord verification
|
|
29
|
+
export { verifyDiscordRequest, unauthorizedResponse, badRequestResponse, } from './discord.js';
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,gBAAgB;AAChB,OAAO,EACL,SAAS,EACT,sBAAsB,EACtB,SAAS,EACT,YAAY,EACZ,kBAAkB,GAEnB,MAAM,UAAU,CAAC;AAElB,iBAAiB;AACjB,OAAO,EACL,aAAa,EACb,QAAQ,EACR,WAAW,EACX,UAAU,EACV,aAAa,EACb,kBAAkB,GAEnB,MAAM,WAAW,CAAC;AAEnB,wBAAwB;AACxB,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEpE,uBAAuB;AACvB,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,GAGnB,MAAM,cAAc,CAAC"}
|
package/dist/jwt.d.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT Verification Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides JWT verification using HMAC-SHA256 (HS256) with the Web Crypto API.
|
|
5
|
+
* Intentionally does NOT include JWT creation - that stays in the oauth service.
|
|
6
|
+
*
|
|
7
|
+
* Security features:
|
|
8
|
+
* - Algorithm validation (rejects non-HS256 tokens)
|
|
9
|
+
* - Expiration checking
|
|
10
|
+
* - Timing-safe signature comparison
|
|
11
|
+
*
|
|
12
|
+
* @module jwt
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* JWT payload structure
|
|
16
|
+
*
|
|
17
|
+
* Re-exported from @xivdyetools/types for convenience.
|
|
18
|
+
* Consumers should import from here rather than directly from types.
|
|
19
|
+
*/
|
|
20
|
+
export interface JWTPayload {
|
|
21
|
+
/** Subject - Discord user ID */
|
|
22
|
+
sub: string;
|
|
23
|
+
/** Issued at timestamp (seconds) */
|
|
24
|
+
iat: number;
|
|
25
|
+
/** Expiration timestamp (seconds) */
|
|
26
|
+
exp: number;
|
|
27
|
+
/** Token type: 'access' or 'refresh' */
|
|
28
|
+
type: 'access' | 'refresh';
|
|
29
|
+
/** Discord username */
|
|
30
|
+
username?: string;
|
|
31
|
+
/** Discord avatar hash */
|
|
32
|
+
avatar?: string | null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Decode a JWT without verifying the signature.
|
|
36
|
+
*
|
|
37
|
+
* WARNING: Only use this for debugging or when you'll verify separately.
|
|
38
|
+
* For production use, always use `verifyJWT()`.
|
|
39
|
+
*
|
|
40
|
+
* @param token - The JWT string
|
|
41
|
+
* @returns Decoded payload or null if malformed
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const payload = decodeJWT(token);
|
|
46
|
+
* console.log('Token expires:', new Date(payload.exp * 1000));
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function decodeJWT(token: string): JWTPayload | null;
|
|
50
|
+
/**
|
|
51
|
+
* Verify a JWT and return the payload if valid.
|
|
52
|
+
*
|
|
53
|
+
* Performs full verification:
|
|
54
|
+
* 1. Validates token structure (3 parts)
|
|
55
|
+
* 2. Validates algorithm is HS256 (prevents confusion attacks)
|
|
56
|
+
* 3. Verifies HMAC-SHA256 signature
|
|
57
|
+
* 4. Checks expiration time
|
|
58
|
+
*
|
|
59
|
+
* @param token - The JWT string
|
|
60
|
+
* @param secret - The HMAC secret used to sign the token
|
|
61
|
+
* @returns Verified payload or null if invalid/expired
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* const payload = await verifyJWT(token, env.JWT_SECRET);
|
|
66
|
+
* if (!payload) {
|
|
67
|
+
* return new Response('Unauthorized', { status: 401 });
|
|
68
|
+
* }
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export declare function verifyJWT(token: string, secret: string): Promise<JWTPayload | null>;
|
|
72
|
+
/**
|
|
73
|
+
* Verify JWT signature only, ignoring expiration.
|
|
74
|
+
*
|
|
75
|
+
* Used for refresh tokens where we want to verify authenticity
|
|
76
|
+
* but allow some grace period past expiration.
|
|
77
|
+
*
|
|
78
|
+
* @param token - The JWT string
|
|
79
|
+
* @param secret - The HMAC secret
|
|
80
|
+
* @param maxAgeMs - Optional maximum age in milliseconds (from iat)
|
|
81
|
+
* @returns Payload if signature valid, null otherwise
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* // Allow refresh tokens up to 7 days old
|
|
86
|
+
* const payload = await verifyJWTSignatureOnly(
|
|
87
|
+
* refreshToken,
|
|
88
|
+
* env.JWT_SECRET,
|
|
89
|
+
* 7 * 24 * 60 * 60 * 1000
|
|
90
|
+
* );
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export declare function verifyJWTSignatureOnly(token: string, secret: string, maxAgeMs?: number): Promise<JWTPayload | null>;
|
|
94
|
+
/**
|
|
95
|
+
* Check if a JWT is expired without full verification.
|
|
96
|
+
*
|
|
97
|
+
* Useful for quick checks before making API calls.
|
|
98
|
+
*
|
|
99
|
+
* @param token - The JWT string
|
|
100
|
+
* @returns true if token is expired or malformed
|
|
101
|
+
*/
|
|
102
|
+
export declare function isJWTExpired(token: string): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Get time until JWT expiration.
|
|
105
|
+
*
|
|
106
|
+
* @param token - The JWT string
|
|
107
|
+
* @returns Seconds until expiration, or 0 if expired/invalid
|
|
108
|
+
*/
|
|
109
|
+
export declare function getJWTTimeToExpiry(token: string): number;
|
|
110
|
+
//# sourceMappingURL=jwt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,gCAAgC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,wCAAwC;IACxC,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC3B,uBAAuB;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAUD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAY1D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,SAAS,CAC7B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAmD5B;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAqD5B;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOnD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOxD"}
|