Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: forgot password #2694

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion _raw/locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1741,7 +1741,10 @@
"required": "Enter the Password to Unlock",
"placeholder": "Enter the Password to Unlock",
"error": "incorrect password"
}
},
"title": "Rabby Wallet",
"description": "The game-changing wallet for Ethereum and all EVM chains",
"btnForgotPassword": "Forgot Password?"
},
"addToken": {
"noTokenFound": "No token found",
Expand Down Expand Up @@ -2438,6 +2441,45 @@
"placeholder": "Input safe address",
"loading": "Searching the deployed chain of this address"
}
},
"forgotPassword": {
"home": {
"title": "Forgot Password",
"description": "Rabby Wallet doesn't store your password and can't help you retrieve it. Reset your wallet to set up a new one.",
"button": "Begin Reset Process",
"descriptionNoData": "Rabby Wallet doesn't store your password and can't help you retrieve it. Set a new password if you forgot",
"buttonNoData": "Set Password"
},
"reset": {
"title": "Reset Rabby Wallet",
"button": "Confirm Reset",
"alert": {
"title": "Data will be deleted and unrecoverable:",
"seed": "Seed Phrase",
"privateKey": "Private Key"
},
"tip": {
"title": "Data will be kept:",
"hardware": "Imported Hardware Wallets",
"whitelist": "Whitelist Settings",
"records": "Signature Records",
"safe": "Imported Safe Wallets",
"watch": "Contacts and Watch-Only Addresses"
},
"confirm": "Type <1>RESET</1> in the box to confirm and proceed"
},
"tip": {
"title": "Rabby Wallet Reset Completed",
"description": "Create a new password to continue",
"button": "Set Password",
"descriptionNoData": "Add your address to get started",
"buttonNoData": "Add Address"
},
"success": {
"title": "Password Successfully Set",
"description": "You're all set to use Rabby Wallet",
"button": "Done"
}
}
},
"component": {
Expand Down
42 changes: 41 additions & 1 deletion _raw/locales/zh-CN/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1702,7 +1702,8 @@
"error": "密码错误",
"placeholder": "输入密码解锁",
"required": "输入密码解锁"
}
},
"btnForgotPassword": "忘记密码?"
},
"addressDetail": {
"add-to-whitelist": "添加到白名单",
Expand Down Expand Up @@ -2038,6 +2039,45 @@
},
"sign": {
"transactionSpeed": "交易速度"
},
"forgotPassword": {
"home": {
"buttonNoData": "设置新密码",
"button": "开始重置流程",
"title": "忘记密码",
"description": "Rabby不会保存你的密码,因此丢失密码无法找回 如果忘记密码只能重置钱包并重新设置密码",
"descriptionNoData": "Rabby不会保存你的密码,因此丢失密码无法找回 如果忘记密码只能重新设置密码"
},
"reset": {
"title": "重置 Rabby Wallet",
"alert": {
"title": "重置后会清除以下数据,且无法帮你找回",
"seed": "助记词",
"privateKey": "私钥"
},
"tip": {
"title": "会保留以下数据",
"hardware": "已导入的硬件钱包地址",
"safe": "已导入的 Safe 地址",
"watch": "已导入的观察地址",
"whitelist": "转账白名单",
"records": "签名记录"
},
"confirm": "如确认重置钱包, 请输入: <1>RESET</1>",
"button": "确认重置"
},
"success": {
"title": "密码创建成功",
"description": "请继续使用 Rabby",
"button": "完成"
},
"tip": {
"title": "Rabby Wallet 已经重置",
"button": "设置新密码",
"buttonNoData": "重新导入地址",
"description": "请设定一个新的密码继续使用钱包",
"descriptionNoData": "请重新导入地址后使用钱包"
}
}
},
"component": {
Expand Down
17 changes: 17 additions & 0 deletions src/background/controller/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4980,6 +4980,23 @@ export class WalletController extends BaseController {
tx,
});
};

savedUnencryptedKeyringData = async () =>
keyringService.savedUnencryptedKeyringData();

hasEncryptedKeyringData = async () =>
keyringService.hasEncryptedKeyringData();

hasUnencryptedKeyringData = async () =>
keyringService.hasUnencryptedKeyringData();

resetPassword = async (password: string) =>
keyringService.resetPassword(password);

resetBooted = async () => keyringService.resetBooted();

getUnencryptedKeyringTypes = async () =>
keyringService.getUnencryptedKeyringTypes();
}

