edge-core-js
Version:
Edge account & wallet management library
1,558 lines (1,466 loc) • 437 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var yaob = require('yaob');
var disklet = require('disklet');
var scryptJs = require('scrypt-js');
var rfc4648 = require('rfc4648');
var serverlet = require('serverlet');
var cleaners = require('cleaners');
var baseX = require('base-x');
var aesjs = require('aes-js');
var hashjs = require('hash.js');
var edgeSyncClient = require('edge-sync-client');
var redux = require('redux');
var reduxPixies = require('redux-pixies');
var biggystring = require('biggystring');
var currencyCodes = require('currency-codes');
var elliptic = require('elliptic');
var yavent = require('yavent');
var reduxKeto = require('redux-keto');
var crypto = require('crypto');
var fetch$1 = require('node-fetch');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var scryptJs__default = /*#__PURE__*/_interopDefaultLegacy(scryptJs);
var baseX__default = /*#__PURE__*/_interopDefaultLegacy(baseX);
var aesjs__default = /*#__PURE__*/_interopDefaultLegacy(aesjs);
var hashjs__default = /*#__PURE__*/_interopDefaultLegacy(hashjs);
var elliptic__default = /*#__PURE__*/_interopDefaultLegacy(elliptic);
var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch$1);
function scrypt$2(data, salt, n, r, p, dklen) {
return new Promise((resolve, reject) => {
// The scrypt library will crash if it gets a Uint8Array > 64 bytes:
const copy = [];
for (let i = 0; i < data.length; ++i) copy[i] = data[i];
scryptJs__default["default"](copy, salt, n, r, p, dklen, (error, progress, key) => {
if (error != null) return reject(error);
if (key != null) return resolve(Uint8Array.from(key));
});
});
}
/**
* Generates deterministic "random" data for unit-testing.
*/
function makeFakeRandom() {
let seed = 0;
return bytes => {
const out = new Uint8Array(bytes);
for (let i = 0; i < bytes; ++i) {
// Simplest numbers that give a full-period generator with
// a good mix of high & low values within the first few bytes:
seed = 5 * seed + 3 & 0xff;
out[i] = seed;
}
return out;
};
}
const fakeFetch = () => {
return Promise.reject(new Error('Fake network error'));
};
/**
* Creates a simulated io context object.
*/
function makeFakeIo() {
const out = {
// Crypto:
random: makeFakeRandom(),
scrypt: scrypt$2,
// Local io:
disklet: disklet.makeMemoryDisklet(),
// Networking:
fetch: fakeFetch,
fetchCors: fakeFetch
};
return out;
}
/**
* Client-side EdgeAccount methods.
*/
class AccountSync {
getFirstWalletInfo(type) {
return this.allKeys.find(info => info.type === type);
}
getWalletInfo(id) {
return this.allKeys.find(info => info.id === id);
}
listWalletIds() {
return this.allKeys.map(info => info.id);
}
}
yaob.shareData(AccountSync.prototype, 'AccountSync');
/**
* Verifies that a password meets our suggested rules.
*/
function checkPasswordRules(password) {
const tooShort = password.length < 10;
const noNumber = !/[0-9]/.test(password);
const noLowerCase = !/[a-z]/.test(password);
const noUpperCase = !/[A-Z]/.test(password);
// Quick & dirty password strength estimation:
const charset = (/[0-9]/.test(password) ? 10 : 0) + (/[A-Z]/.test(password) ? 26 : 0) + (/[a-z]/.test(password) ? 26 : 0) + (/[^0-9A-Za-z]/.test(password) ? 30 : 0);
const secondsToCrack = Math.pow(charset, password.length) / 1e6;
return {
secondsToCrack,
tooShort,
noNumber,
noLowerCase,
noUpperCase,
passed: password.length >= 16 || !(tooShort || noNumber || noUpperCase || noLowerCase)
};
}
yaob.shareData({
checkPasswordRules
});
/**
* Normalizes a username, and checks for invalid characters.
* TODO: Support a wider character range via Unicode normalization.
*/
function fixUsername(username) {
const out = username.toLowerCase().replace(/[ \f\r\n\t\v]+/g, ' ').replace(/ $/, '').replace(/^ /, '');
for (let i = 0; i < out.length; ++i) {
const c = out.charCodeAt(i);
if (c < 0x20 || c > 0x7e) {
throw new Error('Bad characters in username');
}
}
return out;
}
yaob.shareData({
fixUsername
});
/**
* Synchronously constructs a transaction stream.
* This method creates a secret internal stream,
* which differs slightly from the AsyncIterableIterator protocol
* because of YAOB limitations.
* It then wraps the internal stream object with the correct API.
*/
function streamTransactions(opts) {
let stream;
let streamClosed = false;
const out = {
next: async () => {
if (stream == null) stream = await this.$internalStreamTransactions(opts);
if (!streamClosed) {
const out = await stream.next();
if (!out.done) return out;
yaob.close(stream);
streamClosed = true;
}
return {
done: true,
value: undefined
};
},
/**
* Closes the iterator early if the client doesn't want all the results.
* This is necessary to prevent memory leaks over the bridge.
*/
return: async () => {
if (stream != null && !streamClosed) {
yaob.close(stream);
streamClosed = true;
}
return {
done: true,
value: undefined
};
},
[Symbol.asyncIterator]: () => out
};
return out;
}
yaob.shareData({
streamTransactions
}, 'CurrencyWalletSync');
/**
* A string of hex-encoded binary data.
*/
const asBase16 = cleaners.asCodec(raw => rfc4648.base16.parse(cleaners.asString(raw)), clean => rfc4648.base16.stringify(clean).toLowerCase());
/**
* A string of base32-encoded binary data.
*/
const asBase32 = cleaners.asCodec(raw => rfc4648.base32.parse(cleaners.asString(raw), {
loose: true
}), clean => rfc4648.base32.stringify(clean, {
pad: false
}));
/**
* A string of base64-encoded binary data.
*/
const asBase64 = cleaners.asCodec(raw => rfc4648.base64.parse(cleaners.asString(raw)), clean => rfc4648.base64.stringify(clean));
// ---------------------------------------------------------------------
// public Edge types
// ---------------------------------------------------------------------
const asEdgePendingVoucher = cleaners.asObject({
voucherId: cleaners.asString,
activates: cleaners.asDate,
created: cleaners.asDate,
ip: cleaners.asString,
ipDescription: cleaners.asString,
deviceDescription: cleaners.asOptional(cleaners.asString)
});
// ---------------------------------------------------------------------
// internal Edge types
// ---------------------------------------------------------------------
const asEdgeBox = cleaners.asObject({
encryptionType: cleaners.asNumber,
data_base64: asBase64,
iv_hex: asBase16
});
const asEdgeKeyBox = cleaners.asObject({
created: cleaners.asOptional(cleaners.asDate),
data_base64: asBase64,
encryptionType: cleaners.asNumber,
iv_hex: asBase16
});
const asEdgeSnrp = cleaners.asObject({
salt_hex: asBase16,
n: cleaners.asNumber,
r: cleaners.asNumber,
p: cleaners.asNumber
});
const asEdgeLobbyRequest = cleaners.asObject({
loginRequest: cleaners.asOptional(cleaners.asObject({
appId: cleaners.asString
}).withRest),
publicKey: asBase64,
timeout: cleaners.asOptional(cleaners.asNumber)
}).withRest;
const asEdgeLobbyReply = cleaners.asObject({
publicKey: asBase64,
box: asEdgeBox
});
/**
* An array of base64-encoded hashed recovery answers.
*/
const asRecovery2Auth = cleaners.asArray(asBase64);
// ---------------------------------------------------------------------
// top-level request & response bodies
// ---------------------------------------------------------------------
const asLoginRequestBody = cleaners.asObject({
// The request payload:
data: cleaners.asUnknown,
// Common fields for all login methods:
challengeId: cleaners.asOptional(cleaners.asString),
deviceDescription: cleaners.asOptional(cleaners.asString),
otp: cleaners.asOptional(cleaners.asString),
syncToken: cleaners.asOptional(cleaners.asString),
voucherId: cleaners.asOptional(cleaners.asString),
voucherAuth: cleaners.asOptional(asBase64),
// Secret-key login:
loginId: cleaners.asOptional(asBase64),
loginAuth: cleaners.asOptional(asBase64),
// Password login:
userId: cleaners.asOptional(asBase64),
passwordAuth: cleaners.asOptional(asBase64),
// PIN login:
pin2Id: cleaners.asOptional(asBase64),
pin2Auth: cleaners.asOptional(asBase64),
// Recovery login:
recovery2Id: cleaners.asOptional(asBase64),
recovery2Auth: cleaners.asOptional(asRecovery2Auth),
// Messages:
loginIds: cleaners.asOptional(cleaners.asArray(asBase64)),
// OTP reset:
otpResetAuth: cleaners.asOptional(cleaners.asString),
// Legacy:
did: cleaners.asOptional(cleaners.asString),
l1: cleaners.asOptional(asBase64),
lp1: cleaners.asOptional(asBase64),
lpin1: cleaners.asOptional(asBase64),
lra1: cleaners.asOptional(asBase64),
recoveryAuth: cleaners.asOptional(asBase64) // lra1
});
const asLoginResponseBody = cleaners.asObject({
// The response payload:
results: cleaners.asOptional(cleaners.asUnknown),
// What type of response is this (success or failure)?:
status_code: cleaners.asNumber,
message: cleaners.asString
});
// ---------------------------------------------------------------------
// request payloads
// ---------------------------------------------------------------------
const asChangeOtpPayload = cleaners.asObject({
otpTimeout: cleaners.asOptional(cleaners.asNumber, 7 * 24 * 60 * 60),
// seconds
otpKey: asBase32
});
const asChangePasswordPayload = cleaners.asObject({
passwordAuth: asBase64,
passwordAuthBox: asEdgeBox,
passwordAuthSnrp: asEdgeSnrp,
passwordBox: asEdgeBox,
passwordKeySnrp: asEdgeSnrp
});
const asChangePin2IdPayload = cleaners.asObject({
pin2Id: asBase64
});
const asChangePin2Payload = cleaners.asObject({
pin2Id: cleaners.asOptional(asBase64),
pin2Auth: cleaners.asOptional(asBase64),
pin2Box: cleaners.asOptional(asEdgeBox),
pin2KeyBox: cleaners.asOptional(asEdgeBox),
pin2TextBox: asEdgeBox
});
const asChangeRecovery2IdPayload = cleaners.asObject({
recovery2Id: asBase64
});
const asChangeRecovery2Payload = cleaners.asObject({
recovery2Id: asBase64,
recovery2Auth: asRecovery2Auth,
recovery2Box: asEdgeBox,
recovery2KeyBox: asEdgeBox,
question2Box: asEdgeBox
});
const asChangeSecretPayload = cleaners.asObject({
loginAuthBox: asEdgeBox,
loginAuth: asBase64
});
const asChangeUsernamePayload = cleaners.asObject({
userId: asBase64,
userTextBox: asEdgeBox
});
const asChangeVouchersPayload = cleaners.asObject({
approvedVouchers: cleaners.asOptional(cleaners.asArray(cleaners.asString)),
rejectedVouchers: cleaners.asOptional(cleaners.asArray(cleaners.asString))
});
const asCreateKeysPayload = cleaners.asObject({
keyBoxes: cleaners.asArray(asEdgeBox),
newSyncKeys: cleaners.asOptional(cleaners.asArray(cleaners.asString), () => [])
});
const asCreateLoginPayload = cleaners.asObject({
appId: cleaners.asString,
loginId: asBase64,
parentBox: cleaners.asOptional(asEdgeBox)
});
// ---------------------------------------------------------------------
// response payloads
// ---------------------------------------------------------------------
const asChallengeErrorPayload = cleaners.asObject({
challengeId: cleaners.asString,
challengeUri: cleaners.asString
});
const asCreateChallengePayload = cleaners.asObject({
challengeId: cleaners.asString,
challengeUri: cleaners.asOptional(cleaners.asString)
});
const asLobbyPayload = cleaners.asObject({
request: asEdgeLobbyRequest,
replies: cleaners.asArray(asEdgeLobbyReply)
});
const asTrue = cleaners.asValue(true);
const asLoginPayload = cleaners.asObject({
// Identity:
appId: cleaners.asString,
created: cleaners.asDate,
loginId: asBase64,
syncToken: cleaners.asOptional(cleaners.asString),
// Nested logins:
children: cleaners.asOptional(cleaners.asArray(raw => asLoginPayload(raw))),
parentBox: cleaners.asOptional(asEdgeBox),
// 2-factor login:
otpKey: cleaners.asOptional(cleaners.asEither(asTrue, asBase32)),
otpResetDate: cleaners.asOptional(cleaners.asDate),
otpTimeout: cleaners.asOptional(cleaners.asNumber),
// Password login:
passwordAuthBox: cleaners.asOptional(asEdgeBox),
passwordAuthSnrp: cleaners.asOptional(asEdgeSnrp),
passwordBox: cleaners.asOptional(cleaners.asEither(asTrue, asEdgeBox)),
passwordKeySnrp: cleaners.asOptional(asEdgeSnrp),
// PIN v2 login:
pin2Box: cleaners.asOptional(cleaners.asEither(asTrue, asEdgeBox)),
pin2KeyBox: cleaners.asOptional(asEdgeBox),
pin2TextBox: cleaners.asOptional(asEdgeBox),
// Recovery v2 login:
question2Box: cleaners.asOptional(asEdgeBox),
recovery2Box: cleaners.asOptional(cleaners.asEither(asTrue, asEdgeBox)),
recovery2KeyBox: cleaners.asOptional(asEdgeBox),
// Secret-key login:
loginAuthBox: cleaners.asOptional(asEdgeBox),
// Username:
userId: cleaners.asOptional(asBase64),
userTextBox: cleaners.asOptional(asEdgeBox),
// Voucher login:
pendingVouchers: cleaners.asOptional(cleaners.asArray(asEdgePendingVoucher), () => []),
// Resources:
keyBoxes: cleaners.asOptional(cleaners.asArray(asEdgeKeyBox)),
mnemonicBox: cleaners.asOptional(asEdgeBox),
rootKeyBox: cleaners.asOptional(asEdgeBox),
syncKeyBox: cleaners.asOptional(asEdgeBox)
});
const asMessagesPayload = cleaners.asArray(cleaners.asObject({
loginId: asBase64,
otpResetPending: cleaners.asOptional(cleaners.asBoolean, false),
pendingVouchers: cleaners.asOptional(cleaners.asArray(asEdgePendingVoucher), () => []),
recovery2Corrupt: cleaners.asOptional(cleaners.asBoolean, false)
}));
const asOtpErrorPayload = cleaners.asObject({
login_id: cleaners.asOptional(asBase64),
otp_reset_auth: cleaners.asOptional(cleaners.asString),
otp_timeout_date: cleaners.asOptional(cleaners.asDate),
reason: cleaners.asOptional(cleaners.asValue('ip', 'otp'), 'otp'),
voucher_activates: cleaners.asOptional(cleaners.asDate),
voucher_auth: cleaners.asOptional(asBase64),
voucher_id: cleaners.asOptional(cleaners.asString)
});
const asOtpResetPayload = cleaners.asObject({
otpResetDate: cleaners.asDate
});
const asPasswordErrorPayload = cleaners.asObject({
wait_seconds: cleaners.asOptional(cleaners.asNumber)
});
const asRecovery2InfoPayload = cleaners.asObject({
question2Box: asEdgeBox
});
const asUsernameInfoPayload = cleaners.asObject({
loginId: asBase64,
// Password login:
passwordAuthSnrp: cleaners.asOptional(asEdgeSnrp),
// Recovery v1 login:
questionBox: cleaners.asOptional(asEdgeBox),
questionKeySnrp: cleaners.asOptional(asEdgeSnrp),
recoveryAuthSnrp: cleaners.asOptional(asEdgeSnrp)
});
// ---------------------------------------------------------------------
// uncleaners
// ---------------------------------------------------------------------
// Common types:
const wasEdgeBox = cleaners.uncleaner(asEdgeBox);
const wasEdgeLobbyReply = cleaners.uncleaner(asEdgeLobbyReply);
const wasEdgeLobbyRequest = cleaners.uncleaner(asEdgeLobbyRequest);
// Top-level request / response bodies:
const wasLoginRequestBody = cleaners.uncleaner(asLoginRequestBody);
const wasLoginResponseBody = cleaners.uncleaner(asLoginResponseBody);
// Request payloads:
const wasChangeOtpPayload = cleaners.uncleaner(asChangeOtpPayload);
const wasChangePasswordPayload = cleaners.uncleaner(asChangePasswordPayload);
const wasChangePin2IdPayload = cleaners.uncleaner(asChangePin2IdPayload);
const wasChangePin2Payload = cleaners.uncleaner(asChangePin2Payload);
const wasChangeRecovery2IdPayload = cleaners.uncleaner(asChangeRecovery2IdPayload);
const wasChangeRecovery2Payload = cleaners.uncleaner(asChangeRecovery2Payload);
const wasChangeSecretPayload = cleaners.uncleaner(asChangeSecretPayload);
const wasChangeUsernamePayload = cleaners.uncleaner(asChangeUsernamePayload);
const wasChangeVouchersPayload = cleaners.uncleaner(asChangeVouchersPayload);
const wasCreateKeysPayload = cleaners.uncleaner(asCreateKeysPayload);
const wasCreateLoginPayload = cleaners.uncleaner(asCreateLoginPayload);
// Response payloads:
const wasChallengeErrorPayload = cleaners.uncleaner(asChallengeErrorPayload);
const wasCreateChallengePayload = cleaners.uncleaner(asCreateChallengePayload);
const wasLobbyPayload = cleaners.uncleaner(asLobbyPayload);
const wasLoginPayload = cleaners.uncleaner(asLoginPayload);
const wasMessagesPayload = cleaners.uncleaner(asMessagesPayload);
const wasOtpErrorPayload = cleaners.uncleaner(asOtpErrorPayload);
const wasOtpResetPayload = cleaners.uncleaner(asOtpResetPayload);
const wasPasswordErrorPayload = cleaners.uncleaner(asPasswordErrorPayload);
const wasRecovery2InfoPayload = cleaners.uncleaner(asRecovery2InfoPayload);
const wasUsernameInfoPayload = cleaners.uncleaner(asUsernameInfoPayload);
const asEdgeRepoDump = cleaners.asObject(asEdgeBox);
const asEdgeVoucherDump = cleaners.asObject({
// Identity:
loginId: asBase64,
voucherAuth: asBase64,
voucherId: cleaners.asString,
// Login capability:
created: cleaners.asDate,
activates: cleaners.asDate,
// Automatically becomes approved on this date
status: cleaners.asValue('pending', 'approved', 'rejected'),
// Information about the login:
ip: cleaners.asString,
ipDescription: cleaners.asString,
deviceDescription: cleaners.asOptional(cleaners.asString)
});
const asEdgeLoginDump = cleaners.asObject({
// Identity:
appId: cleaners.asString,
created: cleaners.asOptional(cleaners.asDate, () => new Date()),
loginId: asBase64,
// Nested logins:
children: cleaners.asOptional(cleaners.asArray(raw => asEdgeLoginDump(raw)), () => []),
parentBox: cleaners.asOptional(asEdgeBox),
parentId: () => undefined,
// 2-factor login:
otpKey: cleaners.asOptional(asBase32),
otpResetAuth: cleaners.asOptional(cleaners.asString),
otpResetDate: cleaners.asOptional(cleaners.asDate),
otpTimeout: cleaners.asOptional(cleaners.asNumber),
// Password login:
passwordAuth: cleaners.asOptional(asBase64),
passwordAuthBox: cleaners.asOptional(asEdgeBox),
passwordAuthSnrp: cleaners.asOptional(asEdgeSnrp),
passwordBox: cleaners.asOptional(asEdgeBox),
passwordKeySnrp: cleaners.asOptional(asEdgeSnrp),
// PIN v2 login:
pin2Id: cleaners.asOptional(asBase64),
pin2Auth: cleaners.asOptional(asBase64),
pin2Box: cleaners.asOptional(asEdgeBox),
pin2KeyBox: cleaners.asOptional(asEdgeBox),
pin2TextBox: cleaners.asOptional(asEdgeBox),
// Recovery v2 login:
recovery2Id: cleaners.asOptional(asBase64),
recovery2Auth: cleaners.asOptional(asRecovery2Auth),
question2Box: cleaners.asOptional(asEdgeBox),
recovery2Box: cleaners.asOptional(asEdgeBox),
recovery2KeyBox: cleaners.asOptional(asEdgeBox),
// Secret-key login:
loginAuth: cleaners.asOptional(asBase64),
loginAuthBox: cleaners.asOptional(asEdgeBox),
// Username:
userId: cleaners.asOptional(asBase64),
userTextBox: cleaners.asOptional(asEdgeBox),
// Keys and assorted goodies:
keyBoxes: cleaners.asOptional(cleaners.asArray(asEdgeKeyBox), () => []),
mnemonicBox: cleaners.asOptional(asEdgeBox),
rootKeyBox: cleaners.asOptional(asEdgeBox),
syncKeyBox: cleaners.asOptional(asEdgeBox),
vouchers: cleaners.asOptional(cleaners.asArray(asEdgeVoucherDump), () => []),
// Obsolete:
pinBox: cleaners.asOptional(asEdgeBox),
pinId: cleaners.asOptional(cleaners.asString),
pinKeyBox: cleaners.asOptional(asEdgeBox)
});
const wasEdgeLoginDump = cleaners.uncleaner(asEdgeLoginDump);
const wasEdgeRepoDump = cleaners.uncleaner(asEdgeRepoDump);
const base58Codec = baseX__default["default"]('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
const base58 = {
parse(text) {
return base58Codec.decode(text);
},
stringify(data) {
return base58Codec.encode(data);
}
};
const utf8 = {
parse(text) {
const byteString = encodeURI(text);
const out = new Uint8Array(byteString.length);
// Treat each character as a byte, except for %XX escape sequences:
let di = 0; // Destination index
for (let i = 0; i < byteString.length; ++i) {
const c = byteString.charCodeAt(i);
if (c === 0x25) {
out[di++] = parseInt(byteString.slice(i + 1, i + 3), 16);
i += 2;
} else {
out[di++] = c;
}
}
// Trim any over-allocated space (zero-copy):
return out.subarray(0, di);
},
stringify(data) {
// Create a %XX escape sequence for each input byte:
let byteString = '';
for (let i = 0; i < data.length; ++i) {
const byte = data[i];
byteString += '%' + (byte >> 4).toString(16) + (byte & 0xf).toString(16);
}
return decodeURIComponent(byteString);
}
};
/**
* We only accept *.edge.app or localhost as valid domain names.
*/
function validateServer(server) {
const url = new URL(server);
if (url.protocol === 'http:' || url.protocol === 'ws:') {
if (url.hostname === 'localhost') return;
}
if (url.protocol === 'https:' || url.protocol === 'wss:') {
if (url.hostname === 'localhost') return;
if (/^([A-Za-z0-9_-]+\.)*edge(test)?\.app$/.test(url.hostname)) return;
}
throw new Error(`Only *.edge.app or localhost are valid login domain names, not ${url.hostname}`);
}
/*
* These are errors the core knows about.
*
* The GUI should handle these errors in an "intelligent" way, such as by
* displaying a localized error message or asking the user for more info.
* All these errors have a `name` field, which the GUI can use to select
* the appropriate response.
*
* Other errors are possible, of course, since the Javascript language
* itself can generate exceptions. Those errors won't have a `type` field,
* and the GUI should just show them with a stack trace & generic message,
* since the program has basically crashed at that point.
*/
/**
* Thrown when the login server requires a CAPTCHA.
*
* After showing the WebView with the challengeUri,
* pass the challengeId to the login method
* (such as loginWithPassword) to complete the login.
*
* The challengeUri web page will signal that it is done by navigating
* to a new location that ends with either /success or /failure,
* such as https://login.edge.app/challenge/success
* The login UI can use this as a signal to close the WebView.
*/
function ChallengeError(resultsJson, message = 'Login requires a CAPTCHA') {
if (!(this instanceof ChallengeError)) throw new TypeError("Class constructor ChallengeError cannot be invoked without 'new'");
var _this;
function _super(message) {
_this = new Error(message);
Object.defineProperty(_this, "constructor", {
value: ChallengeError,
configurable: true,
writable: true
});
return _this;
}
_super(message);
_this.name = 'ChallengeError';
_this.challengeId = resultsJson.challengeId;
_this.challengeUri = resultsJson.challengeUri;
return _this;
}
/**
* Trying to spend an uneconomically small amount of money.
*/
function DustSpendError(message = 'Please send a larger amount') {
if (!(this instanceof DustSpendError)) throw new TypeError("Class constructor DustSpendError cannot be invoked without 'new'");
var _this2;
function _super2(message) {
_this2 = new Error(message);
Object.defineProperty(_this2, "constructor", {
value: DustSpendError,
configurable: true,
writable: true
});
return _this2;
}
_super2(message);
_this2.name = 'DustSpendError';
return _this2;
}
/**
* Trying to spend more money than the wallet contains.
*/
function InsufficientFundsError(opts) {
if (!(this instanceof InsufficientFundsError)) throw new TypeError("Class constructor InsufficientFundsError cannot be invoked without 'new'");
var _this3;
function _super3(message) {
_this3 = new Error(message);
Object.defineProperty(_this3, "constructor", {
value: InsufficientFundsError,
configurable: true,
writable: true
});
return _this3;
}
const {
tokenId = null,
networkFee
} = opts ?? {};
_super3(`Insufficient ${tokenId ?? 'funds'}`);
_this3.tokenId = tokenId;
_this3.networkFee = networkFee;
_this3.name = 'InsufficientFundsError';
return _this3;
}
/**
* Could not reach the server at all.
*/
function NetworkError(message = 'Cannot reach the network') {
if (!(this instanceof NetworkError)) throw new TypeError("Class constructor NetworkError cannot be invoked without 'new'");
var _this4;
function _super4(message) {
_this4 = new Error(message);
Object.defineProperty(_this4, "constructor", {
value: NetworkError,
configurable: true,
writable: true
});
return _this4;
}
_super4(message);
_this4.name = 'NetworkError';
return _this4;
}
/**
* Attempting to create a MakeSpend without specifying an amount of currency to send
*/
function NoAmountSpecifiedError(message = 'Unable to create zero-amount transaction.') {
if (!(this instanceof NoAmountSpecifiedError)) throw new TypeError("Class constructor NoAmountSpecifiedError cannot be invoked without 'new'");
var _this5;
function _super5(message) {
_this5 = new Error(message);
Object.defineProperty(_this5, "constructor", {
value: NoAmountSpecifiedError,
configurable: true,
writable: true
});
return _this5;
}
_super5(message);
_this5.name = 'NoAmountSpecifiedError';
return _this5;
}
/**
* The endpoint on the server is obsolete, and the app needs to be upgraded.
*/
function ObsoleteApiError(message = 'The application is too old. Please upgrade.') {
if (!(this instanceof ObsoleteApiError)) throw new TypeError("Class constructor ObsoleteApiError cannot be invoked without 'new'");
var _this6;
function _super6(message) {
_this6 = new Error(message);
Object.defineProperty(_this6, "constructor", {
value: ObsoleteApiError,
configurable: true,
writable: true
});
return _this6;
}
_super6(message);
_this6.name = 'ObsoleteApiError';
return _this6;
}
/**
* The OTP token was missing / incorrect.
*
* The error object should include a `resetToken` member,
* which can be used to reset OTP protection on the account.
*
* The error object may include a `resetDate` member,
* which indicates that an OTP reset is already pending,
* and when it will complete.
*/
function OtpError(resultsJson, message = 'Invalid OTP token') {
if (!(this instanceof OtpError)) throw new TypeError("Class constructor OtpError cannot be invoked without 'new'");
var _this7;
function _super7(message) {
_this7 = new Error(message);
Object.defineProperty(_this7, "constructor", {
value: OtpError,
configurable: true,
writable: true
});
return _this7;
}
_super7(message);
_this7.name = 'OtpError';
_this7.reason = 'otp';
const clean = cleaners.asMaybe(asOtpErrorPayload)(resultsJson);
if (clean == null) return;
if (clean.login_id != null) {
_this7.loginId = rfc4648.base64.stringify(clean.login_id);
}
_this7.resetToken = clean.otp_reset_auth;
_this7.reason = clean.reason;
_this7.resetDate = clean.otp_timeout_date;
_this7.voucherActivates = clean.voucher_activates;
if (clean.voucher_auth != null) {
_this7.voucherAuth = rfc4648.base64.stringify(clean.voucher_auth);
}
_this7.voucherId = clean.voucher_id;
return _this7;
}
/**
* The provided authentication is incorrect.
*
* Reasons could include:
* - Password login: wrong password
* - PIN login: wrong PIN
* - Recovery login: wrong answers
*
* The error object may include a `wait` member,
* which is the number of seconds the user must wait before trying again.
*/
function PasswordError(resultsJson, message = 'Invalid password') {
if (!(this instanceof PasswordError)) throw new TypeError("Class constructor PasswordError cannot be invoked without 'new'");
var _this8;
function _super8(message) {
_this8 = new Error(message);
Object.defineProperty(_this8, "constructor", {
value: PasswordError,
configurable: true,
writable: true
});
return _this8;
}
_super8(message);
_this8.name = 'PasswordError';
const clean = cleaners.asMaybe(asPasswordErrorPayload)(resultsJson);
if (clean == null) return;
_this8.wait = clean.wait_seconds;
return _this8;
}
/**
* PIN login is not enabled for this account on this device.
*/
function PinDisabledError(message) {
if (!(this instanceof PinDisabledError)) throw new TypeError("Class constructor PinDisabledError cannot be invoked without 'new'");
var _this9;
function _super9(message) {
_this9 = new Error(message);
Object.defineProperty(_this9, "constructor", {
value: PinDisabledError,
configurable: true,
writable: true
});
return _this9;
}
_super9(message);
_this9.name = 'PinDisabledError';
return _this9;
}
/**
* Trying to spend funds that are not yet confirmed.
*/
function PendingFundsError(message = 'Not enough confirmed funds') {
if (!(this instanceof PendingFundsError)) throw new TypeError("Class constructor PendingFundsError cannot be invoked without 'new'");
var _this10;
function _super10(message) {
_this10 = new Error(message);
Object.defineProperty(_this10, "constructor", {
value: PendingFundsError,
configurable: true,
writable: true
});
return _this10;
}
_super10(message);
_this10.name = 'PendingFundsError';
return _this10;
}
/**
* Attempting to shape shift between two wallets of same currency.
*/
function SameCurrencyError(message = 'Wallets can not be the same currency') {
if (!(this instanceof SameCurrencyError)) throw new TypeError("Class constructor SameCurrencyError cannot be invoked without 'new'");
var _this11;
function _super11(message) {
_this11 = new Error(message);
Object.defineProperty(_this11, "constructor", {
value: SameCurrencyError,
configurable: true,
writable: true
});
return _this11;
}
_super11(message);
_this11.name = 'SameCurrencyError';
return _this11;
}
/**
* Trying to spend to an address of the source wallet
*/
function SpendToSelfError(message = 'Spending to self') {
if (!(this instanceof SpendToSelfError)) throw new TypeError("Class constructor SpendToSelfError cannot be invoked without 'new'");
var _this12;
function _super12(message) {
_this12 = new Error(message);
Object.defineProperty(_this12, "constructor", {
value: SpendToSelfError,
configurable: true,
writable: true
});
return _this12;
}
_super12(message);
_this12.name = 'SpendToSelfError';
return _this12;
}
/**
* Trying to swap an amount that is either too low or too high.
* @param nativeMax the maximum supported amount, in the currency specified
* by the direction (defaults to "from" currency)
*/
function SwapAboveLimitError(swapInfo, nativeMax, direction = 'from') {
if (!(this instanceof SwapAboveLimitError)) throw new TypeError("Class constructor SwapAboveLimitError cannot be invoked without 'new'");
var _this13;
function _super13(message) {
_this13 = new Error(message);
Object.defineProperty(_this13, "constructor", {
value: SwapAboveLimitError,
configurable: true,
writable: true
});
return _this13;
}
_super13('Amount is too high');
_this13.name = 'SwapAboveLimitError';
_this13.pluginId = swapInfo.pluginId;
_this13.swapPluginId = swapInfo.pluginId;
_this13.nativeMax = nativeMax ?? '';
_this13.direction = direction;
return _this13;
}
/**
* Trying to swap an amount that is either too low or too high.
* @param nativeMin the minimum supported amount, in the currency specified
* by the direction (defaults to "from" currency)
*/
function SwapBelowLimitError(swapInfo, nativeMin, direction = 'from') {
if (!(this instanceof SwapBelowLimitError)) throw new TypeError("Class constructor SwapBelowLimitError cannot be invoked without 'new'");
var _this14;
function _super14(message) {
_this14 = new Error(message);
Object.defineProperty(_this14, "constructor", {
value: SwapBelowLimitError,
configurable: true,
writable: true
});
return _this14;
}
_super14('Amount is too low');
_this14.name = 'SwapBelowLimitError';
_this14.pluginId = swapInfo.pluginId;
_this14.swapPluginId = swapInfo.pluginId;
_this14.nativeMin = nativeMin ?? '';
_this14.direction = direction;
return _this14;
}
/**
* The swap plugin does not support this currency pair.
*/
function SwapCurrencyError(swapInfo, request) {
if (!(this instanceof SwapCurrencyError)) throw new TypeError("Class constructor SwapCurrencyError cannot be invoked without 'new'");
var _this15;
function _super15(message) {
_this15 = new Error(message);
Object.defineProperty(_this15, "constructor", {
value: SwapCurrencyError,
configurable: true,
writable: true
});
return _this15;
}
const {
fromWallet,
toWallet,
fromTokenId,
toTokenId
} = request;
const fromPluginId = fromWallet.currencyConfig.currencyInfo.pluginId;
const toPluginId = toWallet.currencyConfig.currencyInfo.pluginId;
const fromString = `${fromPluginId}:${String(fromTokenId)}`;
const toString = `${toPluginId}:${String(toTokenId)}`;
_super15(`${swapInfo.displayName} does not support ${fromString} to ${toString}`);
_this15.name = 'SwapCurrencyError';
_this15.pluginId = swapInfo.pluginId;
_this15.fromTokenId = fromTokenId ?? null;
_this15.toTokenId = toTokenId ?? null;
return _this15;
}
/**
* The user is not allowed to swap these coins for some reason
* (no KYC, restricted IP address, etc...).
* @param reason A string giving the reason for the denial.
* - 'geoRestriction': The IP address is in a restricted region
* - 'noVerification': The user needs to provide KYC credentials
* - 'needsActivation': The user needs to log into the service.
*/
function SwapPermissionError(swapInfo, reason) {
if (!(this instanceof SwapPermissionError)) throw new TypeError("Class constructor SwapPermissionError cannot be invoked without 'new'");
var _this16;
function _super16(message) {
_this16 = new Error(message);
Object.defineProperty(_this16, "constructor", {
value: SwapPermissionError,
configurable: true,
writable: true
});
return _this16;
}
if (reason != null) _super16(reason);else _super16('You are not allowed to make this trade');
_this16.name = 'SwapPermissionError';
_this16.pluginId = swapInfo.pluginId;
_this16.reason = reason;
return _this16;
}
// Address requirements for certain swap flows (extend as needed):
function SwapAddressError(swapInfo, opts) {
if (!(this instanceof SwapAddressError)) throw new TypeError("Class constructor SwapAddressError cannot be invoked without 'new'");
var _this17;
function _super17(message) {
_this17 = new Error(message);
Object.defineProperty(_this17, "constructor", {
value: SwapAddressError,
configurable: true,
writable: true
});
return _this17;
}
const {
reason
} = opts;
switch (reason) {
case 'mustMatch':
_super17('This swap requires from and to wallets to have the same address');
break;
case 'mustBeActivated':
_super17('The destination wallet must be activated to receive this swap.');
break;
default:
_super17('Invalid swap address');
}
_this17.name = 'SwapAddressError';
_this17.swapPluginId = swapInfo.pluginId;
_this17.reason = reason;
return _this17;
}
/**
* Cannot find a login with that id.
*
* Reasons could include:
* - Password login: wrong username
* - PIN login: wrong PIN key
* - Recovery login: wrong username, or wrong recovery key
*/
function UsernameError(message = 'Invalid username') {
if (!(this instanceof UsernameError)) throw new TypeError("Class constructor UsernameError cannot be invoked without 'new'");
var _this18;
function _super18(message) {
_this18 = new Error(message);
Object.defineProperty(_this18, "constructor", {
value: UsernameError,
configurable: true,
writable: true
});
return _this18;
}
_super18(message);
_this18.name = 'UsernameError';
return _this18;
}
function asMaybeError(name) {
return function asError(raw) {
if (raw instanceof Error && raw.name === name) {
const typeHack = raw;
return typeHack;
}
};
}
const asMaybeChallengeError = asMaybeError('ChallengeError');
const asMaybeDustSpendError = asMaybeError('DustSpendError');
const asMaybeInsufficientFundsError = asMaybeError('InsufficientFundsError');
const asMaybeNetworkError = asMaybeError('NetworkError');
const asMaybeNoAmountSpecifiedError = asMaybeError('NoAmountSpecifiedError');
const asMaybeObsoleteApiError = asMaybeError('ObsoleteApiError');
const asMaybeOtpError = asMaybeError('OtpError');
const asMaybePasswordError = asMaybeError('PasswordError');
const asMaybePinDisabledError = asMaybeError('PinDisabledError');
const asMaybePendingFundsError = asMaybeError('PendingFundsError');
const asMaybeSameCurrencyError = asMaybeError('SameCurrencyError');
const asMaybeSpendToSelfError = asMaybeError('SpendToSelfError');
const asMaybeSwapAboveLimitError = asMaybeError('SwapAboveLimitError');
const asMaybeSwapBelowLimitError = asMaybeError('SwapBelowLimitError');
const asMaybeSwapCurrencyError = asMaybeError('SwapCurrencyError');
const asMaybeSwapPermissionError = asMaybeError('SwapPermissionError');
const asMaybeSwapAddressError = asMaybeError('SwapAddressError');
const asMaybeUsernameError = asMaybeError('UsernameError');
function hmacSha1(data, key) {
// @ts-expect-error
const hmac = hashjs__default["default"].hmac(hashjs__default["default"].sha1, key);
return Uint8Array.from(hmac.update(data).digest());
}
function hmacSha256(data, key) {
// @ts-expect-error
const hmac = hashjs__default["default"].hmac(hashjs__default["default"].sha256, key);
return Uint8Array.from(hmac.update(data).digest());
}
function sha256(data) {
const hash = hashjs__default["default"].sha256();
return Uint8Array.from(hash.update(data).digest());
}
/**
* Compares two byte arrays without data-dependent branches.
* Returns true if they match.
*/
function verifyData(a, b) {
const length = a.length;
if (length !== b.length) return false;
let out = 0;
for (let i = 0; i < length; ++i) out |= a[i] ^ b[i];
return out === 0;
}
const AesCbc = aesjs__default["default"].ModeOfOperation.cbc;
/**
* Some of our data contains terminating null bytes due to an old bug,
* so this function handles text decryption as a special case.
*/
function decryptText(box, key) {
const data = decrypt(box, key);
if (data[data.length - 1] === 0) {
return utf8.stringify(data.subarray(0, -1));
}
return utf8.stringify(data);
}
/**
* @param box an Airbitz JSON encryption box
* @param key a key, as an ArrayBuffer
*/
function decrypt(box, key) {
// Check JSON:
if (box.encryptionType !== 0) {
throw new Error('Unknown encryption type');
}
const iv = box.iv_hex;
const ciphertext = box.data_base64;
// Decrypt:
const cipher = new AesCbc(key, iv);
const raw = cipher.decrypt(ciphertext);
// Calculate data locations:
const headerStart = 1;
const headerSize = raw[0];
const dataStart = headerStart + headerSize + 4;
const dataSize = raw[dataStart - 4] << 24 | raw[dataStart - 3] << 16 | raw[dataStart - 2] << 8 | raw[dataStart - 1];
const footerStart = dataStart + dataSize + 1;
const footerSize = raw[footerStart - 1];
const hashStart = footerStart + footerSize;
const paddingStart = hashStart + 32;
// Verify SHA-256 checksum:
const hash = sha256(raw.subarray(0, hashStart));
if (!verifyData(hash, raw.subarray(hashStart, paddingStart))) {
throw new Error('Invalid checksum');
}
// Verify pkcs7 padding:
const padding = pkcs7(paddingStart);
if (!verifyData(padding, raw.subarray(paddingStart))) {
throw new Error('Invalid PKCS7 padding');
}
// Return the payload:
return raw.subarray(dataStart, dataStart + dataSize);
}
/**
* @param payload an ArrayBuffer of data
* @param key a key, as an ArrayBuffer
*/
function encrypt(io, data, key) {
// Calculate data locations:
const headerStart = 1;
const headerSize = io.random(1)[0] & 0x1f;
const dataStart = headerStart + headerSize + 4;
const dataSize = data.length;
const footerStart = dataStart + dataSize + 1;
const footerSize = io.random(1)[0] & 0x1f;
const hashStart = footerStart + footerSize;
const paddingStart = hashStart + 32;
// Initialize the buffer with padding:
const padding = pkcs7(paddingStart);
const raw = new Uint8Array(paddingStart + padding.length);
raw.set(padding, paddingStart);
// Add header:
raw[0] = headerSize;
raw.set(io.random(headerSize), headerStart);
// Add payload:
raw[dataStart - 4] = dataSize >> 24 & 0xff;
raw[dataStart - 3] = dataSize >> 16 & 0xff;
raw[dataStart - 2] = dataSize >> 8 & 0xff;
raw[dataStart - 1] = dataSize & 0xff;
raw.set(data, dataStart);
// Add footer:
raw[footerStart - 1] = footerSize;
raw.set(io.random(footerSize), footerStart);
// Add SHA-256 checksum:
raw.set(sha256(raw.subarray(0, hashStart)), hashStart);
// Encrypt to JSON:
const iv = io.random(16);
const cipher = new AesCbc(key, iv);
const ciphertext = cipher.encrypt(raw);
return {
encryptionType: 0,
iv_hex: iv,
data_base64: ciphertext
};
}
/**
* Generates the pkcs7 padding data that should be appended to
* data of a particular length.
*/
function pkcs7(length) {
const out = new Uint8Array(16 - (length & 0xf));
for (let i = 0; i < out.length; ++i) out[i] = out.length;
return out;
}
function numberToBe64(number) {
const high = Math.floor(number / 0x100000000);
return new Uint8Array([high >> 24 & 0xff, high >> 16 & 0xff, high >> 8 & 0xff, high & 0xff, number >> 24 & 0xff, number >> 16 & 0xff, number >> 8 & 0xff, number & 0xff]);
}
/**
* Implements the rfc4226 HOTP specification.
* @param {*} secret The secret value, K, from rfc4226
* @param {*} counter The counter, C, from rfc4226
* @param {*} digits The number of digits to generate
*/
function hotp(secret, counter, digits) {
const hmac = hmacSha1(numberToBe64(counter), secret);
const offset = hmac[19] & 0xf;
const p = (hmac[offset] & 0x7f) << 24 | hmac[offset + 1] << 16 | hmac[offset + 2] << 8 | hmac[offset + 3];
const text = p.toString();
const padding = Array(digits).join('0');
return (padding + text).slice(-digits);
}
/**
* Generates an HOTP code based on the current time.
*/
function totp(secret, now = Date.now() / 1000) {
return hotp(secret, now / 30, 6);
}
/**
* Validates a TOTP code based on the current time,
* within an adjustable range.
*/
function checkTotp(secret, otp, opts = {}) {
const {
now = Date.now() / 1000,
spread = 1
} = opts;
const index = now / 30;
// Try the middle:
if (otp === hotp(secret, index, 6)) return true;
// Spiral outwards:
for (let i = 1; i <= spread; ++i) {
if (otp === hotp(secret, index - i, 6)) return true;
if (otp === hotp(secret, index + i, 6)) return true;
}
return false;
}
/**
* Safely concatenate a bunch of arrays, which may or may not exist.
* Purrs quietly when pet.
*/
function softCat(...lists) {
const out = [];
return out.concat(...lists.filter(list => list != null));
}
/**
* Like `Object.assign`, but makes the properties non-enumerable.
*/
function addHiddenProperties(object, properties) {
for (const name of Object.keys(properties)) {
Object.defineProperty(object, name, {
writable: true,
configurable: true,
// @ts-expect-error
value: properties[name]
});
}
return object;
}
/**
* Waits for the first successful promise.
* If no promise succeeds, returns the last failure.
*/
/**
* If the promise doesn't resolve in the given time,
* reject it with the provided error, or a generic error if none is provided.
*/
function timeout(promise, ms, error = new Error(`Timeout of ${ms}ms exceeded`)) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(error), ms);
promise.then(ok => {
clearTimeout(timer);
resolve(ok);
}, error => {
clearTimeout(timer);
reject(error);
});
});
}
/**
* Waits for a collection of promises.
* Returns all the promises that manage to resolve within the timeout.
* If no promises mange to resolve within the timeout,
* returns the first promise that resolves.
* If all promises reject, rejects an array of errors.
*/
function fuzzyTimeout(promises, timeoutMs) {
return new Promise((resolve, reject) => {
let done = false;
const results = [];
const errors = [];
// Set up the timer:
let timer = setTimeout(() => {
timer = undefined;
if (results.length > 0) {
done = true;
resolve({
results,
errors
});
}
}, timeoutMs);
function checkEnd() {
const allDone = results.length + errors.length === promises.length;
if (allDone && timer != null) {
clearTimeout(timer);
}
if (allDone || timer == null) {
done = true;
if (results.length > 0) resolve({
results,
errors
});else reject(errors);
}
}
checkEnd(); // Handle empty lists
// Attach to the promises:
for (const promise of promises) {
promise.then(result => {
if (done) return;
results.push(result);
checkEnd();
}, failure => {
if (done) return;
errors.push(failure);
checkEnd();
});
}
});
}
function parseReply(json) {
const clean = asLoginResponseBody(json);
switch (clean.status_code) {
case 0:
// Success
return clean.results;
case 2:
// Account exists
throw new UsernameError('Account already exists on server');
case 3:
// Account does not exist
throw new UsernameError('Account does not exist on server');
case 4: // Invalid password
case 5:
// Invalid answers
throw new PasswordError(clean.results);
case 6:
// Invalid API key
throw new Error('Invalid API key');
case 8:
// Invalid OTP
throw new OtpError(clean.results);
case 1000:
// Endpoint obsolete
throw new ObsoleteApiError();
case 1: // Error
case 7: // Pin expired
case 9: // Invalid voucher
case 10: // Conflicting change
case 11: // Rate limiting
default:
{
const results = cleaners.asMaybe(asChallengeErrorPayload)(clean.results);
if (results != null) {
throw new ChallengeError(results);
}
throw new Error(`Server error: ${clean.message}`);
}
}
}
/**
* Picks a random login server and makes a request.
*
* We don't use the normal async waterfall,
* since we never want these requests to happen in parallel.
* The first server needs to fully fail before we can try again,
* or we risk having document update conflicts, duplicated keys,
* redundant login notifications, or other corruption.
*/
async function loginFetch(ai, method, path, body) {
const {
loginServers
} = ai.props.state.login;
// This will be out of range, but the modulo brings it back:
const startIndex = Math.floor(Math.random() * 255);
let response;
let lastError = new Error('No login servers available');
for (let i = 0; i < loginServers.length; ++i) {
try {
const index = (startIndex + i) % loginServers.length;
response = await loginFetchInner(ai, loginServers[index], method, path, body);
break;
} catch (error) {
lastError = error;
}
}
if (response == null) throw lastError;
const {
status
} = response;
const json = await response.json().catch(() => {
throw new Error(`Invalid reply JSON, HTTP status ${status}`);
});
return parseReply(json);
}
function loginFetchInner(ai, serverUri, method, path, body) {
const {
state,
io,
log
} = ai.props;
const {
apiKey,
apiSecret
} = state.login;
const bodyText = method === 'GET' || body == null ? undefined : JSON.stringify(wasLoginRequestBody(body));
// API key:
let authorization = `Token ${apiKey}`;
if (apiSecret != null) {
const requestText = `${method}\n/api${path}\n${bodyText ?? ''}`;
const hash = hmacSha256(utf8.parse(requestText), apiSecret);
authorization = `HMAC ${apiKey} ${rfc4648.base64.stringify(hash)}`;
}
const opts = {
body: bodyText,
method,
headers: {
'content-type': 'application/json',
accept: 'application/json',
authorization
},
corsBypass: 'never'
};
const start = Date.now();
const fullUri = `${serverUri}/api${path}`;
return timeout(io.fetch(fullUri, opts), 30000).then(response => {
// Log the results:
const time = Date.now() - start;
log(`${method} ${fullUri} returned ${response.status} in ${time}ms`);
if (response.status === 409) {
log.crash(`Login API conflict error`, {
path
});
}
return response;
}, networkError => {
const time = Date.now() - start;
log.error(`${method} ${fullUri} failed in ${time}ms, ${String(networkError)}`);
throw new NetworkError(`Could not reach the auth server: ${path}`);
});
}
function makeSecretKit(ai, login) {
const {
io
} = ai.props;
const {
loginId,
loginKey
} = login;
const loginAuth = io.random(32);
const loginAuthBox = encrypt(io, loginAuth, loginKey);
return {
loginId,
server: wasChangeSecretPayload({
loginAuth,
loginAuthBox
}),
serverPath: '/v2/login/secret',
stash: {
loginAuthBox
}
};
}
/**
* Computes an SNRP value.
*/
function makeSnrp(ai, targetMs = 2000) {
return ai.props.output.scrypt.makeSnrp(targetMs);
}
/**
* Performs an