From 3286c1df828fe65731911787b4ba5c96bd14ad4c Mon Sep 17 00:00:00 2001 From: Spencer Ofwiti Date: Wed, 17 Feb 2021 21:22:06 +0300 Subject: [PATCH] Add auth guard connection for dashboard routes. --- src/app/_guards/auth.guard.ts | 4 +-- src/app/_helpers/pgp-signer.ts | 8 ++++-- src/app/_services/auth.service.ts | 13 +++------ src/app/_services/transaction.service.ts | 11 ++------ src/app/_services/user.service.ts | 36 ++++++++++++------------ src/app/app-routing.module.ts | 3 +- src/app/app.module.ts | 9 ++++-- src/app/auth/auth.component.html | 11 ++------ src/app/auth/auth.component.ts | 34 ++++++++-------------- src/assets/js/hoba-pgp.js | 2 -- src/assets/js/hoba.js | 2 -- src/environments/environment.prod.ts | 1 + src/environments/environment.ts | 1 + 13 files changed, 56 insertions(+), 79 deletions(-) diff --git a/src/app/_guards/auth.guard.ts b/src/app/_guards/auth.guard.ts index 3bc8723..2aed323 100644 --- a/src/app/_guards/auth.guard.ts +++ b/src/app/_guards/auth.guard.ts @@ -12,11 +12,11 @@ export class AuthGuard implements CanActivate { canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { - if (localStorage.getItem(atob('CICADA_USER'))) { + if (sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'))) { return true; } - this.router.navigate(['/auth'], { queryParams: { returnUrl: state.url }}); + this.router.navigate(['/auth']); return false; } diff --git a/src/app/_helpers/pgp-signer.ts b/src/app/_helpers/pgp-signer.ts index b2b7845..33c27ee 100644 --- a/src/app/_helpers/pgp-signer.ts +++ b/src/app/_helpers/pgp-signer.ts @@ -19,7 +19,7 @@ interface Signer { fingerprint(): string; prepare(material: Signable): boolean; verify(digest: string, signature: Signature): void; - sign(digest: string): void; + sign(digest: string): Promise; } class PGPSigner implements Signer { @@ -72,9 +72,13 @@ class PGPSigner implements Signer { }); } - public sign(digest: string): void { + public async sign(digest: string): Promise { const m = openpgp.cleartext.fromText(digest); const pk = this.keyStore.getPrivateKey(); + if (!pk.isDecrypted()) { + const password = window.prompt('password'); + await pk.decrypt(password); + } const opts = { message: m, privateKeys: [pk], diff --git a/src/app/_services/auth.service.ts b/src/app/_services/auth.service.ts index 72ad18c..6b47210 100644 --- a/src/app/_services/auth.service.ts +++ b/src/app/_services/auth.service.ts @@ -5,8 +5,6 @@ import { signChallenge } from '@src/assets/js/hoba-pgp.js'; import {environment} from '@src/environments/environment'; import {HttpClient} from '@angular/common/http'; -const origin = 'http://localhost:4444'; - @Injectable({ providedIn: 'root' }) @@ -34,7 +32,7 @@ export class AuthService { getWithToken(): void { const xhr = new XMLHttpRequest(); xhr.responseType = 'text'; - xhr.open('GET', origin + window.location.search.substring(1)); + xhr.open('GET', environment.cicAuthUrl + window.location.search.substring(1)); xhr.setRequestHeader('Authorization', 'Bearer ' + this.sessionToken); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.setRequestHeader('x-cic-automerge', 'none'); @@ -44,7 +42,6 @@ export class AuthService { } this.sessionLoginCount++; this.setState('click to perform login ' + this.sessionLoginCount + ' with token ' + this.sessionToken); - console.log('received', xhr.responseText); return; }); xhr.send(); @@ -53,7 +50,7 @@ export class AuthService { sendResponse(hobaResponseEncoded): void { const xhr = new XMLHttpRequest(); xhr.responseType = 'text'; - xhr.open('GET', origin + window.location.search.substring(1)); + xhr.open('GET', environment.cicAuthUrl + window.location.search.substring(1)); xhr.setRequestHeader('Authorization', 'HOBA ' + hobaResponseEncoded); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.setRequestHeader('x-cic-automerge', 'none'); @@ -65,7 +62,6 @@ export class AuthService { sessionStorage.setItem(btoa('CICADA_SESSION_TOKEN'), this.sessionToken); this.sessionLoginCount++; this.setState('click to perform login ' + this.sessionLoginCount + ' with token ' + this.sessionToken); - console.log('received', xhr.responseText); return; }); xhr.send(); @@ -74,7 +70,7 @@ export class AuthService { getChallenge(): void { const xhr = new XMLHttpRequest(); xhr.responseType = 'arraybuffer'; - xhr.open('GET', origin + window.location.search.substring(1)); + xhr.open('GET', environment.cicAuthUrl + window.location.search.substring(1)); xhr.onload = (e) => { if (xhr.status === 401) { const authHeader = xhr.getResponseHeader('WWW-Authenticate'); @@ -107,7 +103,7 @@ export class AuthService { async loginResponse(o): Promise { - const r = await signChallenge(o.challenge, o.realm, origin, this.mutableKeyStore); + const r = await signChallenge(o.challenge, o.realm, environment.cicAuthUrl, this.mutableKeyStore); this.sendResponse(r); } @@ -118,7 +114,6 @@ export class AuthService { } async setKey(privateKeyArmored): Promise { - console.log('settings pk' + privateKeyArmored); try { await this.mutableKeyStore.importPrivateKey(privateKeyArmored); localStorage.setItem(btoa('CICADA_PRIVATE_KEY'), privateKeyArmored); diff --git a/src/app/_services/transaction.service.ts b/src/app/_services/transaction.service.ts index bc833ba..f1e9a8b 100644 --- a/src/app/_services/transaction.service.ts +++ b/src/app/_services/transaction.service.ts @@ -1,11 +1,10 @@ import { Injectable } from '@angular/core'; import {HttpClient} from '@angular/common/http'; -import {first, map} from 'rxjs/operators'; +import {map} from 'rxjs/operators'; import {BehaviorSubject, Observable} from 'rxjs'; import {environment} from '@src/environments/environment'; import {User} from 'cic-client-meta'; import {UserService} from '@app/_services/user.service'; -import {parse} from '@src/assets/js/parse-vcard'; import { AccountIndex } from '@app/_helpers'; @Injectable({ @@ -75,13 +74,7 @@ export class TransactionService { } async getUser(address: string): Promise { - this.userService.getUser(await User.toKey(address)).pipe(first()).subscribe(res => { - const vcard = parse(atob(res.vcard)); - res.vcard = vcard; - this.userInfo = res; - }, error => { - console.log(error); - }); + this.userInfo = this.userService.getUser(await User.toKey(address)); } transferRequest(tokenAddress: string, senderAddress: string, recipientAddress: string, value: number): Observable { diff --git a/src/app/_services/user.service.ts b/src/app/_services/user.service.ts index cd66eed..eb438d8 100644 --- a/src/app/_services/user.service.ts +++ b/src/app/_services/user.service.ts @@ -2,9 +2,10 @@ import { Injectable } from '@angular/core'; import {BehaviorSubject, Observable} from 'rxjs'; import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http'; import {environment} from '@src/environments/environment'; -import {first, map} from 'rxjs/operators'; +import {first} from 'rxjs/operators'; import { ArgPair, Envelope, Syncable } from '@src/assets/js/cic-meta/sync.js'; import {MutableKeyStore, MutablePgpKeyStore, PGPSigner, Signer} from '@app/_helpers'; +import {User} from 'cic-client-meta'; @Injectable({ providedIn: 'root' @@ -70,30 +71,29 @@ export class UserService { location, referrer }; - let addressStub = address.slice(2); - addressStub = '0000000000000000000000000000000000000000000000000000000000000000'; + const accountKey = await User.toKey(address); const headers = new HttpHeaders({'x-cic-automerge': 'client'}); - this.http.get(`${environment.cicMetaUrl}/${addressStub}`, { headers }).pipe(first()).subscribe(async res => { + this.http.get(`${environment.cicMetaUrl}/${accountKey}`, { headers }).pipe(first()).subscribe(async res => { this.syncableAccount = Envelope.fromJSON(res).unwrap(); let update = []; for (const prop in accountDetails) { update.push(new ArgPair(prop, accountDetails[prop])); } this.syncableAccount.update(update, 'client-branch'); - await this.updateMeta(addressStub, headers); + await this.updateMeta(accountKey, headers); }, async error => { console.error('There is an error!', error); - const refName = addressStub + ':cic-person'; + const refName = accountKey + ':cic.person'; this.syncableAccount = new Syncable(refName, accountDetails); - await this.updateMeta(addressStub, headers); + await this.updateMeta(accountKey, headers); }); - return addressStub; + return accountKey; } - async updateMeta(addressStub: string, headers: HttpHeaders): Promise { + async updateMeta(accountKey: string, headers: HttpHeaders): Promise { const envelope = await this.wrap(this.syncableAccount , this.signer); const reqBody = envelope.toJSON(); - this.http.put(`${environment.cicMetaUrl}/${addressStub}`, { data: reqBody }, { headers }).pipe(first()).subscribe(res => { + this.http.put(`${environment.cicMetaUrl}/${accountKey}`, { data: reqBody }, { headers }).pipe(first()).subscribe(res => { console.log(res); }); } @@ -146,16 +146,16 @@ export class UserService { return this.http.post(`${environment.cicCacheUrl}/staff/${id}`, {accountType: type}); } - getUser(userKey: string): Observable { - const params = new HttpParams().set('userKey', '0970c6e9cdad650ba9006e5a1caf090a13da312792389a147263c98ac78cd037'); - return this.http.get(`${environment.cicScriptsUrl}`, { params }) - .pipe(map(response => { - return response; - })); + getUser(userKey: string): any { + const headers = new HttpHeaders({'x-cic-automerge': 'client'}); + this.http.get(`${environment.cicMetaUrl}/${userKey}`, { headers }).pipe(first()).subscribe(async res => { + const fetchedAccount = Envelope.fromJSON(res).unwrap(); + return fetchedAccount.m.data; + }); } wrap(syncable: Syncable, signer: Signer): Promise { - return new Promise((whohoo, doh) => { + return new Promise(async (whohoo, doh) => { syncable.setSigner(signer); syncable.onwrap = async (env) => { if (env === undefined) { @@ -164,7 +164,7 @@ export class UserService { } whohoo(env); }; - syncable.sign(); + await syncable.sign(); }); } } diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index c7787a2..d139729 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,9 +1,10 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; +import {AuthGuard} from '@app/_guards'; const routes: Routes = [ { path: 'auth', loadChildren: () => import('@app/auth/auth.module').then(m => m.AuthModule) }, - { path: '', loadChildren: () => import('@pages/pages.module').then(m => m.PagesModule) }, + { path: '', loadChildren: () => import('@pages/pages.module').then(m => m.PagesModule), canActivate: [AuthGuard] }, { path: '**', redirectTo: '', pathMatch: 'full' } ]; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index dd96e27..2dd2efb 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,11 +4,12 @@ import { NgModule } from '@angular/core'; import { AppRoutingModule } from '@app/app-routing.module'; import { AppComponent } from '@app/app.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import {HttpClientModule} from '@angular/common/http'; -import {MutablePgpKeyStore, MockBackendProvider} from '@app/_helpers'; +import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http'; +import {MutablePgpKeyStore, MockBackendProvider, ErrorInterceptor} from '@app/_helpers'; import {DataTablesModule} from 'angular-datatables'; import {SharedModule} from '@app/shared/shared.module'; import {MatTableModule} from '@angular/material/table'; +import {AuthGuard} from '@app/_guards'; @NgModule({ declarations: [ @@ -24,8 +25,10 @@ import {MatTableModule} from '@angular/material/table'; MatTableModule ], providers: [ + AuthGuard, MutablePgpKeyStore, - MockBackendProvider + MockBackendProvider, + { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }, ], bootstrap: [AppComponent] }) diff --git a/src/app/auth/auth.component.html b/src/app/auth/auth.component.html index 400eb83..99501a2 100644 --- a/src/app/auth/auth.component.html +++ b/src/app/auth/auth.component.html @@ -37,20 +37,15 @@

Enter Passphrase

-
+ - Passphrase - -
- Passphrase is required. -
+ Login State +
diff --git a/src/app/auth/auth.component.ts b/src/app/auth/auth.component.ts index 4f5f305..d257849 100644 --- a/src/app/auth/auth.component.ts +++ b/src/app/auth/auth.component.ts @@ -1,16 +1,17 @@ -import {AfterViewInit, Component, OnInit} from '@angular/core'; +import {Component, OnInit} from '@angular/core'; import {FormBuilder, FormGroup, Validators} from '@angular/forms'; import {CustomErrorStateMatcher} from '@app/_helpers'; import {AuthService} from '@app/_services'; +import {Router} from '@angular/router'; @Component({ selector: 'app-auth', templateUrl: './auth.component.html', styleUrls: ['./auth.component.scss'] }) -export class AuthComponent implements OnInit, AfterViewInit { +export class AuthComponent implements OnInit { keyForm: FormGroup; - passphraseForm: FormGroup; + stateForm: FormGroup; submitted: boolean = false; loading: boolean = false; matcher = new CustomErrorStateMatcher(); @@ -18,14 +19,15 @@ export class AuthComponent implements OnInit, AfterViewInit { constructor( private authService: AuthService, private formBuilder: FormBuilder, + private router: Router ) { } ngOnInit(): void { this.keyForm = this.formBuilder.group({ key: ['', Validators.required], }); - this.passphraseForm = this.formBuilder.group({ - passphrase: ['', Validators.required], + this.stateForm = this.formBuilder.group({ + state: '', }); if (this.authService.privateKey !== undefined ) { this.authService.setKey(this.authService.privateKey).then(r => { @@ -37,23 +39,7 @@ export class AuthComponent implements OnInit, AfterViewInit { } } - ngAfterViewInit(): void { - console.log(window.location); - const xhr = new XMLHttpRequest(); - xhr.responseType = 'text'; - xhr.open('GET', window.location.origin + '/privatekey.asc'); - xhr.onload = (e) => { - if (xhr.status !== 200) { - console.warn('failed to autoload private key ciphertext'); - return; - } - (document.getElementById('privateKeyAsc') as HTMLInputElement).value = xhr.responseText; - }; - xhr.send(); - } - get keyFormStub(): any { return this.keyForm.controls; } - get passphraseFormStub(): any { return this.passphraseForm.controls; } onSubmit(): void { this.submitted = true; @@ -66,8 +52,10 @@ export class AuthComponent implements OnInit, AfterViewInit { } login(): void { - // if (this.passphraseForm.invalid) { return; } - this.authService.login(); + const loginStatus = this.authService.login(); + if (loginStatus) { + this.router.navigate(['/home']); + } } switchWindows(): void { diff --git a/src/assets/js/hoba-pgp.js b/src/assets/js/hoba-pgp.js index a4ca895..f987bb7 100644 --- a/src/assets/js/hoba-pgp.js +++ b/src/assets/js/hoba-pgp.js @@ -13,12 +13,10 @@ export async function signChallenge(challenge, realm, origin, keyStore) { const a_nonce = btoa(String.fromCharCode.apply(null, nonce_array)); const a_challenge = btoa(challenge); const message = hobaToSign(a_nonce, a_kid, a_challenge, realm, origin, alg); - console.debug('message to sign', challenge, realm, origin, message); const signature = await keyStore.sign(message); const a_signature = btoa(signature); const result = hobaResult(a_nonce, a_kid, a_challenge, a_signature); - console.debug('result', result); return result; } diff --git a/src/assets/js/hoba.js b/src/assets/js/hoba.js index f7dc83e..f851f77 100644 --- a/src/assets/js/hoba.js +++ b/src/assets/js/hoba.js @@ -19,9 +19,7 @@ export function hobaParseChallengeHeader(s) { var auth_kv = auth_pairs[i].split(/^([^=]+)="(.+)"/); auth_values[auth_kv[1]] = auth_kv[2]; } - console.debug('challenge b64', auth_values['challenge']); const challenge_bytes = atob(auth_values['challenge']); - console.debug('challenge bytes', challenge_bytes); return { challenge: challenge_bytes, diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index d8cde3d..419f1e3 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,5 +1,6 @@ export const environment = { production: true, + cicAuthUrl: 'http://localhost:4444', cicMetaUrl: 'http://localhost:63380', publicKeysUrl: 'http://localhost:8000', cicCacheUrl: 'http://localhost:63313', diff --git a/src/environments/environment.ts b/src/environments/environment.ts index d9e69e7..00fca99 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -4,6 +4,7 @@ export const environment = { production: false, + cicAuthUrl: 'http://localhost:4444', cicMetaUrl: 'http://localhost:63380', publicKeysUrl: 'http://localhost:8000', cicCacheUrl: 'http://localhost:63313',