UNPKG

siwe

Version:
277 lines (276 loc) 12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SiweMessage = void 0; // TODO: Figure out how to get types from this lib: const siwe_parser_1 = require("@spruceid/siwe-parser"); const ethersCompat_1 = require("./ethersCompat"); const types_1 = require("./types"); const utils_1 = require("./utils"); class SiweMessage { /** * Creates a parsed Sign-In with Ethereum Message (EIP-4361) object from a * string or an object. If a string is used an ABNF parser is called to * validate the parameter, otherwise the fields are attributed. * @param param {string | SiweMessage} Sign message as a string or an object. */ constructor(param) { if (typeof param === 'string') { /* the message string (including nonce) is valid or ParsedMessage will throw */ const parsedMessage = new siwe_parser_1.ParsedMessage(param); this.scheme = parsedMessage.scheme; this.domain = parsedMessage.domain; this.address = parsedMessage.address; this.statement = parsedMessage.statement; this.uri = parsedMessage.uri; this.version = parsedMessage.version; this.nonce = parsedMessage.nonce; this.issuedAt = parsedMessage.issuedAt; this.expirationTime = parsedMessage.expirationTime; this.notBefore = parsedMessage.notBefore; this.requestId = parsedMessage.requestId; this.chainId = parsedMessage.chainId; this.resources = parsedMessage.resources; } else { this.scheme = param === null || param === void 0 ? void 0 : param.scheme; this.domain = param.domain; this.address = param.address; this.statement = param === null || param === void 0 ? void 0 : param.statement; this.uri = param.uri; this.version = param.version; this.chainId = param.chainId; this.nonce = param.nonce; this.issuedAt = param === null || param === void 0 ? void 0 : param.issuedAt; this.expirationTime = param === null || param === void 0 ? void 0 : param.expirationTime; this.notBefore = param === null || param === void 0 ? void 0 : param.notBefore; this.requestId = param === null || param === void 0 ? void 0 : param.requestId; this.resources = param === null || param === void 0 ? void 0 : param.resources; if (typeof this.chainId === 'string') { this.chainId = (0, siwe_parser_1.parseIntegerNumber)(this.chainId); } this.nonce = this.nonce || (0, utils_1.generateNonce)(); /* the message object is valid or parsing its stringified value will throw */ new siwe_parser_1.ParsedMessage(this.prepareMessage()); } } /** * This function can be used to retrieve an EIP-4361 formatted message for * signature, although you can call it directly it's advised to use * [prepareMessage()] instead which will resolve to the correct method based * on the [type] attribute of this object, in case of other formats being * implemented. * @returns {string} EIP-4361 formatted message, ready for EIP-191 signing. */ toMessage() { /** Validates all fields of the object */ // this.validateMessage(); const headerPrefix = this.scheme ? `${this.scheme}://${this.domain}` : this.domain; const header = `${headerPrefix} wants you to sign in with your Ethereum account:`; const uriField = `URI: ${this.uri}`; let prefix = [header, this.address].join('\n'); const versionField = `Version: ${this.version}`; if (!this.nonce) { this.nonce = (0, utils_1.generateNonce)(); } const chainField = `Chain ID: ` + this.chainId || '1'; const nonceField = `Nonce: ${this.nonce}`; const suffixArray = [uriField, versionField, chainField, nonceField]; this.issuedAt = this.issuedAt || new Date().toISOString(); suffixArray.push(`Issued At: ${this.issuedAt}`); if (this.expirationTime) { const expiryField = `Expiration Time: ${this.expirationTime}`; suffixArray.push(expiryField); } if (this.notBefore) { suffixArray.push(`Not Before: ${this.notBefore}`); } if (this.requestId) { suffixArray.push(`Request ID: ${this.requestId}`); } if (this.resources) { suffixArray.push([`Resources:`, ...this.resources.map(x => `- ${x}`)].join('\n')); } const suffix = suffixArray.join('\n'); prefix = [prefix, this.statement].join('\n\n'); if (this.statement !== undefined) { prefix += '\n'; } return [prefix, suffix].join('\n'); } /** * This method parses all the fields in the object and creates a messaging for signing * message according with the type defined. * @returns {string} Returns a message ready to be signed according with the * type defined in the object. */ prepareMessage() { let message; switch (this.version) { case '1': { message = this.toMessage(); break; } default: { message = this.toMessage(); break; } } return message; } /** * Verifies the integrity of the object by matching its signature. * @param params Parameters to verify the integrity of the message, signature is required. * @returns {Promise<SiweMessage>} This object if valid. */ async verify(params, opts = { suppressExceptions: false }) { return new Promise((resolve, reject) => { var _a, _b, _c; const fail = result => { if (opts.suppressExceptions) { return resolve(result); } else { return reject(result); } }; const invalidParams = (0, utils_1.checkInvalidKeys)(params, types_1.VerifyParamsKeys); if (invalidParams.length > 0) { fail({ success: false, data: this, error: new Error(`${invalidParams.join(', ')} is/are not valid key(s) for VerifyParams.`), }); } const invalidOpts = (0, utils_1.checkInvalidKeys)(opts, types_1.VerifyOptsKeys); if (invalidOpts.length > 0) { fail({ success: false, data: this, error: new Error(`${invalidOpts.join(', ')} is/are not valid key(s) for VerifyOpts.`), }); } const { signature, scheme, domain, nonce, time } = params; /** Scheme for domain binding */ if (scheme && scheme !== this.scheme) { fail({ success: false, data: this, error: new types_1.SiweError(types_1.SiweErrorType.SCHEME_MISMATCH, scheme, this.scheme), }); } /** Domain binding */ if (domain && domain !== this.domain) { fail({ success: false, data: this, error: new types_1.SiweError(types_1.SiweErrorType.DOMAIN_MISMATCH, domain, this.domain), }); } /** Nonce binding */ if (nonce && nonce !== this.nonce) { fail({ success: false, data: this, error: new types_1.SiweError(types_1.SiweErrorType.NONCE_MISMATCH, nonce, this.nonce), }); } /** Check time or now */ const checkTime = new Date(time || new Date()); /** Message not expired */ if (this.expirationTime) { const expirationDate = new Date(this.expirationTime); if (checkTime.getTime() >= expirationDate.getTime()) { fail({ success: false, data: this, error: new types_1.SiweError(types_1.SiweErrorType.EXPIRED_MESSAGE, `${checkTime.toISOString()} < ${expirationDate.toISOString()}`, `${checkTime.toISOString()} >= ${expirationDate.toISOString()}`), }); } } /** Message is valid already */ if (this.notBefore) { const notBefore = new Date(this.notBefore); if (checkTime.getTime() < notBefore.getTime()) { fail({ success: false, data: this, error: new types_1.SiweError(types_1.SiweErrorType.NOT_YET_VALID_MESSAGE, `${checkTime.toISOString()} >= ${notBefore.toISOString()}`, `${checkTime.toISOString()} < ${notBefore.toISOString()}`), }); } } let EIP4361Message; try { EIP4361Message = this.prepareMessage(); } catch (e) { fail({ success: false, data: this, error: e, }); } /** Recover address from signature */ let addr; try { addr = (0, ethersCompat_1.verifyMessage)(EIP4361Message, signature); } catch (e) { console.error(e); } /** Match signature with message's address */ if (addr === this.address) { return resolve({ success: true, data: this, }); } else { const EIP1271Promise = (0, utils_1.checkContractWalletSignature)(this, signature, opts.provider) .then(isValid => { if (!isValid) { return { success: false, data: this, error: new types_1.SiweError(types_1.SiweErrorType.INVALID_SIGNATURE, addr, `Resolved address to be ${this.address}`), }; } return { success: true, data: this, }; }) .catch(error => { return { success: false, data: this, error, }; }); Promise.all([ EIP1271Promise, (_c = (_b = (_a = opts === null || opts === void 0 ? void 0 : opts.verificationFallback) === null || _a === void 0 ? void 0 : _a.call(opts, params, opts, this, EIP1271Promise)) === null || _b === void 0 ? void 0 : _b.then(res => res)) === null || _c === void 0 ? void 0 : _c.catch((res) => res), ]).then(([EIP1271Response, fallbackResponse]) => { if (fallbackResponse) { if (fallbackResponse.success) { return resolve(fallbackResponse); } else { fail(fallbackResponse); } } else { if (EIP1271Response.success) { return resolve(EIP1271Response); } else { fail(EIP1271Response); } } }); } }); } } exports.SiweMessage = SiweMessage;