Skip to content

Commit

Permalink
feature: backup seed component
Browse files Browse the repository at this point in the history
  • Loading branch information
jackson-dean committed Sep 20, 2023
1 parent 2dd6da4 commit fbb2f01
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 1 deletion.
6 changes: 6 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,7 +32,9 @@ 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 { HomeComponent } from './home/home.component';
import { IconsModule } from './icons/icons.module';
import { IdentityService } from './identity.service';
Expand Down Expand Up @@ -100,6 +103,8 @@ import { TransactionSpendingLimitComponent } from './transaction-spending-limit/
TransactionSpendingLimitAccessGroupComponent,
TransactionSpendingLimitAccessGroupMemberComponent,
GroupedAccountSelectComponent,
RecoverySecretComponent,
BackupSeedDialogComponent,
],
imports: [
BrowserModule,
Expand All @@ -116,6 +121,7 @@ import { TransactionSpendingLimitComponent } from './transaction-spending-limit/
}),
BuyDeSoComponentWrapper,
CookieModule.forRoot(),
MatDialogModule,
],
providers: [
IdentityService,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<div class="dialog">
<header class="dialog__header">
<button class="dialog__x" style="top: 0; right: 0" (click)="onCancel()">
<i-feather name="x" />
</button>
<h1 class="dialog__title">Backup Recovery Data</h1>
</header>
<div class="dialog__body">
<div *ngIf="!this.secrets" 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! DeSo support agents will
never request this.</strong
>
</p>
<footer class="display--flex">
<button
class="button--medium button--primary margin-right--small"
(click)="onContinue()"
>
Continue
</button>
<button class="button--medium button--secondary" (click)="onCancel()">
Cancel
</button>
</footer>
</div>

<ng-container *ngIf="this.secrets">
<div class="margin-bottom--small">
<p>Seed Phrase</p>
<recovery-secret [secret]="this.secrets.mnemonic" />
</div>
<div *ngIf="this.secrets.extraText" class="margin-bottom--small">
<p>Pass Phrase</p>
<recovery-secret [secret]="this.secrets.extraText" />
</div>
<div>
<p>Seed Hex</p>
<recovery-secret [secret]="this.secrets.seedHex" />
</div>
<button
class="button--primary button--medium margin-top--large"
(click)="onDisableExport()"
>
Disable recovery data backup
</button>
<p class="font-size--small text--center margin-top--xsmall">
Disabling backup is recommended because it makes your account more
secure by preventing anyone from revealing your seed phrase in the
future, even if they've gained access to your device.
<strong>Note that this action is irrevocable;</strong> Only disable
backup once you're sure that you've copied your seed phrase and stored
it in a safe place.
</p>
</ng-container>
</div>
</div>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { AccountService } from '../../account.service';

@Component({
selector: 'backup-seed-dialog',
templateUrl: './backup-seed-dialog.component.html',
styleUrls: ['./backup-seed-dialog.component.scss'],
})
export class BackupSeedDialogComponent {
secrets?: {
mnemonic: string;
extraText: string;
seedHex: string;
};

constructor(
public dialogRef: MatDialogRef<BackupSeedDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: { rootPublicKey: string },
private accountService: AccountService
) {}

onCancel(): void {
this.dialogRef.close();
}

onContinue() {
if (!this.data.rootPublicKey) {
throw new Error('Root public key is required');
}

const { mnemonic, extraText, seedHex } = this.accountService.getAccountInfo(
this.data.rootPublicKey
);
this.secrets = { mnemonic, extraText, seedHex };
}

onDisableExport() {
this.accountService.updateAccountInfo(this.data.rootPublicKey, {
exportDisabled: true,
});
this.dialogRef.close();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { escape } from 'lodash';
import { finalize, take } from 'rxjs/operators';
import {
Expand All @@ -11,6 +12,7 @@ import { AccountService } from '../account.service';
import { BackendAPIService } from '../backend-api.service';
import { GlobalVarsService } from '../global-vars.service';
import { isValid32BitUnsignedInt } from './account-number';
import { BackupSeedDialogComponent } from './backup-seed-dialog/backup-seed-dialog.component';

type AccountViewModel = SubAccountMetadata &
UserProfile & { publicKey: string } & { lastUsed?: boolean };
Expand Down Expand Up @@ -64,7 +66,8 @@ export class GroupedAccountSelectComponent implements OnInit {
constructor(
public accountService: AccountService,
public globalVars: GlobalVarsService,
private backendApi: BackendAPIService
private backendApi: BackendAPIService,
public dialog: MatDialog
) {}

ngOnInit(): void {
Expand Down Expand Up @@ -327,4 +330,19 @@ export class GroupedAccountSelectComponent implements OnInit {
const rootAccount = this.accountService.getAccountInfo(rootPublicKey);
return this.accountService.isMetamaskAccount(rootAccount);
}

shouldShowExportSeedButton(rootPublicKey: string) {
const rootAccount = this.accountService.getAccountInfo(rootPublicKey);
return !rootAccount.exportDisabled;
}

exportSeed(rootPublicKey: string) {
const dialogRef = this.dialog.open(BackupSeedDialogComponent, {
data: { rootPublicKey },
});

dialogRef.afterClosed().subscribe((result) => {
console.log(`Dialog result: ${result}`);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<div class="input--textarea input--seed margin-bottom--small" style="background-color: black; overflow: hidden;">
{{ this.isRevealed ? secret : maskSecret(secret) }}
</div>
<div class="margin-left--xsmall display--flex">
<button
(click)="copySecret()"
class="button--primary button--small margin-left--auto"
style="width: auto"
>
<i-feather [name]="copySuccess ? 'check' : 'copy'" />
</button>
<button
(click)="toggleRevealSecret()"
class="button--secondary button--small margin-left--small"
style="width: auto"
>
<i-feather [name]="!this.isRevealed ? 'eye' : 'eye-off'" />
</button>
</div>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Component, Input } from '@angular/core';

@Component({
selector: 'recovery-secret',
templateUrl: './recovery-secret.component.html',
styleUrls: ['./recovery-secret.component.scss'],
})
export class RecoverySecretComponent {
@Input() secret = '';

isRevealed = false;
copySuccess = false;

maskSecret(secret = '') {
return secret.slice().replace(/\S/g, '*');
}

copySecret() {
window.navigator.clipboard.writeText(this.secret).then(() => {
this.copySuccess = true;
setTimeout(() => {
this.copySuccess = false;
}, 1500);
});
}

toggleRevealSecret() {
this.isRevealed = !this.isRevealed;
}
}
6 changes: 6 additions & 0 deletions src/app/icons/icons.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
CreditCard,
DollarSign,
ExternalLink,
Eye,
EyeOff,
Feather,
Flag,
FolderMinus,
Expand All @@ -48,6 +50,7 @@ import {
RefreshCw,
Repeat,
RotateCw,
Save,
Search,
Send,
Settings,
Expand Down Expand Up @@ -117,6 +120,8 @@ const icons = {
DollarSign,
Diamond,
ExternalLink,
Eye,
EyeOff,
Feather,
Flag,
FolderMinus,
Expand Down Expand Up @@ -152,6 +157,7 @@ const icons = {
RefreshCw,
Repeat,
RotateCw,
Save,
Search,
Send,
Settings,
Expand Down
46 changes: 46 additions & 0 deletions src/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,17 @@ a, .link--primary {
.flex--row-reverse--tablet {
@include media(tablet) { flex-direction: row-reverse; };
}

//-------------------------------
// POSITION
//-------------------------------
.relative {
position: relative;
}
.absolute {
position: absolute;
}

//-------------------------------
// LAYOUT
//-------------------------------
Expand Down Expand Up @@ -1114,3 +1125,38 @@ a, .link--primary {
margin: 0;
cursor: pointer;
}

//-------------------------
// Material Dialog
//-------------------------
.cdk-overlay-dark-backdrop {
background-color: rgba(0,0,0,.8) !important;
}
.dialog {
background-color: lighten(#040609, 10%);
border: 1px solid darkgray;
border-radius: 4px;
padding: 8px;
}
.dialog__header {
position: relative;
@include color('neutral', 'white', 'color');
}
.dialog__x {
border: none;
background: none;
position: absolute;
top: 0;
right: 0;
margin: 0;
padding: 0;
cursor: pointer;
@include color('neutral', 'white', 'color');
}
.dialog__title {
@include font-size('large');
@include color('neutral', 'white', 'color');
}
.dialog__body {
padding: 12px 0;
}
7 changes: 7 additions & 0 deletions src/types/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ export interface PrivateUserInfo extends AccountMetadata {
*/
subAccounts?: SubAccountMetadata[];

/**
* Determines whether we display the "Back up your seed" button in the UI. We
* show it by default for all users, but we hide it for users who have
* explicitly disabled it.
*/
exportDisabled?: boolean;

/** DEPRECATED in favor of loginMethod */
google?: boolean;
}
Expand Down

0 comments on commit fbb2f01

Please sign in to comment.