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

feature: new component for sub account UI #266

Merged
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
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>
Loading