PHP WebShell
Текущая директория: /usr/lib/node_modules/bitgo/node_modules/@near-js/accounts/lib/commonjs
Просмотр файла: account_2fa.cjs
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Account2FA = void 0;
const crypto_1 = require("@near-js/crypto");
const types_1 = require("@near-js/types");
const transactions_1 = require("@near-js/transactions");
const utils_1 = require("@near-js/utils");
const account_multisig_1 = require("./account_multisig.cjs");
const constants_1 = require("./constants.cjs");
const types_2 = require("./types.cjs");
const { addKey, deleteKey, deployContract, fullAccessKey, functionCall, functionCallAccessKey } = transactions_1.actionCreators;
class Account2FA extends account_multisig_1.AccountMultisig {
/********************************
Account2FA has options object where you can provide callbacks for:
- sendCode: how to send the 2FA code in case you don't use NEAR Contract Helper
- getCode: how to get code from user (use this to provide custom UI/UX for prompt of 2FA code)
- onResult: the tx result after it's been confirmed by NEAR Contract Helper
********************************/
sendCode;
getCode;
verifyCode;
onConfirmResult;
helperUrl = 'https://helper.testnet.near.org';
constructor(connection, accountId, options) {
super(connection, accountId, options);
this.helperUrl = options.helperUrl || this.helperUrl;
this.storage = options.storage;
this.sendCode = options.sendCode || this.sendCodeDefault;
this.getCode = options.getCode || this.getCodeDefault;
this.verifyCode = options.verifyCode || this.verifyCodeDefault;
this.onConfirmResult = options.onConfirmResult;
}
/**
* Sign a transaction to preform a list of actions and broadcast it using the RPC API.
* @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider.sendTransaction | JsonRpcProvider.sendTransaction}
*
* @param options Options for the transaction.
* @param options.receiverId The NEAR account ID of the transaction receiver.
* @param options.actions The list of actions to be included in the transaction.
* @returns {Promise<FinalExecutionOutcome>} A promise that resolves to the final execution outcome of the transaction.
*/
async signAndSendTransaction({ receiverId, actions }) {
await super.signAndSendTransaction({ receiverId, actions });
// TODO: Should following override onRequestResult in superclass instead of doing custom signAndSendTransaction?
await this.sendCode();
const result = await this.promptAndVerify();
if (this.onConfirmResult) {
await this.onConfirmResult(result);
}
return result;
}
// default helpers for CH deployments of multisig
/**
* Deploy a multisig contract with 2FA and handle the deployment process.
* @param contractBytes - The bytecode of the multisig contract.
* @returns {Promise<FinalExecutionOutcome>} A promise that resolves to the final execution outcome of the deployment.
*/
async deployMultisig(contractBytes) {
const { accountId } = this;
const seedOrLedgerKey = (await this.getRecoveryMethods()).data
// @ts-ignore
.filter(({ kind, publicKey }) => (kind === 'phrase' || kind === 'ledger') && publicKey !== null)
.map((rm) => rm.publicKey);
const fak2lak = (await this.getAccessKeys())
.filter(({ public_key, access_key: { permission } }) => permission === 'FullAccess' && !seedOrLedgerKey.includes(public_key))
.map((ak) => ak.public_key)
.map(toPK);
// @ts-ignore
const confirmOnlyKey = toPK((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey);
const newArgs = Buffer.from(JSON.stringify({ 'num_confirmations': 2 }));
const actions = [
...fak2lak.map((pk) => deleteKey(pk)),
...fak2lak.map((pk) => addKey(pk, functionCallAccessKey(accountId, constants_1.MULTISIG_CHANGE_METHODS, null))),
addKey(confirmOnlyKey, functionCallAccessKey(accountId, constants_1.MULTISIG_CONFIRM_METHODS, null)),
deployContract(contractBytes),
];
const newFunctionCallActionBatch = actions.concat(functionCall('new', newArgs, constants_1.MULTISIG_GAS, constants_1.MULTISIG_DEPOSIT));
utils_1.Logger.log('deploying multisig contract for', accountId);
const { stateStatus: multisigStateStatus } = await this.checkMultisigCodeAndStateStatus(contractBytes);
switch (multisigStateStatus) {
case types_2.MultisigStateStatus.STATE_NOT_INITIALIZED:
return await super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch);
case types_2.MultisigStateStatus.VALID_STATE:
return await super.signAndSendTransactionWithAccount(accountId, actions);
case types_2.MultisigStateStatus.INVALID_STATE:
throw new types_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState');
default:
throw new types_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown');
}
}
/**
* Disable 2FA with the option to clean up contract state.
* @param options Options for disabling 2FA.
* @param options.contractBytes The bytecode of the contract to deploy.
* @param options.cleanupContractBytes The bytecode of the cleanup contract (optional).
* @returns {Promise<FinalExecutionOutcome>} A promise that resolves to the final execution outcome of the operation.
*/
async disableWithFAK({ contractBytes, cleanupContractBytes }) {
let cleanupActions = [];
if (cleanupContractBytes) {
await this.deleteAllRequests().catch(e => e);
cleanupActions = await this.get2faDisableCleanupActions(cleanupContractBytes);
}
const keyConversionActions = await this.get2faDisableKeyConversionActions();
const actions = [
...cleanupActions,
...keyConversionActions,
deployContract(contractBytes)
];
const accessKeyInfo = await this.findAccessKey(this.accountId, actions);
if (accessKeyInfo && accessKeyInfo.accessKey && accessKeyInfo.accessKey.permission !== 'FullAccess') {
throw new types_1.TypedError('No full access key found in keystore. Unable to bypass multisig', 'NoFAKFound');
}
return this.signAndSendTransactionWithAccount(this.accountId, actions);
}
/**
* Retrieves cleanup actions for disabling 2FA.
* @param cleanupContractBytes - The bytecode of the cleanup contract.
* @returns {Promise<Action[]>} - A promise that resolves to an array of cleanup actions.
*/
async get2faDisableCleanupActions(cleanupContractBytes) {
const currentAccountState = await this.viewState('').catch(error => {
const cause = error.cause && error.cause.name;
if (cause == 'NO_CONTRACT_CODE') {
return [];
}
throw cause == 'TOO_LARGE_CONTRACT_STATE'
? new types_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState')
: error;
});
const currentAccountStateKeys = currentAccountState.map(({ key }) => key.toString('base64'));
return currentAccountState.length ? [
deployContract(cleanupContractBytes),
functionCall('clean', { keys: currentAccountStateKeys }, constants_1.MULTISIG_GAS, 0n)
] : [];
}
/**
* Retrieves key conversion actions for disabling 2FA.
* @returns {Promise<Action[]>} - A promise that resolves to an array of key conversion actions.
*/
async get2faDisableKeyConversionActions() {
const { accountId } = this;
const accessKeys = await this.getAccessKeys();
const lak2fak = accessKeys
.filter(({ access_key }) => access_key.permission !== 'FullAccess')
.filter(({ access_key }) => {
const perm = access_key.permission.FunctionCall;
return perm.receiver_id === accountId &&
perm.method_names.length === 4 &&
perm.method_names.includes('add_request_and_confirm');
});
// @ts-ignore
const confirmOnlyKey = crypto_1.PublicKey.from((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey);
return [
deleteKey(confirmOnlyKey),
...lak2fak.map(({ public_key }) => deleteKey(crypto_1.PublicKey.from(public_key))),
...lak2fak.map(({ public_key }) => addKey(crypto_1.PublicKey.from(public_key), fullAccessKey()))
];
}
/**
* This method converts LAKs back to FAKs, clears state and deploys an 'empty' contract (contractBytes param)
* @param [contractBytes]{@link https://github.com/near/near-wallet/blob/master/packages/frontend/src/wasm/main.wasm?raw=true}
* @param [cleanupContractBytes]{@link https://github.com/near/core-contracts/blob/master/state-manipulation/res/state_cleanup.wasm?raw=true}
* @returns {Promise<FinalExecutionOutcome>} A promise that resolves to the final execution outcome of the operation.
*/
async disable(contractBytes, cleanupContractBytes) {
const { stateStatus } = await this.checkMultisigCodeAndStateStatus();
if (stateStatus !== types_2.MultisigStateStatus.VALID_STATE && stateStatus !== types_2.MultisigStateStatus.STATE_NOT_INITIALIZED) {
throw new types_1.TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown');
}
let deleteAllRequestsError;
await this.deleteAllRequests().catch(e => deleteAllRequestsError = e);
const cleanupActions = await this.get2faDisableCleanupActions(cleanupContractBytes).catch(e => {
if (e.type === 'ContractHasExistingState') {
throw deleteAllRequestsError || e;
}
throw e;
});
const actions = [
...cleanupActions,
...(await this.get2faDisableKeyConversionActions()),
deployContract(contractBytes),
];
utils_1.Logger.log('disabling 2fa for', this.accountId);
return await this.signAndSendTransaction({
receiverId: this.accountId,
actions
});
}
/**
* Default implementation for sending the 2FA code.
* @returns {Promise<string>} - A promise that resolves to the request ID.
*/
async sendCodeDefault() {
const { accountId } = this;
const { requestId } = this.getRequest();
const method = await this.get2faMethod();
await this.postSignedJson('/2fa/send', {
accountId,
method,
requestId,
});
return requestId;
}
async getCodeDefault() {
throw new Error('There is no getCode callback provided. Please provide your own in AccountMultisig constructor options. It has a parameter method where method.kind is "email" or "phone".');
}
/**
* Prompts the user to enter and verify the 2FA code.
* @returns {Promise<any>} - A promise that resolves to the verification result.
*/
async promptAndVerify() {
const method = await this.get2faMethod();
const securityCode = await this.getCode(method);
try {
const result = await this.verifyCode(securityCode);
// TODO: Parse error from result for real (like in normal account.signAndSendTransaction)
return result;
}
catch (e) {
utils_1.Logger.warn('Error validating security code:', e);
if (e.toString().includes('invalid 2fa code provided') || e.toString().includes('2fa code not valid')) {
return await this.promptAndVerify();
}
throw e;
}
}
/**
* Verify the 2FA code using the default method.
* @param securityCode - The security code to verify.
* @returns {Promise<any>} A promise that resolves to the verification result.
*/
async verifyCodeDefault(securityCode) {
const { accountId } = this;
const request = this.getRequest();
if (!request) {
throw new Error('no request pending');
}
const { requestId } = request;
return await this.postSignedJson('/2fa/verify', {
accountId,
securityCode,
requestId
});
}
/**
* Retrieves recovery methods for the account.
* @returns {Promise<{ accountId: string, data: any }>} - A promise that resolves to recovery methods data.
*/
async getRecoveryMethods() {
const { accountId } = this;
return {
accountId,
data: await this.postSignedJson('/account/recoveryMethods', { accountId })
};
}
/**
* Gets the 2FA method (kind and detail).
* @returns {Promise<{ kind: string, detail: string }>} A promise that resolves to the 2FA method.
*/
async get2faMethod() {
let { data } = await this.getRecoveryMethods();
// @ts-ignore
if (data && data.length) {
// @ts-ignore
data = data.find((m) => m.kind.indexOf('2fa-') === 0);
}
if (!data)
return null;
// @ts-ignore
const { kind, detail } = data;
return { kind, detail };
}
/**
* Generates a signature for the latest finalized block.
* @returns {Promise<{ blockNumber: string, blockNumberSignature: string }>} - A promise that resolves to the signature information.
*/
async signatureFor() {
const { accountId } = this;
const block = await this.connection.provider.block({ finality: 'final' });
const blockNumber = block.header.height.toString();
const signed = await this.connection.signer.signMessage(Buffer.from(blockNumber), accountId, this.connection.networkId);
const blockNumberSignature = Buffer.from(signed.signature).toString('base64');
return { blockNumber, blockNumberSignature };
}
/**
* Sends a signed JSON request to a specified path.
* @param path - The path for the request.
* @param body - The request body.
* @returns {Promise<any>} - A promise that resolves to the response from the helper.
*/
async postSignedJson(path, body) {
return await fetch(this.helperUrl + path, {
body: JSON.stringify({
...body,
...(await this.signatureFor()),
}),
method: 'POST',
});
}
}
exports.Account2FA = Account2FA;
// helpers
const toPK = (pk) => crypto_1.PublicKey.from(pk);
Выполнить команду
Для локальной разработки. Не используйте в интернете!