const wallet = new WalletController();
Expand Down
104 changes: 91 additions & 13 deletions src/background/service/keyring/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ import {
} from 'background/utils/password';
import uninstalledMetricService from '../uninstalled';

const UNENCRYPTED_IGNORE_KEYRING = [
KEYRING_TYPE.SimpleKeyring,
KEYRING_TYPE.HdKeyring,
];

export const KEYRING_SDK_TYPES = {
SimpleKeyring,
HdKeyring,
Expand All @@ -64,6 +69,11 @@ export const KEYRING_SDK_TYPES = {
EthImKeyKeyring,
};

export type KeyringSerializedData<T = any> = {
type: string;
data: T;
};

interface MemStoreState {
isUnlocked: boolean;
keyringTypes: any[];
Expand Down Expand Up @@ -346,6 +356,11 @@ export class KeyringService extends EventEmitter {
this.setUnlocked();
}

// force store unencrypted keyring data if not exist
if (!this.store.getState().unencryptedKeyringData) {
await this.persistAllKeyrings();
}

return this.fullUpdate();
}

Expand Down Expand Up @@ -786,36 +801,50 @@ export class KeyringService extends EventEmitter {
* @param {string} password - The keyring controller password.
* @returns {Promise<boolean>} Resolves to true once keyrings are persisted.
*/
persistAllKeyrings(): Promise<boolean> {
async persistAllKeyrings(): Promise<boolean> {
if (!this.isUnlocked()) {
return Promise.reject(
new Error('KeyringController - password is not a string')
);
}

return Promise.all(
const serializedKeyrings = await Promise.all(
this.keyrings.map((keyring) => {
return Promise.all([keyring.type, keyring.serialize()]).then(
(serializedKeyringArray) => {
// Label the output values on each serialized Keyring:
return {
type: serializedKeyringArray[0],
data: serializedKeyringArray[1],
};
} as KeyringSerializedData;
}
);
})
)
.then((serializedKeyrings) => {
return passwordEncrypt({
data: serializedKeyrings,
password: this.password,
});
);

let hasEncryptedKeyringData = false;
const unencryptedKeyringData = serializedKeyrings
.map(({ type, data }) => {
if (!UNENCRYPTED_IGNORE_KEYRING.includes(type as any)) {
return { type, data };
}
hasEncryptedKeyringData = true;
return undefined;
})
.then((encryptedString) => {
this.store.updateState({ vault: encryptedString });
return true;
});
.filter(Boolean) as KeyringSerializedData[];

const encryptedString = await passwordEncrypt({
data: serializedKeyrings,
password: this.password,
});

this.store.updateState({
vault: encryptedString,
unencryptedKeyringData,
hasEncryptedKeyringData,
});

return true;
}

/**
Expand Down Expand Up @@ -1235,6 +1264,55 @@ export class KeyringService extends EventEmitter {
isUnlocked(): boolean {
return this.memStore.getState().isUnlocked;
}

/**
* unencryptedKeyringData is saved in the store
*/
savedUnencryptedKeyringData(): boolean {
return 'unencryptedKeyringData' in this.store.getState();
}

/**
* has seed phrase or private key in the store
*/
hasEncryptedKeyringData(): boolean {
return this.store.getState().hasEncryptedKeyringData;
}

/**
* has unencrypted keyring data (not seed phrase or private key) in the store
*/
hasUnencryptedKeyringData(): boolean {
return this.store.getState().unencryptedKeyringData?.length > 0;
}

async resetPassword(password: string) {
// update vault and booted with new password
const unencryptedKeyringData = this.store.getState().unencryptedKeyringData;
const booted = await passwordEncrypt({
data: 'true',
password,
});
const vault = await passwordEncrypt({
data: unencryptedKeyringData,
password,
});

this.store.updateState({ vault, booted, hasEncryptedKeyringData: false });

// lock wallet
this.setLocked();
}

async resetBooted() {
this.store.updateState({ booted: undefined });
}

async getUnencryptedKeyringTypes() {
return (this.store
.getState()
.unencryptedKeyringData?.map((item) => item.type) ?? []) as string[];
}
}

export default new KeyringService();
5 changes: 5 additions & 0 deletions src/ui/assets/forgot/lock-success.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/ui/assets/forgot/lock.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 35 additions & 0 deletions src/ui/assets/forgot/recycle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions src/ui/assets/forgot/time-cc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions src/ui/assets/forgot/trash-cc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading