UNPKG

ox

Version:

Ethereum Standard Library

230 lines 9.33 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.InvalidMessageFieldError = exports.suffixRegex = exports.prefixRegex = exports.schemeRegex = exports.nonceRegex = exports.localhostRegex = exports.ipRegex = exports.domainRegex = void 0; exports.createMessage = createMessage; exports.generateNonce = generateNonce; exports.isUri = isUri; exports.parseMessage = parseMessage; exports.validateMessage = validateMessage; const Address = require("./Address.js"); const Errors = require("./Errors.js"); const uid_js_1 = require("./internal/uid.js"); exports.domainRegex = /^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}(:[0-9]{1,5})?$/; exports.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})?$/; exports.localhostRegex = /^localhost(:[0-9]{1,5})?$/; exports.nonceRegex = /^[a-zA-Z0-9]{8,}$/; exports.schemeRegex = /^([a-zA-Z][a-zA-Z0-9+-.]*)$/; exports.prefixRegex = /^(?:(?<scheme>[a-zA-Z][a-zA-Z0-9+-.]*):\/\/)?(?<domain>[a-zA-Z0-9+-.]*(?::[0-9]{1,5})?) (?:wants you to sign in with your Ethereum account:\n)(?<address>0x[a-fA-F0-9]{40})\n\n(?:(?<statement>.*)\n\n)?/; exports.suffixRegex = /(?:URI: (?<uri>.+))\n(?:Version: (?<version>.+))\n(?:Chain ID: (?<chainId>\d+))\n(?:Nonce: (?<nonce>[a-zA-Z0-9]+))\n(?:Issued At: (?<issuedAt>.+))(?:\nExpiration Time: (?<expirationTime>.+))?(?:\nNot Before: (?<notBefore>.+))?(?:\nRequest ID: (?<requestId>.+))?/; function createMessage(value) { const { chainId, domain, expirationTime, issuedAt = new Date(), nonce, notBefore, requestId, resources, scheme, uri, version, } = value; { if (chainId !== Math.floor(chainId)) throw new InvalidMessageFieldError({ field: 'chainId', metaMessages: [ '- Chain ID must be a EIP-155 chain ID.', '- See https://eips.ethereum.org/EIPS/eip-155', '', `Provided value: ${chainId}`, ], }); if (!(exports.domainRegex.test(domain) || exports.ipRegex.test(domain) || exports.localhostRegex.test(domain))) throw new InvalidMessageFieldError({ field: 'domain', metaMessages: [ '- Domain must be an RFC 3986 authority.', '- See https://www.rfc-editor.org/rfc/rfc3986', '', `Provided value: ${domain}`, ], }); if (!exports.nonceRegex.test(nonce)) throw new InvalidMessageFieldError({ field: 'nonce', metaMessages: [ '- Nonce must be at least 8 characters.', '- Nonce must be alphanumeric.', '', `Provided value: ${nonce}`, ], }); if (!isUri(uri)) throw new InvalidMessageFieldError({ 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 InvalidMessageFieldError({ field: 'version', metaMessages: [ "- Version must be '1'.", '', `Provided value: ${version}`, ], }); if (scheme && !exports.schemeRegex.test(scheme)) throw new InvalidMessageFieldError({ 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 = value.statement; if (statement?.includes('\n')) throw new InvalidMessageFieldError({ field: 'statement', metaMessages: [ "- Statement must not include '\\n'.", '', `Provided value: ${statement}`, ], }); } const address = Address.from(value.address, { checksum: true }); const origin = (() => { if (scheme) return `${scheme}://${domain}`; return domain; })(); const statement = (() => { if (!value.statement) return ''; return `${value.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 InvalidMessageFieldError({ 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}`; } function generateNonce() { return (0, uid_js_1.uid)(96); } function isUri(value) { if (/[^a-z0-9\:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=\.\-\_\~\%]/i.test(value)) return false; if (/%[^0-9a-f]/i.test(value)) return false; if (/%[0-9a-f](:?[^0-9a-f]|$)/i.test(value)) return false; const splitted = splitUri(value); const scheme = splitted[1]; const authority = splitted[2]; const path = splitted[3]; const query = splitted[4]; const fragment = splitted[5]; if (!(scheme?.length && path && path.length >= 0)) return false; if (authority?.length) { if (!(path.length === 0 || /^\//.test(path))) return false; } else { if (/^\/\//.test(path)) return false; } if (!/^[a-z][a-z0-9\+\-\.]*$/.test(scheme.toLowerCase())) return false; let out = ''; out += `${scheme}:`; if (authority?.length) out += `//${authority}`; out += path; if (query?.length) out += `?${query}`; if (fragment?.length) out += `#${fragment}`; return out; } function splitUri(value) { return value.match(/(?:([^:\/?#]+):)?(?:\/\/([^\/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?/); } function parseMessage(message) { const { scheme, statement, ...prefix } = (message.match(exports.prefixRegex) ?.groups ?? {}); const { chainId, expirationTime, issuedAt, notBefore, requestId, ...suffix } = (message.match(exports.suffixRegex)?.groups ?? {}); 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 } : {}), }; } function validateMessage(value) { const { address, domain, message, nonce, scheme, time = new Date() } = value; 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 && !Address.isEqual(message.address, address)) return false; } catch { return false; } return true; } class InvalidMessageFieldError extends Errors.BaseError { constructor(parameters) { const { field, metaMessages } = parameters; super(`Invalid Sign-In with Ethereum message field "${field}".`, { metaMessages, }); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'Siwe.InvalidMessageFieldError' }); } } exports.InvalidMessageFieldError = InvalidMessageFieldError; //# sourceMappingURL=Siwe.js.map