From 8e2662809254c314174f6000d79f02f5e0be9b36 Mon Sep 17 00:00:00 2001 From: Blair Vanderlugt Date: Sat, 20 Mar 2021 19:23:50 -0700 Subject: [PATCH] private key loading error handling --- .../_interceptors/http-config.interceptor.ts | 11 +++-- src/app/_pgp/pgp-key-store.ts | 26 ++++++---- src/app/_services/auth.service.ts | 47 ++++++++++++++----- src/app/app.component.ts | 4 +- src/app/app.module.ts | 4 +- 5 files changed, 63 insertions(+), 29 deletions(-) diff --git a/src/app/_interceptors/http-config.interceptor.ts b/src/app/_interceptors/http-config.interceptor.ts index 72b4295..d10de43 100644 --- a/src/app/_interceptors/http-config.interceptor.ts +++ b/src/app/_interceptors/http-config.interceptor.ts @@ -19,11 +19,12 @@ export class HttpConfigInterceptor implements HttpInterceptor { request = request.clone({headers: request.headers.set('Authorization', 'Bearer ' + token)}); } - if (!request.headers.has('Content-Type')) { - request = request.clone({headers: request.headers.set('Content-Type', 'text/plain')}); - } - - request = request.clone({headers: request.headers.set('Accept', 'application/json')}); + // I think this is the default behavior + // if (!request.headers.has('Content-Type')) { + // request = request.clone({headers: request.headers.set('Content-Type', 'application/json')}); + // } + + // request = request.clone({headers: request.headers.set('Accept', 'application/json')}); return next.handle(request); } } diff --git a/src/app/_pgp/pgp-key-store.ts b/src/app/_pgp/pgp-key-store.ts index ae8f03f..170be22 100644 --- a/src/app/_pgp/pgp-key-store.ts +++ b/src/app/_pgp/pgp-key-store.ts @@ -1,11 +1,12 @@ import { KeyStore } from 'cic-client-meta'; -const openpgp = require('openpgp'); +// const openpgp = require('openpgp'); //TODO should we put this on the mutalble key store object +import * as openpgp from 'openpgp'; const keyring = new openpgp.Keyring(); interface MutableKeyStore extends KeyStore { - loadKeyring(): Promise; + loadKeyring(): void; importKeyPair(publicKey: any, privateKey: any): Promise; - importPublicKey(publicKey: any): Promise; + importPublicKey(publicKey: any): void; importPrivateKey(privateKey: any): Promise; getPublicKeys(): Array; getTrustedKeys(): Array; @@ -13,7 +14,7 @@ interface MutableKeyStore extends KeyStore { getEncryptKeys(): Array; getPrivateKeys(): Array; getPrivateKey(): any; - isValidKey(key: any): boolean; + isValidKey(key: any): Promise; getFingerprint(): string; getKeyId(key: any): string; getPrivateKeyId(): string; @@ -43,8 +44,8 @@ class MutablePgpKeyStore implements MutableKeyStore{ await keyring.privateKeys.importKey(privateKey); } - async importPublicKey(publicKey: any): Promise { - await keyring.publicKeys.importKey(publicKey); + importPublicKey(publicKey: any): void { + keyring.publicKeys.importKey(publicKey) } async importPrivateKey(privateKey: any): Promise { @@ -75,8 +76,14 @@ class MutablePgpKeyStore implements MutableKeyStore{ return keyring.privateKeys.keys[0]; } - isValidKey(key): boolean { - return typeof key === openpgp.Key; + async isValidKey(key): Promise { + // There is supposed to be an opengpg.readKey() method but I can't find it? + const _key = await openpgp.key.readArmored(key) + if (_key.err) { + return false + } else { + return true + } } getFingerprint(): string { @@ -88,7 +95,8 @@ class MutablePgpKeyStore implements MutableKeyStore{ } getPrivateKeyId(): string { - return keyring.privateKeys.keys[0].getKeyId().toHex(); + // TODO is there a library that comes with angular for doing this? + return keyring.privateKeys && keyring.privateKeys.keys[0] && keyring.privateKeys.keys[0].getKeyId().toHex(); } getKeysForId(keyId: string): Array { diff --git a/src/app/_services/auth.service.ts b/src/app/_services/auth.service.ts index c1201a1..bd907bd 100644 --- a/src/app/_services/auth.service.ts +++ b/src/app/_services/auth.service.ts @@ -6,8 +6,9 @@ import {LoggingService} from '@app/_services/logging.service'; import {HttpWrapperService} from '@app/_services/http-wrapper.service'; import {MutableKeyStore, MutablePgpKeyStore} from '@app/_pgp'; import {ErrorDialogService} from '@app/_services/error-dialog.service'; -import {first} from 'rxjs/operators'; -import { HttpClient } from '@angular/common/http'; +import {catchError, first, tap} from 'rxjs/operators'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { throwError } from 'rxjs'; @Injectable({ providedIn: 'root' @@ -24,6 +25,7 @@ export class AuthService { private loggingService: LoggingService, private errorDialogService: ErrorDialogService ) { + // TODO setting these together shoulds be atomic if (sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'))) { this.sessionToken = sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN')); } @@ -121,13 +123,17 @@ export class AuthService { async setKey(privateKeyArmored): Promise { try { - await this.mutableKeyStore.importPrivateKey(privateKeyArmored); + const isValidKeyCheck = await this.mutableKeyStore.isValidKey(privateKeyArmored) + if (!isValidKeyCheck) { + throw Error("The private key is invalid") + } + const key = await this.mutableKeyStore.importPrivateKey(privateKeyArmored); localStorage.setItem(btoa('CICADA_PRIVATE_KEY'), privateKeyArmored); } catch (err) { this.loggingService.sendErrorLevelMessage('Failed setting key', this, {error: err}); + // TODO use a global error handler here this.errorDialogService.openDialog({ - message: `Failed to set key, Enter your private key again. Reason: ${err.error.message || err.statusText}`, - status: err.status + message: `Failed to set key: ${err.message || err.statusText}`, }); return false; } @@ -146,14 +152,33 @@ export class AuthService { return trustedUsers; } - async getPublicKeys(): Promise { - this.httpClient.get(`${environment.publicKeysUrl}`).pipe(first()).subscribe(async res => { - await this.mutableKeyStore.importPublicKey(res); - }, error => { - this.loggingService.sendErrorLevelMessage('There was an error fetching public keys!', this, {error}); - }); + getPublicKeys() { + return this.httpClient.get(`${environment.publicKeysUrl}`, {responseType: 'text'}) + .pipe(tap( + data => { }, + error => { this.handleError(error, 'Unable to load trusted public keys.') } + )) + } + + async getPrivateKeys(): Promise { if (this.privateKey !== undefined) { await this.mutableKeyStore.importPrivateKey(this.privateKey); } } + + // TODO this is from the docs and for reference. Move it somewhere better. + private handleError(error: HttpErrorResponse, customMessage: string) { + if (error.error instanceof ErrorEvent) { + // A client-side or network error occurred. Handle it accordingly. + console.error('An error occurred:', error.error.message); + } else { + // The backend returned an unsuccessful response code. + // The response body may contain clues as to what went wrong. + console.error( + `Backend returned code ${error.status}, ` + + `body was: ${JSON.stringify(error.error)}`); + } + // Return an observable with a user-facing error message. + return throwError(customMessage); + } } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index e1421c6..485edd1 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -21,8 +21,8 @@ export class AppComponent { ) { (async () => { await this.authService.mutableKeyStore.loadKeyring(); - await this.authService.getPublicKeys(); - this.loggingService.sendInfoLevelMessage(await this.tokenService.getTokens()); + this.authService.getPublicKeys().subscribe(this.authService.mutableKeyStore.importPublicKey) + // this.loggingService.sendInfoLevelMessage(await this.tokenService.getTokens()); })(); this.mediaQuery.addListener(this.onResize); this.onResize(this.mediaQuery); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 82af3c2..4be5742 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -44,8 +44,8 @@ import {MutablePgpKeyStore} from '@app/_pgp'; GlobalErrorHandler, { provide: ErrorHandler, useClass: GlobalErrorHandler }, { provide: HTTP_INTERCEPTORS, useClass: HttpConfigInterceptor, multi: true }, - { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }, - { provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true }, + // { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }, + // { provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true }, ], bootstrap: [AppComponent] })