discord-verify
Advanced tools
| const encoder = new TextEncoder(); | ||
| const KEYS = { | ||
| ZERO: 48, | ||
| A: 65, | ||
| a: 97 | ||
| }; | ||
| function hexCharToBinary(char) { | ||
| const code = char.charCodeAt(0); | ||
| if (code >= KEYS.a) { | ||
| return code - KEYS.a + 10; | ||
| } | ||
| if (code >= KEYS.A) { | ||
| return code - KEYS.A + 10; | ||
| } | ||
| return code - KEYS.ZERO; | ||
| } | ||
| function hexStringToBinary(key) { | ||
| if (key == null || key.length % 2 !== 0) { | ||
| return new Uint8Array(0).buffer; | ||
| } | ||
| const view = new Uint8Array(key.length / 2); | ||
| for (let i = 0, o = 0; i < key.length; i += 2, ++o) { | ||
| view[o] = hexCharToBinary(key[i]) << 4 | hexCharToBinary(key[i + 1]); | ||
| } | ||
| return view.buffer; | ||
| } | ||
| async function getCryptoKey(publicKey, subtleCrypto, algorithm) { | ||
| const key = await subtleCrypto.importKey( | ||
| "raw", | ||
| hexStringToBinary(publicKey), | ||
| algorithm, | ||
| true, | ||
| ["verify"] | ||
| ); | ||
| return key; | ||
| } | ||
| const PlatformAlgorithm = { | ||
| Web: "Ed25519", | ||
| /** | ||
| * Node v18.4.0+ | ||
| * Node v16.17.0+ | ||
| * For Node v17, use OldNode | ||
| */ | ||
| NewNode: "Ed25519", | ||
| /** | ||
| * Node v18.3.0 and below | ||
| * Node v17.0.0+ | ||
| * Node v16.16.0 and below | ||
| */ | ||
| OldNode: { | ||
| name: "NODE-ED25519", | ||
| namedCurve: "NODE-ED25519", | ||
| public: true | ||
| }, | ||
| Cloudflare: { | ||
| name: "NODE-ED25519", | ||
| namedCurve: "NODE-ED25519", | ||
| public: true | ||
| }, | ||
| /** | ||
| * Despite being documented as `{ name: "eddsa", namedCurve: "ed25519"}` or | ||
| * `{ name: "ecdsa", namedCurve: "ed25519" }`, Vercel uses the same format as | ||
| * Cloudflare in Production (despite Dev using documented formats) | ||
| */ | ||
| VercelProd: { | ||
| name: "NODE-ED25519", | ||
| namedCurve: "NODE-ED25519", | ||
| public: true | ||
| }, | ||
| /** | ||
| * Despite being documented as using this format, Vercel uses the same format | ||
| * as Cloudflare in Production and only uses this format in Development. | ||
| */ | ||
| VercelDev: { name: "eddsa", namedCurve: "ed25519" } | ||
| }; | ||
| async function isValidRequest(request, publicKey, subtleCrypto, algorithm = PlatformAlgorithm.NewNode) { | ||
| const clone = request.clone(); | ||
| const timestamp = clone.headers.get("X-Signature-Timestamp"); | ||
| const signature = clone.headers.get("X-Signature-Ed25519"); | ||
| const body = await clone.text(); | ||
| return verify(body, signature, timestamp, publicKey, subtleCrypto, algorithm); | ||
| } | ||
| async function verify(rawBody, signature, timestamp, publicKey, subtleCrypto, algorithm = PlatformAlgorithm.NewNode) { | ||
| if (timestamp == null || signature == null || rawBody == null) { | ||
| return false; | ||
| } | ||
| const key = await getCryptoKey(publicKey, subtleCrypto, algorithm); | ||
| const name = typeof algorithm === "string" ? algorithm : algorithm.name; | ||
| const isVerified = await subtleCrypto.verify( | ||
| name, | ||
| key, | ||
| hexStringToBinary(signature), | ||
| encoder.encode(`${timestamp ?? ""}${rawBody}`) | ||
| ); | ||
| return isVerified; | ||
| } | ||
| export { PlatformAlgorithm as P, hexStringToBinary as h, isValidRequest as i, verify as v }; |
| 'use strict'; | ||
| const encoder = new TextEncoder(); | ||
| const KEYS = { | ||
| ZERO: 48, | ||
| A: 65, | ||
| a: 97 | ||
| }; | ||
| function hexCharToBinary(char) { | ||
| const code = char.charCodeAt(0); | ||
| if (code >= KEYS.a) { | ||
| return code - KEYS.a + 10; | ||
| } | ||
| if (code >= KEYS.A) { | ||
| return code - KEYS.A + 10; | ||
| } | ||
| return code - KEYS.ZERO; | ||
| } | ||
| function hexStringToBinary(key) { | ||
| if (key == null || key.length % 2 !== 0) { | ||
| return new Uint8Array(0).buffer; | ||
| } | ||
| const view = new Uint8Array(key.length / 2); | ||
| for (let i = 0, o = 0; i < key.length; i += 2, ++o) { | ||
| view[o] = hexCharToBinary(key[i]) << 4 | hexCharToBinary(key[i + 1]); | ||
| } | ||
| return view.buffer; | ||
| } | ||
| async function getCryptoKey(publicKey, subtleCrypto, algorithm) { | ||
| const key = await subtleCrypto.importKey( | ||
| "raw", | ||
| hexStringToBinary(publicKey), | ||
| algorithm, | ||
| true, | ||
| ["verify"] | ||
| ); | ||
| return key; | ||
| } | ||
| const PlatformAlgorithm = { | ||
| Web: "Ed25519", | ||
| /** | ||
| * Node v18.4.0+ | ||
| * Node v16.17.0+ | ||
| * For Node v17, use OldNode | ||
| */ | ||
| NewNode: "Ed25519", | ||
| /** | ||
| * Node v18.3.0 and below | ||
| * Node v17.0.0+ | ||
| * Node v16.16.0 and below | ||
| */ | ||
| OldNode: { | ||
| name: "NODE-ED25519", | ||
| namedCurve: "NODE-ED25519", | ||
| public: true | ||
| }, | ||
| Cloudflare: { | ||
| name: "NODE-ED25519", | ||
| namedCurve: "NODE-ED25519", | ||
| public: true | ||
| }, | ||
| /** | ||
| * Despite being documented as `{ name: "eddsa", namedCurve: "ed25519"}` or | ||
| * `{ name: "ecdsa", namedCurve: "ed25519" }`, Vercel uses the same format as | ||
| * Cloudflare in Production (despite Dev using documented formats) | ||
| */ | ||
| VercelProd: { | ||
| name: "NODE-ED25519", | ||
| namedCurve: "NODE-ED25519", | ||
| public: true | ||
| }, | ||
| /** | ||
| * Despite being documented as using this format, Vercel uses the same format | ||
| * as Cloudflare in Production and only uses this format in Development. | ||
| */ | ||
| VercelDev: { name: "eddsa", namedCurve: "ed25519" } | ||
| }; | ||
| async function isValidRequest(request, publicKey, subtleCrypto, algorithm = PlatformAlgorithm.NewNode) { | ||
| const clone = request.clone(); | ||
| const timestamp = clone.headers.get("X-Signature-Timestamp"); | ||
| const signature = clone.headers.get("X-Signature-Ed25519"); | ||
| const body = await clone.text(); | ||
| return verify(body, signature, timestamp, publicKey, subtleCrypto, algorithm); | ||
| } | ||
| async function verify(rawBody, signature, timestamp, publicKey, subtleCrypto, algorithm = PlatformAlgorithm.NewNode) { | ||
| if (timestamp == null || signature == null || rawBody == null) { | ||
| return false; | ||
| } | ||
| const key = await getCryptoKey(publicKey, subtleCrypto, algorithm); | ||
| const name = typeof algorithm === "string" ? algorithm : algorithm.name; | ||
| const isVerified = await subtleCrypto.verify( | ||
| name, | ||
| key, | ||
| hexStringToBinary(signature), | ||
| encoder.encode(`${timestamp ?? ""}${rawBody}`) | ||
| ); | ||
| return isVerified; | ||
| } | ||
| exports.PlatformAlgorithm = PlatformAlgorithm; | ||
| exports.hexStringToBinary = hexStringToBinary; | ||
| exports.isValidRequest = isValidRequest; | ||
| exports.verify = verify; |
| interface SubtleCryptoSignAlgorithm { | ||
| name: string; | ||
| hash?: string | SubtleCryptoHashAlgorithm; | ||
| dataLength?: number; | ||
| saltLength?: number; | ||
| } | ||
| interface CryptoKeyKeyAlgorithm { | ||
| name: string; | ||
| } | ||
| interface CryptoKeyAesKeyAlgorithm { | ||
| name: string; | ||
| length: number; | ||
| } | ||
| interface CryptoKeyHmacKeyAlgorithm { | ||
| name: string; | ||
| hash: CryptoKeyKeyAlgorithm; | ||
| length: number; | ||
| } | ||
| interface CryptoKeyRsaKeyAlgorithm { | ||
| name: string; | ||
| modulusLength: number; | ||
| publicExponent: ArrayBuffer; | ||
| hash?: CryptoKeyKeyAlgorithm; | ||
| } | ||
| interface CryptoKeyEllipticKeyAlgorithm { | ||
| name: string; | ||
| namedCurve: string; | ||
| } | ||
| interface CryptoKeyVoprfKeyAlgorithm { | ||
| name: string; | ||
| hash: CryptoKeyKeyAlgorithm; | ||
| namedCurve: string; | ||
| } | ||
| interface CryptoKeyOprfKeyAlgorithm { | ||
| name: string; | ||
| namedCurve: string; | ||
| } | ||
| type CryptoKeyAlgorithmVariant = CryptoKeyKeyAlgorithm | CryptoKeyAesKeyAlgorithm | CryptoKeyHmacKeyAlgorithm | CryptoKeyRsaKeyAlgorithm | CryptoKeyEllipticKeyAlgorithm | CryptoKeyVoprfKeyAlgorithm | CryptoKeyOprfKeyAlgorithm; | ||
| interface CryptoKey { | ||
| readonly type: string; | ||
| readonly extractable: boolean; | ||
| readonly algorithm: CryptoKeyAlgorithmVariant; | ||
| readonly usages: string[]; | ||
| } | ||
| interface SubtleCrypto { | ||
| verify(algorithm: string | SubtleCryptoSignAlgorithm, key: CryptoKey, signature: ArrayBuffer | ArrayBufferView, data: ArrayBuffer | ArrayBufferView): Promise<boolean>; | ||
| importKey(format: string, keyData: ArrayBuffer, algorithm: string | SubtleCryptoImportKeyAlgorithm, extractable: boolean, keyUsages: string[]): Promise<CryptoKey>; | ||
| } | ||
| interface SubtleCryptoHashAlgorithm { | ||
| name: string; | ||
| } | ||
| interface SubtleCryptoHashAlgorithm { | ||
| name: string; | ||
| } | ||
| interface SubtleCryptoImportKeyAlgorithm { | ||
| name: string; | ||
| hash?: string | SubtleCryptoHashAlgorithm; | ||
| length?: number; | ||
| namedCurve?: string; | ||
| compressed?: boolean; | ||
| } | ||
| interface Body { | ||
| text(): Promise<string>; | ||
| } | ||
| interface Headers { | ||
| get(name: string): string | null; | ||
| } | ||
| interface Request extends Body { | ||
| readonly headers: Headers; | ||
| clone(): Request; | ||
| } | ||
| /** | ||
| * Helper method that takes in a hex string and converts it to its binary representation. | ||
| * @param key - Hex string to convert to binary | ||
| * @returns The binary form of a hex string | ||
| */ | ||
| declare function hexStringToBinary(key: string | null): ArrayBufferLike; | ||
| /** | ||
| * Helper values for popular platforms | ||
| */ | ||
| declare const PlatformAlgorithm: { | ||
| Web: string; | ||
| /** | ||
| * Node v18.4.0+ | ||
| * Node v16.17.0+ | ||
| * For Node v17, use OldNode | ||
| */ | ||
| NewNode: string; | ||
| /** | ||
| * Node v18.3.0 and below | ||
| * Node v17.0.0+ | ||
| * Node v16.16.0 and below | ||
| */ | ||
| OldNode: { | ||
| name: string; | ||
| namedCurve: string; | ||
| public: boolean; | ||
| }; | ||
| Cloudflare: { | ||
| name: string; | ||
| namedCurve: string; | ||
| public: boolean; | ||
| }; | ||
| /** | ||
| * Despite being documented as `{ name: "eddsa", namedCurve: "ed25519"}` or | ||
| * `{ name: "ecdsa", namedCurve: "ed25519" }`, Vercel uses the same format as | ||
| * Cloudflare in Production (despite Dev using documented formats) | ||
| */ | ||
| VercelProd: { | ||
| name: string; | ||
| namedCurve: string; | ||
| public: boolean; | ||
| }; | ||
| /** | ||
| * Despite being documented as using this format, Vercel uses the same format | ||
| * as Cloudflare in Production and only uses this format in Development. | ||
| */ | ||
| VercelDev: { | ||
| name: string; | ||
| namedCurve: string; | ||
| }; | ||
| }; | ||
| /** | ||
| * Determines if a request is valid or not based on provided values | ||
| * @param rawBody - The raw body of the request | ||
| * @param signature - The signature header of the request | ||
| * @param timestamp - The timestamp header of the request | ||
| * @param publicKey - The application's public key | ||
| * @param subtleCrypto - The crypto engine to use | ||
| * @param algorithm - The name of the crypto algorithm to use | ||
| * @returns Whether the request is valid or not | ||
| */ | ||
| declare function verify(rawBody: string | null | undefined, signature: string | null | undefined, timestamp: string | null | undefined, publicKey: string, subtleCrypto: SubtleCrypto, algorithm?: SubtleCryptoImportKeyAlgorithm | string): Promise<boolean>; | ||
| export { PlatformAlgorithm as P, Request as R, SubtleCryptoImportKeyAlgorithm as S, hexStringToBinary as h, verify as v }; |
+3
-5
| 'use strict'; | ||
| Object.defineProperty(exports, '__esModule', { value: true }); | ||
| const crypto = require('node:crypto'); | ||
| const verify = require('./shared/discord-verify.59ff0ac4.cjs'); | ||
| const verify = require('./shared/discord-verify.786a0ce0.cjs'); | ||
| function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; } | ||
| function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; } | ||
| const crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto); | ||
| const crypto__default = /*#__PURE__*/_interopDefaultCompat(crypto); | ||
@@ -12,0 +10,0 @@ async function isValidRequest(request, publicKey, algorithm) { |
+5
-5
@@ -1,3 +0,3 @@ | ||
| import { R as Request, S as SubtleCryptoImportKeyAlgorithm } from './verify-a44bae14.js'; | ||
| export { P as PlatformAlgorithm, h as hexStringToBinary, v as verify } from './verify-a44bae14.js'; | ||
| import { R as Request, S as SubtleCryptoImportKeyAlgorithm } from './verify-b4db2f9d.js'; | ||
| export { P as PlatformAlgorithm, h as hexStringToBinary, v as verify } from './verify-b4db2f9d.js'; | ||
@@ -9,5 +9,5 @@ /** | ||
| * to calling this function. | ||
| * @param request Request to verify. This should not have been consumed yet. | ||
| * @param publicKey The application's public key | ||
| * @param algorithm The name of the crypto algorithm to use | ||
| * @param request - Request to verify. This should not have been consumed yet. | ||
| * @param publicKey - The application's public key | ||
| * @param algorithm - The name of the crypto algorithm to use | ||
| * @returns Whether the request is valid or not | ||
@@ -14,0 +14,0 @@ */ |
+3
-3
| import crypto from 'node:crypto'; | ||
| import { i as isValidRequest$1, P as PlatformAlgorithm } from './shared/discord-verify.b06f072d.mjs'; | ||
| export { P as PlatformAlgorithm, h as hexStringToBinary, v as verify } from './shared/discord-verify.b06f072d.mjs'; | ||
| import { i as isValidRequest$1, P as PlatformAlgorithm } from './shared/discord-verify.710b4879.mjs'; | ||
| export { h as hexStringToBinary, v as verify } from './shared/discord-verify.710b4879.mjs'; | ||
@@ -26,2 +26,2 @@ async function isValidRequest(request, publicKey, algorithm) { | ||
| export { isValidRequest }; | ||
| export { PlatformAlgorithm, isValidRequest }; |
+1
-3
| 'use strict'; | ||
| Object.defineProperty(exports, '__esModule', { value: true }); | ||
| const verify = require('./shared/discord-verify.786a0ce0.cjs'); | ||
| const verify = require('./shared/discord-verify.59ff0ac4.cjs'); | ||
| async function isValidRequest(request, publicKey, algorithm = verify.PlatformAlgorithm.Web) { | ||
@@ -8,0 +6,0 @@ return verify.isValidRequest(request, publicKey, crypto.subtle, algorithm); |
+5
-5
@@ -1,3 +0,3 @@ | ||
| import { R as Request, S as SubtleCryptoImportKeyAlgorithm } from './verify-a44bae14.js'; | ||
| export { P as PlatformAlgorithm, h as hexStringToBinary, v as verify } from './verify-a44bae14.js'; | ||
| import { R as Request, S as SubtleCryptoImportKeyAlgorithm } from './verify-b4db2f9d.js'; | ||
| export { P as PlatformAlgorithm, h as hexStringToBinary, v as verify } from './verify-b4db2f9d.js'; | ||
@@ -7,5 +7,5 @@ /** | ||
| * to calling this function. | ||
| * @param request Request to verify. This should not have been consumed yet. | ||
| * @param publicKey The application's public key | ||
| * @param algorithm The name of the crypto algorithm to use | ||
| * @param request - Request to verify. This should not have been consumed yet. | ||
| * @param publicKey - The application's public key | ||
| * @param algorithm - The name of the crypto algorithm to use | ||
| * @returns Whether the request is valid or not | ||
@@ -12,0 +12,0 @@ */ |
+3
-3
@@ -1,3 +0,3 @@ | ||
| import { i as isValidRequest$1, P as PlatformAlgorithm } from './shared/discord-verify.b06f072d.mjs'; | ||
| export { P as PlatformAlgorithm, h as hexStringToBinary, v as verify } from './shared/discord-verify.b06f072d.mjs'; | ||
| import { i as isValidRequest$1, P as PlatformAlgorithm } from './shared/discord-verify.710b4879.mjs'; | ||
| export { h as hexStringToBinary, v as verify } from './shared/discord-verify.710b4879.mjs'; | ||
@@ -8,2 +8,2 @@ async function isValidRequest(request, publicKey, algorithm = PlatformAlgorithm.Web) { | ||
| export { isValidRequest }; | ||
| export { PlatformAlgorithm, isValidRequest }; |
+3
-3
| { | ||
| "name": "discord-verify", | ||
| "version": "1.0.2", | ||
| "version": "1.0.3", | ||
| "author": "Ian Mitchell", | ||
@@ -41,3 +41,3 @@ "description": "A library for verifying the authenticity of requests coming from the Discord Interactions API", | ||
| "scripts": { | ||
| "clean": "rimraf dist", | ||
| "clean": "rimraf dist .turbo", | ||
| "build": "unbuild", | ||
@@ -60,4 +60,4 @@ "typecheck": "tsc --noEmit", | ||
| "dependencies": { | ||
| "@types/express": "^4.17.14" | ||
| "@types/express": "^4.17.17" | ||
| } | ||
| } |
+3
-3
@@ -16,5 +16,5 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/prefer-ts-expect-error | ||
| * to calling this function. | ||
| * @param request Request to verify. This should not have been consumed yet. | ||
| * @param publicKey The application's public key | ||
| * @param algorithm The name of the crypto algorithm to use | ||
| * @param request - Request to verify. This should not have been consumed yet. | ||
| * @param publicKey - The application's public key | ||
| * @param algorithm - The name of the crypto algorithm to use | ||
| * @returns Whether the request is valid or not | ||
@@ -21,0 +21,0 @@ */ |
+3
-3
@@ -19,5 +19,5 @@ import { | ||
| * to calling this function. | ||
| * @param request Request to verify. This should not have been consumed yet. | ||
| * @param publicKey The application's public key | ||
| * @param algorithm The name of the crypto algorithm to use | ||
| * @param request - Request to verify. This should not have been consumed yet. | ||
| * @param publicKey - The application's public key | ||
| * @param algorithm - The name of the crypto algorithm to use | ||
| * @returns Whether the request is valid or not | ||
@@ -24,0 +24,0 @@ */ |
| 'use strict'; | ||
| const encoder = new TextEncoder(); | ||
| const KEYS = { | ||
| ZERO: 48, | ||
| A: 65, | ||
| a: 97 | ||
| }; | ||
| function hexCharToBinary(char) { | ||
| const code = char.charCodeAt(0); | ||
| if (code >= KEYS.a) { | ||
| return code - KEYS.a + 10; | ||
| } | ||
| if (code >= KEYS.A) { | ||
| return code - KEYS.A + 10; | ||
| } | ||
| return code - KEYS.ZERO; | ||
| } | ||
| function hexStringToBinary(key) { | ||
| if (key == null || key.length % 2 !== 0) { | ||
| return new Uint8Array(0).buffer; | ||
| } | ||
| const view = new Uint8Array(key.length / 2); | ||
| for (let i = 0, o = 0; i < key.length; i += 2, ++o) { | ||
| view[o] = hexCharToBinary(key[i]) << 4 | hexCharToBinary(key[i + 1]); | ||
| } | ||
| return view.buffer; | ||
| } | ||
| async function getCryptoKey(publicKey, subtleCrypto, algorithm) { | ||
| const key = await subtleCrypto.importKey( | ||
| "raw", | ||
| hexStringToBinary(publicKey), | ||
| algorithm, | ||
| true, | ||
| ["verify"] | ||
| ); | ||
| return key; | ||
| } | ||
| const PlatformAlgorithm = { | ||
| Web: "Ed25519", | ||
| NewNode: "Ed25519", | ||
| OldNode: { | ||
| name: "NODE-ED25519", | ||
| namedCurve: "NODE-ED25519", | ||
| public: true | ||
| }, | ||
| Cloudflare: { | ||
| name: "NODE-ED25519", | ||
| namedCurve: "NODE-ED25519", | ||
| public: true | ||
| }, | ||
| VercelProd: { | ||
| name: "NODE-ED25519", | ||
| namedCurve: "NODE-ED25519", | ||
| public: true | ||
| }, | ||
| VercelDev: { name: "eddsa", namedCurve: "ed25519" } | ||
| }; | ||
| async function isValidRequest(request, publicKey, subtleCrypto, algorithm = PlatformAlgorithm.NewNode) { | ||
| const clone = request.clone(); | ||
| const timestamp = clone.headers.get("X-Signature-Timestamp"); | ||
| const signature = clone.headers.get("X-Signature-Ed25519"); | ||
| const body = await clone.text(); | ||
| return verify(body, signature, timestamp, publicKey, subtleCrypto, algorithm); | ||
| } | ||
| async function verify(rawBody, signature, timestamp, publicKey, subtleCrypto, algorithm = PlatformAlgorithm.NewNode) { | ||
| if (timestamp == null || signature == null || rawBody == null) { | ||
| return false; | ||
| } | ||
| const key = await getCryptoKey(publicKey, subtleCrypto, algorithm); | ||
| const name = typeof algorithm === "string" ? algorithm : algorithm.name; | ||
| const isVerified = await subtleCrypto.verify( | ||
| name, | ||
| key, | ||
| hexStringToBinary(signature), | ||
| encoder.encode(`${timestamp ?? ""}${rawBody}`) | ||
| ); | ||
| return isVerified; | ||
| } | ||
| exports.PlatformAlgorithm = PlatformAlgorithm; | ||
| exports.hexStringToBinary = hexStringToBinary; | ||
| exports.isValidRequest = isValidRequest; | ||
| exports.verify = verify; |
| const encoder = new TextEncoder(); | ||
| const KEYS = { | ||
| ZERO: 48, | ||
| A: 65, | ||
| a: 97 | ||
| }; | ||
| function hexCharToBinary(char) { | ||
| const code = char.charCodeAt(0); | ||
| if (code >= KEYS.a) { | ||
| return code - KEYS.a + 10; | ||
| } | ||
| if (code >= KEYS.A) { | ||
| return code - KEYS.A + 10; | ||
| } | ||
| return code - KEYS.ZERO; | ||
| } | ||
| function hexStringToBinary(key) { | ||
| if (key == null || key.length % 2 !== 0) { | ||
| return new Uint8Array(0).buffer; | ||
| } | ||
| const view = new Uint8Array(key.length / 2); | ||
| for (let i = 0, o = 0; i < key.length; i += 2, ++o) { | ||
| view[o] = hexCharToBinary(key[i]) << 4 | hexCharToBinary(key[i + 1]); | ||
| } | ||
| return view.buffer; | ||
| } | ||
| async function getCryptoKey(publicKey, subtleCrypto, algorithm) { | ||
| const key = await subtleCrypto.importKey( | ||
| "raw", | ||
| hexStringToBinary(publicKey), | ||
| algorithm, | ||
| true, | ||
| ["verify"] | ||
| ); | ||
| return key; | ||
| } | ||
| const PlatformAlgorithm = { | ||
| Web: "Ed25519", | ||
| NewNode: "Ed25519", | ||
| OldNode: { | ||
| name: "NODE-ED25519", | ||
| namedCurve: "NODE-ED25519", | ||
| public: true | ||
| }, | ||
| Cloudflare: { | ||
| name: "NODE-ED25519", | ||
| namedCurve: "NODE-ED25519", | ||
| public: true | ||
| }, | ||
| VercelProd: { | ||
| name: "NODE-ED25519", | ||
| namedCurve: "NODE-ED25519", | ||
| public: true | ||
| }, | ||
| VercelDev: { name: "eddsa", namedCurve: "ed25519" } | ||
| }; | ||
| async function isValidRequest(request, publicKey, subtleCrypto, algorithm = PlatformAlgorithm.NewNode) { | ||
| const clone = request.clone(); | ||
| const timestamp = clone.headers.get("X-Signature-Timestamp"); | ||
| const signature = clone.headers.get("X-Signature-Ed25519"); | ||
| const body = await clone.text(); | ||
| return verify(body, signature, timestamp, publicKey, subtleCrypto, algorithm); | ||
| } | ||
| async function verify(rawBody, signature, timestamp, publicKey, subtleCrypto, algorithm = PlatformAlgorithm.NewNode) { | ||
| if (timestamp == null || signature == null || rawBody == null) { | ||
| return false; | ||
| } | ||
| const key = await getCryptoKey(publicKey, subtleCrypto, algorithm); | ||
| const name = typeof algorithm === "string" ? algorithm : algorithm.name; | ||
| const isVerified = await subtleCrypto.verify( | ||
| name, | ||
| key, | ||
| hexStringToBinary(signature), | ||
| encoder.encode(`${timestamp ?? ""}${rawBody}`) | ||
| ); | ||
| return isVerified; | ||
| } | ||
| export { PlatformAlgorithm as P, hexStringToBinary as h, isValidRequest as i, verify as v }; |
| interface SubtleCryptoSignAlgorithm { | ||
| name: string; | ||
| hash?: string | SubtleCryptoHashAlgorithm; | ||
| dataLength?: number; | ||
| saltLength?: number; | ||
| } | ||
| interface CryptoKeyKeyAlgorithm { | ||
| name: string; | ||
| } | ||
| interface CryptoKeyAesKeyAlgorithm { | ||
| name: string; | ||
| length: number; | ||
| } | ||
| interface CryptoKeyHmacKeyAlgorithm { | ||
| name: string; | ||
| hash: CryptoKeyKeyAlgorithm; | ||
| length: number; | ||
| } | ||
| interface CryptoKeyRsaKeyAlgorithm { | ||
| name: string; | ||
| modulusLength: number; | ||
| publicExponent: ArrayBuffer; | ||
| hash?: CryptoKeyKeyAlgorithm; | ||
| } | ||
| interface CryptoKeyEllipticKeyAlgorithm { | ||
| name: string; | ||
| namedCurve: string; | ||
| } | ||
| interface CryptoKeyVoprfKeyAlgorithm { | ||
| name: string; | ||
| hash: CryptoKeyKeyAlgorithm; | ||
| namedCurve: string; | ||
| } | ||
| interface CryptoKeyOprfKeyAlgorithm { | ||
| name: string; | ||
| namedCurve: string; | ||
| } | ||
| declare type CryptoKeyAlgorithmVariant = CryptoKeyKeyAlgorithm | CryptoKeyAesKeyAlgorithm | CryptoKeyHmacKeyAlgorithm | CryptoKeyRsaKeyAlgorithm | CryptoKeyEllipticKeyAlgorithm | CryptoKeyVoprfKeyAlgorithm | CryptoKeyOprfKeyAlgorithm; | ||
| interface CryptoKey { | ||
| readonly type: string; | ||
| readonly extractable: boolean; | ||
| readonly algorithm: CryptoKeyAlgorithmVariant; | ||
| readonly usages: string[]; | ||
| } | ||
| interface SubtleCrypto { | ||
| verify(algorithm: string | SubtleCryptoSignAlgorithm, key: CryptoKey, signature: ArrayBuffer | ArrayBufferView, data: ArrayBuffer | ArrayBufferView): Promise<boolean>; | ||
| importKey(format: string, keyData: ArrayBuffer, algorithm: string | SubtleCryptoImportKeyAlgorithm, extractable: boolean, keyUsages: string[]): Promise<CryptoKey>; | ||
| } | ||
| interface SubtleCryptoHashAlgorithm { | ||
| name: string; | ||
| } | ||
| interface SubtleCryptoHashAlgorithm { | ||
| name: string; | ||
| } | ||
| interface SubtleCryptoImportKeyAlgorithm { | ||
| name: string; | ||
| hash?: string | SubtleCryptoHashAlgorithm; | ||
| length?: number; | ||
| namedCurve?: string; | ||
| compressed?: boolean; | ||
| } | ||
| interface Body { | ||
| text(): Promise<string>; | ||
| } | ||
| interface Headers { | ||
| get(name: string): string | null; | ||
| } | ||
| interface Request extends Body { | ||
| readonly headers: Headers; | ||
| clone(): Request; | ||
| } | ||
| /** | ||
| * Helper method that takes in a hex string and converts it to its binary representation. | ||
| * @param key - Hex string to convert to binary | ||
| * @returns The binary form of a hex string | ||
| */ | ||
| declare function hexStringToBinary(key: string | null): ArrayBufferLike; | ||
| /** | ||
| * Helper values for popular platforms | ||
| */ | ||
| declare const PlatformAlgorithm: { | ||
| Web: string; | ||
| /** | ||
| * Node v18.4.0+ | ||
| * Node v16.17.0+ | ||
| * For Node v17, use OldNode | ||
| */ | ||
| NewNode: string; | ||
| /** | ||
| * Node v18.3.0 and below | ||
| * Node v17.0.0+ | ||
| * Node v16.16.0 and below | ||
| */ | ||
| OldNode: { | ||
| name: string; | ||
| namedCurve: string; | ||
| public: boolean; | ||
| }; | ||
| Cloudflare: { | ||
| name: string; | ||
| namedCurve: string; | ||
| public: boolean; | ||
| }; | ||
| /** | ||
| * Despite being documented as `{ name: "eddsa", namedCurve: "ed25519"}` or | ||
| * `{ name: "ecdsa", namedCurve: "ed25519" }`, Vercel uses the same format as | ||
| * Cloudflare in Production (despite Dev using documented formats) | ||
| */ | ||
| VercelProd: { | ||
| name: string; | ||
| namedCurve: string; | ||
| public: boolean; | ||
| }; | ||
| /** | ||
| * Despite being documented as using this format, Vercel uses the same format | ||
| * as Cloudflare in Production and only uses this format in Development. | ||
| */ | ||
| VercelDev: { | ||
| name: string; | ||
| namedCurve: string; | ||
| }; | ||
| }; | ||
| /** | ||
| * Determines if a request is valid or not based on provided values | ||
| * @param rawBody - The raw body of the request | ||
| * @param signature - The signature header of the request | ||
| * @param timestamp - The timestamp header of the request | ||
| * @param publicKey - The application's public key | ||
| * @param subtleCrypto - The crypto engine to use | ||
| * @param algorithm - The name of the crypto algorithm to use | ||
| * @returns Whether the request is valid or not | ||
| */ | ||
| declare function verify(rawBody: string | null | undefined, signature: string | null | undefined, timestamp: string | null | undefined, publicKey: string, subtleCrypto: SubtleCrypto, algorithm?: SubtleCryptoImportKeyAlgorithm | string): Promise<boolean>; | ||
| export { PlatformAlgorithm as P, Request as R, SubtleCryptoImportKeyAlgorithm as S, hexStringToBinary as h, verify as v }; |
31658
3.38%800
4.71%Updated