siwe
Version:
Sign-In with Ethereum
277 lines (276 loc) • 12 kB
JavaScript
"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;