Skip to content

Commit

Permalink
feature: new component for sub account UI (#266)
Browse files Browse the repository at this point in the history
* feature: new component for sub account UI

* feature: add feature flag guard for new account select component (#268)

* feature: add feature flag guard for new account select component

* feature: backup seed component (#269)

* feature: backup seed component

* feature: add balance to account selector UI (#270)

* feature: add balance to account selector UI

* migrate swal to mat dialog (#273)

* migrate swal to mat dialog

* update button styles (#277)

* update button styles

* review feedback and bug fixes (#279)
  • Loading branch information
jackson-dean committed Sep 25, 2023
1 parent 5ea24e3 commit 7027d24
Show file tree
Hide file tree
Showing 34 changed files with 1,182 additions and 264 deletions.
219 changes: 123 additions & 96 deletions src/app/account.service.ts

Large diffs are not rendered by default.

4 changes: 0 additions & 4 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,6 @@ export class AppComponent implements OnInit {
this.globalVars.authenticatedUsers = authenticatedUsers;
}

if (params.get('subAccounts') === 'true') {
this.globalVars.subAccounts = true;
}

// Callback should only be used in mobile applications, where payload is passed through URL parameters.
const callback = params.get('callback') || stateParamsFromGoogle.callback;
if (callback) {
Expand Down
10 changes: 10 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatTooltipModule } from '@angular/material/tooltip';
import { BrowserModule } from '@angular/platform-browser';
Expand Down Expand Up @@ -31,6 +32,10 @@ import { ErrorCallbackComponent } from './error-callback/error-callback.componen
import { FreeDeSoDisclaimerComponent } from './free-deso-message/free-deso-disclaimer/free-deso-disclaimer.component';
import { FreeDesoMessageComponent } from './free-deso-message/free-deso-message.component';
import { GetDesoComponent } from './get-deso/get-deso.component';
import { BackupSeedDialogComponent } from './grouped-account-select/backup-seed-dialog/backup-seed-dialog.component';
import { GroupedAccountSelectComponent } from './grouped-account-select/grouped-account-select.component';
import { RecoverySecretComponent } from './grouped-account-select/recovery-secret/recovery-secret.component';
import { RemoveAccountDialogComponent } from './grouped-account-select/remove-account-dialog/remove-account-dialog.component';
import { HomeComponent } from './home/home.component';
import { IconsModule } from './icons/icons.module';
import { IdentityService } from './identity.service';
Expand Down Expand Up @@ -98,6 +103,10 @@ import { TransactionSpendingLimitComponent } from './transaction-spending-limit/
TransactionSpendingLimitAssociationComponent,
TransactionSpendingLimitAccessGroupComponent,
TransactionSpendingLimitAccessGroupMemberComponent,
GroupedAccountSelectComponent,
RecoverySecretComponent,
BackupSeedDialogComponent,
RemoveAccountDialogComponent,
],
imports: [
BrowserModule,
Expand All @@ -114,6 +123,7 @@ import { TransactionSpendingLimitComponent } from './transaction-spending-limit/
}),
BuyDeSoComponentWrapper,
CookieModule.forRoot(),
MatDialogModule,
],
providers: [
IdentityService,
Expand Down
3 changes: 1 addition & 2 deletions src/app/approve/approve.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,7 @@ export class ApproveComponent implements OnInit {
const signedTransactionHex = this.signingService.signTransaction(
account.seedHex,
this.transactionHex,
isDerived,
account.accountNumber
isDerived
);
this.finishFlow(signedTransactionHex);
}
Expand Down
8 changes: 3 additions & 5 deletions src/app/auth/google/google.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Component, NgZone, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subject } from 'rxjs';
import { environment } from '../../../environments/environment';
import { GoogleAuthState } from '../../../types/identity';
import { GoogleAuthState, LoginMethod } from '../../../types/identity';
import { AccountService } from '../../account.service';
import { RouteNames } from '../../app-routing.module';
import { BackendAPIService } from '../../backend-api.service';
Expand Down Expand Up @@ -93,9 +93,8 @@ export class GoogleComponent implements OnInit {
mnemonic,
extraText,
network,
0,
{
google: true,
loginMethod: LoginMethod.GOOGLE,
}
);
} catch (err) {
Expand Down Expand Up @@ -149,9 +148,8 @@ export class GoogleComponent implements OnInit {
mnemonic,
extraText,
network,
0,
{
google: true,
loginMethod: LoginMethod.GOOGLE,
}
);
this.loading = false;
Expand Down
9 changes: 3 additions & 6 deletions src/app/backend-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,11 +304,7 @@ export class BackendAPIService {
}
const isDerived = this.accountService.isMetamaskAccount(account);

const jwt = this.signingService.signJWT(
account.seedHex,
account.accountNumber,
isDerived
);
const jwt = this.signingService.signJWT(account.seedHex, isDerived);
return this.post(path, { ...body, ...{ JWT: jwt } });
}

Expand Down Expand Up @@ -349,7 +345,7 @@ export class BackendAPIService {
publicKeys: string[]
): Observable<{ [key: string]: UserProfile }> {
const userProfiles: { [key: string]: any } = {};
const req = this.GetUsersStateless(publicKeys, true);
const req = this.GetUsersStateless(publicKeys, true, true);
if (publicKeys.length > 0) {
return req
.pipe(
Expand All @@ -358,6 +354,7 @@ export class BackendAPIService {
userProfiles[user.PublicKeyBase58Check] = {
username: user.ProfileEntryResponse?.Username,
profilePic: user.ProfileEntryResponse?.ProfilePic,
balanceNanos: user.BalanceNanos,
};
}
return userProfiles;
Expand Down
29 changes: 6 additions & 23 deletions src/app/crypto.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,49 +142,32 @@ export class CryptoService {
nonStandard?: boolean
): HDNode {
const seed = bip39.mnemonicToSeedSync(mnemonic, extraText);
return deriveKeys(seed, 0, {
return generateSubAccountKeys(seed, 0, {
nonStandard,
});
}

getSubAccountKeychain(masterSeedHex: string, accountIndex: number): HDNode {
const seedBytes = Buffer.from(masterSeedHex, 'hex');
return deriveKeys(seedBytes, accountIndex);
return generateSubAccountKeys(seedBytes, accountIndex);
}

keychainToSeedHex(keychain: HDNode): string {
return keychain.privateKey.toString('hex');
}

/**
* For a given parent seed hex and account number, return the corresponding private key. Public/private
* key pairs are independent and unique based on a combination of the seed hex and account number.
* @param parentSeedHex This is the seed hex used to generate multiple HD wallets/keys from a single seed.
* @param accountNumber This is the account number used to generate unique keys from the parent seed.
* @returns
*/
seedHexToKeyPair(parentSeedHex: string, accountNumber: number): EC.KeyPair {
seedHexToKeyPair(seedHex: string): EC.KeyPair {
const ec = new EC('secp256k1');

if (accountNumber === 0) {
return ec.keyFromPrivate(parentSeedHex);
}

const hdKeys = this.getSubAccountKeychain(parentSeedHex, accountNumber);
const seedHex = this.keychainToSeedHex(hdKeys);

return ec.keyFromPrivate(seedHex);
}

encryptedSeedHexToPublicKey(
encryptedSeedHex: string,
accountNumber: number
): string {
encryptedSeedHexToPublicKey(encryptedSeedHex: string): string {
const seedHex = this.decryptSeedHex(
encryptedSeedHex,
this.globalVars.hostname
);
const privateKey = this.seedHexToKeyPair(seedHex, accountNumber);
const privateKey = this.seedHexToKeyPair(seedHex);
return this.privateKeyToDeSoPublicKey(privateKey, this.globalVars.network);
}

Expand Down Expand Up @@ -309,7 +292,7 @@ export class CryptoService {
* m / purpose' / coin_type' / account' / change / address_index
* See for more details: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#account
*/
function deriveKeys(
function generateSubAccountKeys(
seedBytes: Buffer,
accountIndex: number,
options?: { nonStandard?: boolean }
Expand Down
9 changes: 1 addition & 8 deletions src/app/derive/derive.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,7 @@
}}</span>
</h4>
<ng-container *ngIf="!publicKeyBase58Check">
<app-account-select
[allUsers]="allUsers"
(onAccountSelect)="this.onAccountSelected($event)"
></app-account-select>
<div class="text--divider margin-top--medium margin-bottom--medium">
or
</div>
<app-log-in-options></app-log-in-options>
<grouped-account-select (onAccountSelect)="onAccountSelected($event)" />
</ng-container>
<div
class="box--border box--base box--rounded"
Expand Down
10 changes: 5 additions & 5 deletions src/app/derive/derive.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export class DeriveComponent implements OnInit {
transactionSpendingLimitResponse:
| TransactionSpendingLimitResponse
| undefined;
hasUsers = false;
hoveredAccount = -1;
publicKeyBase58Check: string | undefined = undefined;
derivedPublicKeyBase58Check: string | undefined = undefined;
Expand All @@ -54,10 +53,6 @@ export class DeriveComponent implements OnInit {
) {}

ngOnInit(): void {
// Load profile pictures and usernames
const publicKeys = this.accountService.getPublicKeys();
this.hasUsers = publicKeys.length > 0;

this.backendApi.GetAppState().subscribe((res) => {
this.blockHeight = res.BlockHeight;
});
Expand All @@ -69,6 +64,11 @@ export class DeriveComponent implements OnInit {
throw Error('invalid query parameter permutation');
}
if (params.publicKey) {
if (!this.publicKeyBase58Check) {
this.accountService.updateAccountInfo(params.publicKey, {
lastLoginTimestamp: Date.now(),
});
}
this.publicKeyBase58Check = params.publicKey;
this.isSingleAccount = true;
}
Expand Down
38 changes: 32 additions & 6 deletions src/app/global-vars.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,6 @@ export class GlobalVarsService {
*/
showSkip: boolean = false;

/**
* Flag used to gate the new subAccounts functionality. After some sunset
* period (TBD), we can remove this flag and make this the default behavior.
*/
subAccounts: boolean = false;

/**
* Set of public keys that have been authenticated by the calling application.
* This is used as a hint to decide whether to show the derived key approval
Expand Down Expand Up @@ -180,4 +174,36 @@ export class GlobalVarsService {
formatTxCountLimit(count: number = 0): string {
return count >= 1e9 ? 'UNLIMITED' : count.toLocaleString();
}

abbreviateNumber(value: number) {
if (value === 0) {
return '0';
}

if (value < 0) {
return value.toString();
}
if (value < 0.01) {
return value.toFixed(5);
}
if (value < 0.1) {
return value.toFixed(4);
}

let shortValue;
const suffixes = ['', 'K', 'M', 'B', 'e12', 'e15', 'e18', 'e21'];
const suffixNum = Math.floor((('' + value.toFixed(0)).length - 1) / 3);
shortValue = value / Math.pow(1000, suffixNum);
if (
Math.floor(shortValue / 100) > 0 ||
shortValue / 1 === 0 ||
suffixNum > 3
) {
return shortValue.toFixed(0) + suffixes[suffixNum];
}
if (Math.floor(shortValue / 10) > 0 || Math.floor(shortValue) > 0) {
return shortValue.toFixed(2) + suffixes[suffixNum];
}
return shortValue.toFixed(3) + suffixes[suffixNum];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<div class="dialog">
<header class="dialog__header">
<button class="dialog__x" style="top: 0; right: 0" (click)="cancel()">
<i-feather name="x" />
</button>
<h1 class="dialog__title">Backup DeSo Seed</h1>
</header>
<div class="dialog__body">
<div *ngIf="this.step === 1" class="text--center">
<p class="margin-bottom--small">
Your seed phrase is the only way to recover your DeSo account. If you
lose your seed phrase, you will lose access to your DeSo account. Store
it in a safe and secure place.
</p>
<p class="margin-bottom--small font-size--large text--neutral-white">
<strong
>DO NOT share your seed phrase with anyone! Support agents will never
request this.</strong
>
</p>
<footer class="display--flex">
<button
class="button--medium button--primary--outline"
(click)="cancel()"
>
Cancel
</button>
<button
class="button--medium button--primary margin-left--small"
(click)="showSecrets()"
>
Continue
</button>
</footer>
</div>

<ng-container *ngIf="this.step === 2">
<div *ngIf="this.mnemonic" class="margin-bottom--small">
<p>Seed Phrase</p>
<recovery-secret [secret]="this.mnemonic" />
</div>
<div *ngIf="this.extraText" class="margin-bottom--small">
<p>Pass Phrase</p>
<recovery-secret [secret]="this.extraText" />
</div>
<div *ngIf="this.seedHex">
<p>Seed Hex</p>
<p class="font-size--small">
Provides an alternative means of logging in if you don't have a seed
phrase.
</p>
<recovery-secret [secret]="this.seedHex" />
</div>
<button
class="button--primary button--medium margin-top--large"
(click)="showDisableBackupConfirmation()"
>
Disable DeSo Seed Backup
</button>
<p
class="font-size--small margin-top--xsmall padding-left--small padding-right--small"
>
Disabling backup makes your account more secure by preventing anyone
from revealing your seed in the future, even if they've gained access to
your device.
</p>
</ng-container>

<div *ngIf="this.step === 3" class="text--center">
<p class="margin-bottom--small">
Disabling backup means you will not be able to access your seed phrase
anymore.
<strong
>Make sure that you've copied your seed phrase and stored it in a safe
place before you proceed.</strong
>
</p>
<footer class="display--flex">
<button
class="button--medium button--primary--outline"
(click)="cancel()"
>
Cancel
</button>
<button
class="button--medium button--primary margin-left--small"
(click)="disableBackup()"
>
Confirm
</button>
</footer>
</div>
</div>
</div>
Empty file.
Loading

0 comments on commit 7027d24

Please sign in to comment.