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

add jwt approve page #282

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
485 changes: 396 additions & 89 deletions src/app/account.service.ts

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { BuyDeSoCompletePageComponent } from './buy-deso/buy-deso-complete-page/
import { BuyOrSendDesoComponent } from './buy-or-send-deso/buy-or-send-deso.component';
import { SignUpMetamaskComponent } from './sign-up-metamask/sign-up-metamask.component';
import { MessagingGroupComponent } from './messaging-group/messaging-group.component';
import { JwtApproveComponent } from './jwt-approve/jwt-approve.component';

export class RouteNames {
public static EMBED = 'embed';
Expand All @@ -40,6 +41,7 @@ export class RouteNames {
public static BUY_DESO = 'buy-deso';
public static BUY_OR_SEND_DESO = 'buy-or-send-deso';
public static MESSAGING_GROUP = 'messaging-group';
public static JWT = 'jwt';
}

const routes: Routes = [
Expand Down Expand Up @@ -96,6 +98,11 @@ const routes: Routes = [
component: MessagingGroupComponent,
pathMatch: 'full',
},
{
path: RouteNames.JWT,
component: JwtApproveComponent,
pathMatch: 'full',
},
];

@NgModule({
Expand Down
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
12 changes: 12 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 All @@ -55,6 +60,7 @@ import { TransactionSpendingLimitDaoCoinLimitOrderComponent } from './transactio
import { TransactionSpendingLimitNftComponent } from './transaction-spending-limit/transaction-spending-limit-nft/transaction-spending-limit-nft.component';
import { TransactionSpendingLimitSectionComponent } from './transaction-spending-limit/transaction-spending-limit-section/transaction-spending-limit-section.component';
import { TransactionSpendingLimitComponent } from './transaction-spending-limit/transaction-spending-limit.component';
import { JwtApproveComponent } from './jwt-approve/jwt-approve.component';

@NgModule({
declarations: [
Expand Down Expand Up @@ -98,6 +104,11 @@ import { TransactionSpendingLimitComponent } from './transaction-spending-limit/
TransactionSpendingLimitAssociationComponent,
TransactionSpendingLimitAccessGroupComponent,
TransactionSpendingLimitAccessGroupMemberComponent,
GroupedAccountSelectComponent,
RecoverySecretComponent,
BackupSeedDialogComponent,
RemoveAccountDialogComponent,
JwtApproveComponent,
],
imports: [
BrowserModule,
Expand All @@ -114,6 +125,7 @@ import { TransactionSpendingLimitComponent } from './transaction-spending-limit/
}),
BuyDeSoComponentWrapper,
CookieModule.forRoot(),
MatDialogModule,
],
providers: [
IdentityService,
Expand Down
15 changes: 3 additions & 12 deletions src/app/approve/approve.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,10 @@ export class ApproveComponent implements OnInit {
}

onSubmit(): void {
const user = this.accountService.getEncryptedUsers()[this.publicKey];
const isDerived = this.accountService.isMetamaskAccount(user);
const account = this.accountService.getAccountInfo(this.publicKey);
const isDerived = this.accountService.isMetamaskAccount(account);
const signedTransactionHex = this.signingService.signTransaction(
this.seedHex(),
account.seedHex,
this.transactionHex,
isDerived
);
Expand All @@ -117,15 +117,6 @@ export class ApproveComponent implements OnInit {
});
}

seedHex(): string {
const encryptedSeedHex =
this.accountService.getEncryptedUsers()[this.publicKey].encryptedSeedHex;
return this.cryptoService.decryptSeedHex(
encryptedSeedHex,
this.globalVars.hostname
);
}

generateTransactionDescription(): void {
let description = 'sign an unknown transaction';
let publicKeys: string[] = [];
Expand Down
24 changes: 13 additions & 11 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 @@ -83,17 +83,18 @@ export class GoogleComponent implements OnInit {
const mnemonic = fileContents.mnemonic;
const extraText = fileContents.extraText;
const network = fileContents.network;
const keychain = this.cryptoService.mnemonicToKeychain(
mnemonic,
extraText
);
const keychain = this.cryptoService.mnemonicToKeychain(mnemonic, {
extraText,
});

this.publicKey = this.accountService.addUser(
keychain,
mnemonic,
extraText,
network,
true
{
loginMethod: LoginMethod.GOOGLE,
}
);
} catch (err) {
console.error(err);
Expand Down Expand Up @@ -137,16 +138,17 @@ export class GoogleComponent implements OnInit {
this.googleDrive
.uploadFile(this.fileName(), JSON.stringify(userInfo))
.subscribe(() => {
const keychain = this.cryptoService.mnemonicToKeychain(
mnemonic,
extraText
);
const keychain = this.cryptoService.mnemonicToKeychain(mnemonic, {
extraText,
});
this.publicKey = this.accountService.addUser(
keychain,
mnemonic,
extraText,
network,
true
{
loginMethod: LoginMethod.GOOGLE,
}
);
this.loading = false;
});
Expand Down
17 changes: 7 additions & 10 deletions src/app/backend-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,24 +291,20 @@ export class BackendAPIService {
}

jwtPost(path: string, publicKey: string, body: any): Observable<any> {
const publicUserInfo = this.accountService.getEncryptedUsers()[publicKey];
const account = this.accountService.getAccountInfo(publicKey);
// NOTE: there are some cases where derived user's were not being sent phone number
// verification texts due to missing public user info. This is to log how often
// this is happening.
logInteractionEvent('backend-api', 'jwt-post', {
hasPublicUserInfo: !!publicUserInfo,
hasPublicUserInfo: !!account,
});

if (!publicUserInfo) {
if (!account) {
return of(null);
}
const isDerived = this.accountService.isMetamaskAccount(publicUserInfo);
const isDerived = this.accountService.isMetamaskAccount(account);

const seedHex = this.cryptoService.decryptSeedHex(
publicUserInfo.encryptedSeedHex,
this.globalVars.hostname
);
const jwt = this.signingService.signJWT(seedHex, 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
40 changes: 34 additions & 6 deletions src/app/crypto.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,20 +138,29 @@ export class CryptoService {

mnemonicToKeychain(
mnemonic: string,
extraText?: string,
nonStandard?: boolean
{
extraText,
nonStandard,
accountNumber = 0,
}: {
extraText?: string;
nonStandard?: boolean;
accountNumber?: number;
} = {}
): HDNode {
const seed = bip39.mnemonicToSeedSync(mnemonic, extraText);
// @ts-ignore
return HDKey.fromMasterSeed(seed).derive("m/44'/0'/0'/0/0", nonStandard);
return generateSubAccountKeys(seed, accountNumber, {
nonStandard,
});
}

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

seedHexToPrivateKey(seedHex: string): EC.KeyPair {
seedHexToKeyPair(seedHex: string): EC.KeyPair {
const ec = new EC('secp256k1');

return ec.keyFromPrivate(seedHex);
}

Expand All @@ -160,7 +169,7 @@ export class CryptoService {
encryptedSeedHex,
this.globalVars.hostname
);
const privateKey = this.seedHexToPrivateKey(seedHex);
const privateKey = this.seedHexToKeyPair(seedHex);
return this.privateKeyToDeSoPublicKey(privateKey, this.globalVars.network);
}

Expand Down Expand Up @@ -279,3 +288,22 @@ export class CryptoService {
return ethAddressChecksum;
}
}

/**
* We set the account according to the following derivation path scheme:
* m / purpose' / coin_type' / account' / change / address_index
* See for more details: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#account
*/
function generateSubAccountKeys(
seedBytes: Buffer,
accountIndex: number,
options?: { nonStandard?: boolean }
) {
// We are using a customized version of hdkey and the derive signature types
// are not compatible with the "nonStandard" flag. Hence the ts-ignore.
return HDKey.fromMasterSeed(seedBytes).derive(
`m/44'/0'/${accountIndex}'/0/0`,
// @ts-ignore
!!options?.nonStandard
);
}
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
18 changes: 9 additions & 9 deletions src/app/get-deso/get-deso.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@ <h3 class="text--neutral-white">Get starter $DESO</h3>
*ngIf="!alternativeOptionsEnabled && captchaAvailable"
class="padding-bottom--2xlarge text--center"
>
<div class="section--starter__option">
<div class="section--starter__option--captcha">
<div class="font-size--small margin-bottom--medium">
<h6
class="margin-bottom--small text--text-lightest font-weight--medium"
>
Complete a captcha to get free $DESO
</h6>
<p>
Prove you're not a robot 🤖 and we'll<br />send you a small amount
of $DESO that will last<br />you up to thousands of on-chain
transactions.
Prove you're not a robot 🤖 and we'll<br />
send you a small amount of free $DESO that will last<br />
you up to thousands of on-chain transactions.
</p>
</div>
<ng-hcaptcha
Expand Down Expand Up @@ -75,8 +75,8 @@ <h3 class="text--neutral-white">Get starter $DESO</h3>
1. Get DESO for free by verifying your phone number
</h6>
<p>
We'll send you a small amount of $DESO that will last<br />you up to
thousands of on-chain transactions.
We'll send you a small amount of $DESO that will last <br />
you up to thousands of on-chain transactions.
</p>
</div>
<button
Expand Down Expand Up @@ -181,7 +181,7 @@ <h3 class="text--neutral-white">Get starter $DESO</h3>
>
<a
target="_blank"
href="https://www.huobi.com/en-us/exchange/deso_usdt?inviter_id=11345710"
href="https://www.huobi.com/en-us/exchange/deso_usdt"
class="link--secondary display--inline-block margin-right--xsmall"
>Huobi,</a
>
Expand Down Expand Up @@ -232,7 +232,7 @@ <h3 class="text--neutral-white">Get starter $DESO</h3>
data-control-name="sign-up-with-seed-copy-button"
data-control-option="copy"
(click)="copyPublicKey()"
class="button--primary--outline button--small display--flex items--center"
class="button--primary button--small display--flex items--center"
>
<img
*ngIf="publicKeyCopied"
Expand All @@ -246,7 +246,7 @@ <h3 class="text--neutral-white">Get starter $DESO</h3>
src="assets/copy.svg"
class="margin-right--small image--invert"
/>
<span>Copy</span>
<span>Copy to clipboard</span>
</button>
</div>
</div>
Expand Down
Loading