From 5a2cf8c0a11aabb51e7c5847eae9475223cc1f5d Mon Sep 17 00:00:00 2001 From: Tom Meagher Date: Fri, 20 Dec 2024 19:24:26 -0500 Subject: [PATCH] refactor(siwe): use ox internally --- pnpm-lock.yaml | 28 +++- src/errors/siwe.test.ts | 26 ---- src/errors/siwe.ts | 19 --- src/siwe/index.ts | 16 ++- src/utils/siwe/createSiweMessage.test.ts | 59 +++----- src/utils/siwe/createSiweMessage.ts | 156 +-------------------- src/utils/siwe/generateSiweNonce.test.ts | 8 -- src/utils/siwe/generateSiweNonce.ts | 15 -- src/utils/siwe/parseSiweMessage.ts | 45 +----- src/utils/siwe/types.ts | 61 -------- src/utils/siwe/utils.test.ts | 42 ------ src/utils/siwe/utils.ts | 51 ------- src/utils/siwe/validateSiweMessage.test.ts | 4 +- src/utils/siwe/validateSiweMessage.ts | 60 +------- 14 files changed, 70 insertions(+), 520 deletions(-) delete mode 100644 src/errors/siwe.test.ts delete mode 100644 src/errors/siwe.ts delete mode 100644 src/utils/siwe/generateSiweNonce.test.ts delete mode 100644 src/utils/siwe/generateSiweNonce.ts delete mode 100644 src/utils/siwe/types.ts delete mode 100644 src/utils/siwe/utils.test.ts delete mode 100644 src/utils/siwe/utils.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7435ad62ea..d491f98007 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5444,6 +5444,14 @@ packages: typescript: optional: true + viem@2.21.56: + resolution: {integrity: sha512-lHcVd1sFDlVWu482Sb4j22a5+hXJWE8HwqLgXDH49L7mfdA5QHfkQgeyl7K2kKg6pBbPbxIwd9so/u3LcynKTg==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + viem@file:src: resolution: {directory: src, type: directory} peerDependencies: @@ -6707,7 +6715,7 @@ snapshots: pino-http: 8.6.1 pino-pretty: 10.3.1 prom-client: 14.2.0 - viem: 2.21.55(typescript@5.6.2)(zod@3.23.8) + viem: 2.21.56(typescript@5.6.2)(zod@3.23.8) yargs: 17.7.2 zod: 3.23.8 zod-validation-error: 1.5.0(zod@3.23.8) @@ -11325,6 +11333,24 @@ snapshots: - utf-8-validate - zod + viem@2.21.56(typescript@5.6.2)(zod@3.23.8): + dependencies: + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.1 + '@scure/bip32': 1.6.0 + '@scure/bip39': 1.5.0 + abitype: 1.0.7(typescript@5.6.2)(zod@3.23.8) + isows: 1.0.6(ws@8.18.0) + ox: 0.1.2(typescript@5.6.2)(zod@3.23.8) + webauthn-p256: 0.0.10 + ws: 8.18.0 + optionalDependencies: + typescript: 5.6.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + viem@file:src(typescript@5.6.2)(zod@3.23.8): dependencies: '@noble/curves': 1.7.0 diff --git a/src/errors/siwe.test.ts b/src/errors/siwe.test.ts deleted file mode 100644 index 7e92fd5eef..0000000000 --- a/src/errors/siwe.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { expect, test } from 'vitest' - -import { SiweInvalidMessageFieldError } from './siwe.js' - -test('SiweInvalidMessageFieldError', () => { - expect( - new SiweInvalidMessageFieldError({ - field: 'nonce', - metaMessages: [ - '- Nonce must be at least 8 characters.', - '- Nonce must be alphanumeric.', - '', - 'Provided value: foobarbaz$', - ], - }), - ).toMatchInlineSnapshot(` - [SiweInvalidMessageFieldError: Invalid Sign-In with Ethereum message field "nonce". - - - Nonce must be at least 8 characters. - - Nonce must be alphanumeric. - - Provided value: foobarbaz$ - - Version: viem@x.y.z] - `) -}) diff --git a/src/errors/siwe.ts b/src/errors/siwe.ts deleted file mode 100644 index ca1652e8f8..0000000000 --- a/src/errors/siwe.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { BaseError } from './base.js' - -export type SiweInvalidMessageFieldErrorType = SiweInvalidMessageFieldError & { - name: 'SiweInvalidMessageFieldError' -} -export class SiweInvalidMessageFieldError extends BaseError { - constructor(parameters: { - docsPath?: string | undefined - field: string - metaMessages?: string[] | undefined - }) { - const { docsPath, field, metaMessages } = parameters - super(`Invalid Sign-In with Ethereum message field "${field}".`, { - docsPath, - metaMessages, - name: 'SiweInvalidMessageFieldError', - }) - } -} diff --git a/src/siwe/index.ts b/src/siwe/index.ts index 647d08b642..f97383fc44 100644 --- a/src/siwe/index.ts +++ b/src/siwe/index.ts @@ -1,4 +1,10 @@ // biome-ignore lint/performance/noBarrelFile: entrypoint module +export { + type Message as SiweMessage, + generateNonce as generateSiweNonce, + InvalidMessageFieldError as SiweInvalidMessageFieldError, +} from 'ox/Siwe' + export { verifySiweMessage, type VerifySiweMessageParameters, @@ -13,7 +19,6 @@ export { type CreateSiweMessageErrorType, } from '../utils/siwe/createSiweMessage.js' -export { generateSiweNonce } from '../utils/siwe/generateSiweNonce.js' export { parseSiweMessage } from '../utils/siwe/parseSiweMessage.js' export { @@ -22,9 +27,8 @@ export { type ValidateSiweMessageReturnType, } from '../utils/siwe/validateSiweMessage.js' -export type { SiweMessage } from '../utils/siwe/types.js' +import type { Siwe } from 'ox' -export { - type SiweInvalidMessageFieldErrorType, - SiweInvalidMessageFieldError, -} from '../errors/siwe.js' +export type SiweInvalidMessageFieldErrorType = Siwe.InvalidMessageFieldError & { + name: 'SiweInvalidMessageFieldError' +} diff --git a/src/utils/siwe/createSiweMessage.test.ts b/src/utils/siwe/createSiweMessage.test.ts index 31c3e8cac9..61b7db7208 100644 --- a/src/utils/siwe/createSiweMessage.test.ts +++ b/src/utils/siwe/createSiweMessage.test.ts @@ -1,8 +1,8 @@ +import type { Siwe } from 'ox' import { expect, test, vi } from 'vitest' import { mainnet } from '../../chains/definitions/mainnet.js' import { createSiweMessage } from './createSiweMessage.js' -import type { SiweMessage } from './types.js' const message = { address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', @@ -11,7 +11,7 @@ const message = { nonce: 'foobarbaz', uri: 'https://example.com/path', version: '1', -} satisfies SiweMessage +} satisfies Siwe.Message test('default', () => { vi.useFakeTimers() @@ -249,12 +249,9 @@ test('behavior: invalid address', () => { expect(() => createSiweMessage({ ...message, address: '0xfoobarbaz' }), ).toThrowErrorMatchingInlineSnapshot(` - [InvalidAddressError: Address "0xfoobarbaz" is invalid. + [Address.InvalidAddressError: Address "0xfoobarbaz" is invalid. - - Address must be a hex value of 20 bytes (40 hex characters). - - Address must match its checksum counterpart. - - Version: viem@x.y.z] + Details: Address is not a 20 byte (40 hexadecimal character) value.] `) }) @@ -262,14 +259,12 @@ test('behavior: invalid chainId', () => { expect(() => createSiweMessage({ ...message, chainId: 1.1 }), ).toThrowErrorMatchingInlineSnapshot(` - [SiweInvalidMessageFieldError: Invalid Sign-In with Ethereum message field "chainId". + [Siwe.InvalidMessageFieldError: Invalid Sign-In with Ethereum message field "chainId". - Chain ID must be a EIP-155 chain ID. - See https://eips.ethereum.org/EIPS/eip-155 - Provided value: 1.1 - - Version: viem@x.y.z] + Provided value: 1.1] `) }) @@ -277,14 +272,12 @@ test('behavior: invalid domain', () => { expect(() => createSiweMessage({ ...message, domain: '#foo' }), ).toThrowErrorMatchingInlineSnapshot(` - [SiweInvalidMessageFieldError: Invalid Sign-In with Ethereum message field "domain". + [Siwe.InvalidMessageFieldError: Invalid Sign-In with Ethereum message field "domain". - Domain must be an RFC 3986 authority. - See https://www.rfc-editor.org/rfc/rfc3986 - Provided value: #foo - - Version: viem@x.y.z] + Provided value: #foo] `) }) @@ -292,14 +285,12 @@ test('behavior: invalid nonce', () => { expect(() => createSiweMessage({ ...message, nonce: '#foo' }), ).toThrowErrorMatchingInlineSnapshot(` - [SiweInvalidMessageFieldError: Invalid Sign-In with Ethereum message field "nonce". + [Siwe.InvalidMessageFieldError: Invalid Sign-In with Ethereum message field "nonce". - Nonce must be at least 8 characters. - Nonce must be alphanumeric. - Provided value: #foo - - Version: viem@x.y.z] + Provided value: #foo] `) }) @@ -307,14 +298,12 @@ test('behavior: invalid uri', () => { expect(() => createSiweMessage({ ...message, uri: '#foo' }), ).toThrowErrorMatchingInlineSnapshot(` - [SiweInvalidMessageFieldError: Invalid Sign-In with Ethereum message field "uri". + [Siwe.InvalidMessageFieldError: Invalid Sign-In with Ethereum message field "uri". - URI must be a RFC 3986 URI referring to the resource that is the subject of the signing. - See https://www.rfc-editor.org/rfc/rfc3986 - Provided value: #foo - - Version: viem@x.y.z] + Provided value: #foo] `) }) @@ -323,13 +312,11 @@ test('behavior: invalid version', () => { // @ts-expect-error createSiweMessage({ ...message, version: '2' }), ).toThrowErrorMatchingInlineSnapshot(` - [SiweInvalidMessageFieldError: Invalid Sign-In with Ethereum message field "version". + [Siwe.InvalidMessageFieldError: Invalid Sign-In with Ethereum message field "version". - Version must be '1'. - Provided value: 2 - - Version: viem@x.y.z] + Provided value: 2] `) }) @@ -337,14 +324,12 @@ test('behavior: invalid scheme', () => { expect(() => createSiweMessage({ ...message, scheme: 'foo_bar' }), ).toThrowErrorMatchingInlineSnapshot(` - [SiweInvalidMessageFieldError: Invalid Sign-In with Ethereum message field "scheme". + [Siwe.InvalidMessageFieldError: Invalid Sign-In with Ethereum message field "scheme". - Scheme must be an RFC 3986 URI scheme. - See https://www.rfc-editor.org/rfc/rfc3986#section-3.1 - Provided value: foo_bar - - Version: viem@x.y.z] + Provided value: foo_bar] `) }) @@ -352,14 +337,12 @@ test('behavior: invalid statement', () => { expect(() => createSiweMessage({ ...message, statement: 'foo\nbar' }), ).toThrowErrorMatchingInlineSnapshot(` - [SiweInvalidMessageFieldError: Invalid Sign-In with Ethereum message field "statement". + [Siwe.InvalidMessageFieldError: Invalid Sign-In with Ethereum message field "statement". - Statement must not include '\\n'. Provided value: foo - bar - - Version: viem@x.y.z] + bar] `) }) @@ -370,14 +353,12 @@ test('behavior: invalid resources', () => { resources: ['https://example.com', 'foo'], }), ).toThrowErrorMatchingInlineSnapshot(` - [SiweInvalidMessageFieldError: Invalid Sign-In with Ethereum message field "resources". + [Siwe.InvalidMessageFieldError: Invalid Sign-In with Ethereum message field "resources". - Every resource must be a RFC 3986 URI. - See https://www.rfc-editor.org/rfc/rfc3986 - Provided value: foo - - Version: viem@x.y.z] + Provided value: https://example.com] `) }) diff --git a/src/utils/siwe/createSiweMessage.ts b/src/utils/siwe/createSiweMessage.ts index cda7c2b30e..76b0986976 100644 --- a/src/utils/siwe/createSiweMessage.ts +++ b/src/utils/siwe/createSiweMessage.ts @@ -1,19 +1,13 @@ -import { - SiweInvalidMessageFieldError, - type SiweInvalidMessageFieldErrorType, -} from '../../errors/siwe.js' +import * as Siwe from 'ox/Siwe' + import type { ErrorType } from '../../errors/utils.js' -import { type GetAddressErrorType, getAddress } from '../address/getAddress.js' -import type { SiweMessage } from './types.js' -import { isUri } from './utils.js' -export type CreateSiweMessageParameters = SiweMessage +export type CreateSiweMessageParameters = Siwe.Message export type CreateSiweMessageReturnType = string export type CreateSiweMessageErrorType = - | GetAddressErrorType - | SiweInvalidMessageFieldErrorType + | Siwe.createMessage.ErrorType | ErrorType /** @@ -34,145 +28,5 @@ export type CreateSiweMessageErrorType = export function createSiweMessage( parameters: CreateSiweMessageParameters, ): CreateSiweMessageReturnType { - const { - chainId, - domain, - expirationTime, - issuedAt = new Date(), - nonce, - notBefore, - requestId, - resources, - scheme, - uri, - version, - } = parameters - - // Validate fields - { - // Required fields - if (chainId !== Math.floor(chainId)) - throw new SiweInvalidMessageFieldError({ - field: 'chainId', - metaMessages: [ - '- Chain ID must be a EIP-155 chain ID.', - '- See https://eips.ethereum.org/EIPS/eip-155', - '', - `Provided value: ${chainId}`, - ], - }) - if ( - !( - domainRegex.test(domain) || - ipRegex.test(domain) || - localhostRegex.test(domain) - ) - ) - throw new SiweInvalidMessageFieldError({ - field: 'domain', - metaMessages: [ - '- Domain must be an RFC 3986 authority.', - '- See https://www.rfc-editor.org/rfc/rfc3986', - '', - `Provided value: ${domain}`, - ], - }) - if (!nonceRegex.test(nonce)) - throw new SiweInvalidMessageFieldError({ - field: 'nonce', - metaMessages: [ - '- Nonce must be at least 8 characters.', - '- Nonce must be alphanumeric.', - '', - `Provided value: ${nonce}`, - ], - }) - if (!isUri(uri)) - throw new SiweInvalidMessageFieldError({ - field: 'uri', - metaMessages: [ - '- URI must be a RFC 3986 URI referring to the resource that is the subject of the signing.', - '- See https://www.rfc-editor.org/rfc/rfc3986', - '', - `Provided value: ${uri}`, - ], - }) - if (version !== '1') - throw new SiweInvalidMessageFieldError({ - field: 'version', - metaMessages: [ - "- Version must be '1'.", - '', - `Provided value: ${version}`, - ], - }) - - // Optional fields - if (scheme && !schemeRegex.test(scheme)) - throw new SiweInvalidMessageFieldError({ - field: 'scheme', - metaMessages: [ - '- Scheme must be an RFC 3986 URI scheme.', - '- See https://www.rfc-editor.org/rfc/rfc3986#section-3.1', - '', - `Provided value: ${scheme}`, - ], - }) - const statement = parameters.statement - if (statement?.includes('\n')) - throw new SiweInvalidMessageFieldError({ - field: 'statement', - metaMessages: [ - "- Statement must not include '\\n'.", - '', - `Provided value: ${statement}`, - ], - }) - } - - // Construct message - const address = getAddress(parameters.address) - const origin = (() => { - if (scheme) return `${scheme}://${domain}` - return domain - })() - const statement = (() => { - if (!parameters.statement) return '' - return `${parameters.statement}\n` - })() - const prefix = `${origin} wants you to sign in with your Ethereum account:\n${address}\n\n${statement}` - - let suffix = `URI: ${uri}\nVersion: ${version}\nChain ID: ${chainId}\nNonce: ${nonce}\nIssued At: ${issuedAt.toISOString()}` - - if (expirationTime) - suffix += `\nExpiration Time: ${expirationTime.toISOString()}` - if (notBefore) suffix += `\nNot Before: ${notBefore.toISOString()}` - if (requestId) suffix += `\nRequest ID: ${requestId}` - if (resources) { - let content = '\nResources:' - for (const resource of resources) { - if (!isUri(resource)) - throw new SiweInvalidMessageFieldError({ - field: 'resources', - metaMessages: [ - '- Every resource must be a RFC 3986 URI.', - '- See https://www.rfc-editor.org/rfc/rfc3986', - '', - `Provided value: ${resource}`, - ], - }) - content += `\n- ${resource}` - } - suffix += content - } - - return `${prefix}\n${suffix}` + return Siwe.createMessage(parameters) } - -const domainRegex = - /^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}(:[0-9]{1,5})?$/ -const ipRegex = - /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(:[0-9]{1,5})?$/ -const localhostRegex = /^localhost(:[0-9]{1,5})?$/ -const nonceRegex = /^[a-zA-Z0-9]{8,}$/ -const schemeRegex = /^([a-zA-Z][a-zA-Z0-9+-.]*)$/ diff --git a/src/utils/siwe/generateSiweNonce.test.ts b/src/utils/siwe/generateSiweNonce.test.ts deleted file mode 100644 index 3072065adb..0000000000 --- a/src/utils/siwe/generateSiweNonce.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { expect, test } from 'vitest' - -import { generateSiweNonce } from './generateSiweNonce.js' - -test('default', () => { - const nonce = generateSiweNonce() - expect(nonce.length).toMatchInlineSnapshot('96') -}) diff --git a/src/utils/siwe/generateSiweNonce.ts b/src/utils/siwe/generateSiweNonce.ts deleted file mode 100644 index 967c86eb3b..0000000000 --- a/src/utils/siwe/generateSiweNonce.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { uid } from '../../utils/uid.js' - -/** - * @description Generates random EIP-4361 nonce. - * - * @example - * const nonce = generateNonce() - * - * @see https://eips.ethereum.org/EIPS/eip-4361 - * - * @returns A randomly generated EIP-4361 nonce. - */ -export function generateSiweNonce(): string { - return uid(96) -} diff --git a/src/utils/siwe/parseSiweMessage.ts b/src/utils/siwe/parseSiweMessage.ts index ebab5da8cd..a495c2e7a7 100644 --- a/src/utils/siwe/parseSiweMessage.ts +++ b/src/utils/siwe/parseSiweMessage.ts @@ -1,7 +1,6 @@ -import type { Address } from 'abitype' +import * as Siwe from 'ox/Siwe' import type { ExactPartial, Prettify } from '../../types/utils.js' -import type { SiweMessage } from './types.js' /** * @description Parses EIP-4361 formatted message into message fields object. @@ -12,44 +11,6 @@ import type { SiweMessage } from './types.js' */ export function parseSiweMessage( message: string, -): Prettify> { - const { scheme, statement, ...prefix } = (message.match(prefixRegex) - ?.groups ?? {}) as { - address: Address - domain: string - scheme?: string - statement?: string - } - const { chainId, expirationTime, issuedAt, notBefore, requestId, ...suffix } = - (message.match(suffixRegex)?.groups ?? {}) as { - chainId: string - expirationTime?: string - issuedAt?: string - nonce: string - notBefore?: string - requestId?: string - uri: string - version: '1' - } - const resources = message.split('Resources:')[1]?.split('\n- ').slice(1) - return { - ...prefix, - ...suffix, - ...(chainId ? { chainId: Number(chainId) } : {}), - ...(expirationTime ? { expirationTime: new Date(expirationTime) } : {}), - ...(issuedAt ? { issuedAt: new Date(issuedAt) } : {}), - ...(notBefore ? { notBefore: new Date(notBefore) } : {}), - ...(requestId ? { requestId } : {}), - ...(resources ? { resources } : {}), - ...(scheme ? { scheme } : {}), - ...(statement ? { statement } : {}), - } +): Prettify> { + return Siwe.parseMessage(message) } - -// https://regexr.com/80gdj -const prefixRegex = - /^(?:(?[a-zA-Z][a-zA-Z0-9+-.]*):\/\/)?(?[a-zA-Z0-9+-.]*(?::[0-9]{1,5})?) (?:wants you to sign in with your Ethereum account:\n)(?
0x[a-fA-F0-9]{40})\n\n(?:(?.*)\n\n)?/ - -// https://regexr.com/80gf9 -const suffixRegex = - /(?:URI: (?.+))\n(?:Version: (?.+))\n(?:Chain ID: (?\d+))\n(?:Nonce: (?[a-zA-Z0-9]+))\n(?:Issued At: (?.+))(?:\nExpiration Time: (?.+))?(?:\nNot Before: (?.+))?(?:\nRequest ID: (?.+))?/ diff --git a/src/utils/siwe/types.ts b/src/utils/siwe/types.ts deleted file mode 100644 index ead665e157..0000000000 --- a/src/utils/siwe/types.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { Address } from 'abitype' - -/** - * @description EIP-4361 message fields - * - * @see https://eips.ethereum.org/EIPS/eip-4361 - */ -export type SiweMessage = { - /** - * The Ethereum address performing the signing. - */ - address: Address - /** - * The [EIP-155](https://eips.ethereum.org/EIPS/eip-155) Chain ID to which the session is bound, - */ - chainId: number - /** - * [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) authority that is requesting the signing. - */ - domain: string - /** - * Time when the signed authentication message is no longer valid. - */ - expirationTime?: Date | undefined - /** - * Time when the message was generated, typically the current time. - */ - issuedAt?: Date | undefined - /** - * A random string typically chosen by the relying party and used to prevent replay attacks. - */ - nonce: string - /** - * Time when the signed authentication message will become valid. - */ - notBefore?: Date | undefined - /** - * A system-specific identifier that may be used to uniquely refer to the sign-in request. - */ - requestId?: string | undefined - /** - * A list of information or references to information the user wishes to have resolved as part of authentication by the relying party. - */ - resources?: string[] | undefined - /** - * [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) URI scheme of the origin of the request. - */ - scheme?: string | undefined - /** - * A human-readable ASCII assertion that the user will sign. - */ - statement?: string | undefined - /** - * [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) URI referring to the resource that is the subject of the signing (as in the subject of a claim). - */ - uri: string - /** - * The current version of the SIWE Message. - */ - version: '1' -} diff --git a/src/utils/siwe/utils.test.ts b/src/utils/siwe/utils.test.ts deleted file mode 100644 index 5179dea79c..0000000000 --- a/src/utils/siwe/utils.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { expect, test } from 'vitest' - -import { isUri } from './utils.js' - -test('isUri - default', () => { - expect(isUri('https://example.com/foo')).toMatchInlineSnapshot( - `"https://example.com/foo"`, - ) -}) - -test('isUri - behavior: check for illegal characters', () => { - expect(isUri('^')).toBeFalsy() -}) - -test('isUri - incomplete hex escapes', () => { - expect(isUri('%$#')).toBeFalsy() - expect(isUri('%0:#')).toBeFalsy() -}) - -test('isUri - missing scheme', () => { - expect(isUri('example.com/foo')).toBeFalsy() -}) - -test('isUri - authority with missing path', () => { - expect(isUri('1http:////foo.html')).toBeFalsy() -}) - -test('isUri - scheme begins with letter', () => { - expect(isUri('$https://example.com/foo')).toBeFalsy() -}) - -test('isUri - query', () => { - expect(isUri('https://example.com/foo?bar')).toMatchInlineSnapshot( - `"https://example.com/foo?bar"`, - ) -}) - -test('isUri - fragment', () => { - expect(isUri('https://example.com/foo#bar')).toMatchInlineSnapshot( - `"https://example.com/foo#bar"`, - ) -}) diff --git a/src/utils/siwe/utils.ts b/src/utils/siwe/utils.ts deleted file mode 100644 index 240d980031..0000000000 --- a/src/utils/siwe/utils.ts +++ /dev/null @@ -1,51 +0,0 @@ -export function isUri(value: string) { - // based on https://github.com/ogt/valid-url - - // check for illegal characters - if (/[^a-z0-9\:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=\.\-\_\~\%]/i.test(value)) - return false - - // check for hex escapes that aren't complete - if (/%[^0-9a-f]/i.test(value)) return false - if (/%[0-9a-f](:?[^0-9a-f]|$)/i.test(value)) return false - - // from RFC 3986 - const splitted = splitUri(value) - const scheme = splitted[1] - const authority = splitted[2] - const path = splitted[3] - const query = splitted[4] - const fragment = splitted[5] - - // scheme and path are required, though the path can be empty - if (!(scheme?.length && path.length >= 0)) return false - - // if authority is present, the path must be empty or begin with a / - if (authority?.length) { - if (!(path.length === 0 || /^\//.test(path))) return false - } else { - // if authority is not present, the path must not start with // - if (/^\/\//.test(path)) return false - } - - // scheme must begin with a letter, then consist of letters, digits, +, ., or - - if (!/^[a-z][a-z0-9\+\-\.]*$/.test(scheme.toLowerCase())) return false - - let out = '' - // re-assemble the URL per section 5.3 in RFC 3986 - out += `${scheme}:` - if (authority?.length) out += `//${authority}` - - out += path - - if (query?.length) out += `?${query}` - if (fragment?.length) out += `#${fragment}` - - return out -} - -function splitUri(value: string) { - return value.match( - /(?:([^:\/?#]+):)?(?:\/\/([^\/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?/, - )! -} diff --git a/src/utils/siwe/validateSiweMessage.test.ts b/src/utils/siwe/validateSiweMessage.test.ts index e49683747d..fea82109f6 100644 --- a/src/utils/siwe/validateSiweMessage.test.ts +++ b/src/utils/siwe/validateSiweMessage.test.ts @@ -1,6 +1,6 @@ +import type { Siwe } from 'ox' import { expect, test, vi } from 'vitest' -import type { SiweMessage } from './types.js' import { validateSiweMessage } from './validateSiweMessage.js' const message = { @@ -10,7 +10,7 @@ const message = { nonce: 'foobarbaz', uri: 'https://example.com/path', version: '1', -} satisfies SiweMessage +} satisfies Siwe.Message test('default', () => { expect( diff --git a/src/utils/siwe/validateSiweMessage.ts b/src/utils/siwe/validateSiweMessage.ts index 2147a2f145..1fc5e4e9cc 100644 --- a/src/utils/siwe/validateSiweMessage.ts +++ b/src/utils/siwe/validateSiweMessage.ts @@ -1,37 +1,6 @@ -import type { Address } from 'abitype' +import * as Siwe from 'ox/Siwe' -import type { ExactPartial } from '../../types/utils.js' -import { isAddressEqual } from '../address/isAddressEqual.js' -import type { SiweMessage } from './types.js' - -export type ValidateSiweMessageParameters = { - /** - * Ethereum address to check against. - */ - address?: Address | undefined - /** - * [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) authority to check against. - */ - domain?: string | undefined - /** - * EIP-4361 message fields. - */ - message: ExactPartial - /** - * Random string to check against. - */ - nonce?: string | undefined - /** - * [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) URI scheme to check against. - */ - scheme?: string | undefined - /** - * Current time to check optional `expirationTime` and `notBefore` fields. - * - * @default new Date() - */ - time?: Date | undefined -} +export type ValidateSiweMessageParameters = Siwe.validateMessage.Value export type ValidateSiweMessageReturnType = boolean @@ -43,28 +12,5 @@ export type ValidateSiweMessageReturnType = boolean export function validateSiweMessage( parameters: ValidateSiweMessageParameters, ): ValidateSiweMessageReturnType { - const { - address, - domain, - message, - nonce, - scheme, - time = new Date(), - } = parameters - - if (domain && message.domain !== domain) return false - if (nonce && message.nonce !== nonce) return false - if (scheme && message.scheme !== scheme) return false - - if (message.expirationTime && time >= message.expirationTime) return false - if (message.notBefore && time < message.notBefore) return false - - try { - if (!message.address) return false - if (address && !isAddressEqual(message.address, address)) return false - } catch { - return false - } - - return true + return Siwe.validateMessage(parameters) }