added wizard steps to validate imported encryption keys and validate generated encryption keys.

This commit is contained in:
Jason Kulatunga 2022-10-17 22:21:38 -07:00
parent 62b795319e
commit be93fef13c
3 changed files with 207 additions and 70 deletions

View File

@ -2,7 +2,7 @@
<div class="container">
<div class="row w-100">
<div class="col-sm-12">
<h2 class="az-content-title mg-t-40">Security Manager</h2>
<h2 class="az-content-title mg-t-20">Security Manager</h2>
<p class="mb-5">
Before you use Fasten you'll need to import or generate a new encryption key.
<br/>
@ -18,55 +18,12 @@
<div *ngIf="cryptoPanel == CryptoPanelType.Generate" class="wizard clearfix">
<div class="steps clearfix">
<ul role="tablist">
<li role="tab" class="first current" aria-disabled="false" aria-selected="true">
<li role="tab" class="first" [ngClass]="{'current': currentStep == 1, 'disabled': currentStep != 1}">
<a aria-controls="wizard1-p-0">
<span class="current-info audible">current step: </span>
<span class="number">1</span>
<span class="title">Download</span></a>
</li>
<li role="tab" class="disabled last" aria-disabled="true">
<a aria-controls="wizard1-p-2">
<span class="number">2</span>
<span class="title">Validate</span>
</a>
</li>
</ul>
</div>
<div class="content clearfix">
<h3 tabindex="-1" class="title current">Generate an encryption key</h3>
<section role="tabpanel" aria-labelledby="wizard1-h-0" class="body current" aria-hidden="false">
<p class="mg-b-0">
Fasten has generated an encryption key for you. You can use this encryption key to decode your medical records on this browser, and other devices.
<br/>
This is the only time the encryption key will be available to view, copy or download. We recommend downloading this key and storing the file in a secure location.
<br/>
You can reset your encryption key at any time, however any previously encrypted data will no longer be accessible.
</p>
<pre><code [highlight]="currentCryptoConfig | json"></code></pre>
<div class="row row-xs wd-xl-80p">
<div class="col-sm-6 col-md-3">
<a [href]="generateCryptoConfigUrl" [download]="generateCryptoConfigFilename" class="btn btn-indigo btn-rounded btn-block">Download Encryption Key</a>
</div>
</div>
</section>
</div>
</div>
<div *ngIf="cryptoPanel == CryptoPanelType.Import" class="wizard clearfix">
<div class="steps clearfix">
<ul role="tablist">
<li role="tab" class="first current" aria-disabled="false" aria-selected="true">
<a aria-controls="wizard1-p-0">
<span class="current-info audible">current step: </span>
<span class="number">1</span>
<span class="title">Import</span></a>
</li>
<li role="tab" class="disabled last" aria-disabled="true">
<li role="tab" class="last" [ngClass]="{'current': currentStep == 2, 'disabled': currentStep != 2}">
<a aria-controls="wizard1-p-2">
<span class="number">2</span>
<span class="title">Validate</span>
@ -75,25 +32,115 @@
</ul>
</div>
<div class="content clearfix">
<h3 tabindex="-1" class="title current">Import existing encryption key</h3>
<section role="tabpanel" aria-labelledby="wizard1-h-0" class="body current" aria-hidden="false">
<p class="mg-b-0">
<p class="mg-b-10">
Fasten was unable to find your encryption key on this device, and has detected encrypted data in your database.
<br/>
You will need to provide your encryption key to access your health records.
</p>
<ng-container [ngSwitch]="currentStep">
<ng-container *ngSwitchCase="1">
<h3 tabindex="-1" class="title current">Generate an encryption key</h3>
<section role="tabpanel" aria-labelledby="wizard1-h-0" class="body current" aria-hidden="false">
<p class="mg-b-0">
Fasten has generated an encryption key for you. You can use this encryption key to decode your medical records on this browser, and other devices.
<br/>
<div class="row">
<div class="col-4">
<div class="custom-file">
<input type="file" class="custom-file-input" id="customFile" (change)="openFileHandler($event.target.files)" accept="application/json">
<label class="custom-file-label" for="customFile">Choose file</label>
This is the only time the encryption key will be available to view, copy or download. We recommend downloading this key and storing the file in a secure location.
<br/>
You can reset your encryption key at any time, however any previously encrypted data will no longer be accessible.
</p>
<pre><code [highlight]="currentCryptoConfig | json"></code></pre>
<div class="row row-xs wd-xl-80p">
<div class="col-sm-6 col-md-3">
<a [href]="generateCryptoConfigUrl" [download]="generateCryptoConfigFilename" class="btn btn-warning btn-rounded btn-block">Download Encryption Key</a>
</div>
</div>
</div>
</div>
</section>
</ng-container>
<ng-container *ngSwitchCase="2">
<h3 tabindex="-1" class="title current">Validate your encryption key</h3>
<section role="tabpanel" aria-labelledby="wizard1-h-0" class="body current" aria-hidden="false">
<p class="mg-b-10">
Please select your encryption key (which you generated in the previous step) using the file input below.
<br/>
It'll be validated against your browser's encryption key to ensure fidelity.
</p>
</section>
<div class="row">
<div class="col-4">
<div class="custom-file">
<input type="file" class="custom-file-input" id="generateCustomFile" (change)="generateOpenFileHandler($event.target.files)" accept="application/json">
<label class="custom-file-label" for="generateCustomFile">Choose file</label>
<div *ngIf="generateCustomFileError" class="alert alert-danger">
{{generateCustomFileError}}
</div>
</div>
</div>
</div>
</section>
</ng-container>
</ng-container>
</div>
<div *ngIf="currentStep != lastStep" class="actions clearfix">
<ul role="menu" aria-label="Pagination">
<li class="disabled" aria-disabled="true"></li>
<li aria-disabled="false"><button class="btn btn-az-primary btn-block" (click)="nextHandler()" role="menuitem">Next</button></li>
</ul>
</div>
</div>
<div *ngIf="cryptoPanel == CryptoPanelType.Import" class="wizard clearfix">
<div class="steps clearfix">
<ul role="tablist">
<li role="tab" class="first" [ngClass]="{'current': currentStep == 1, 'disabled': currentStep != 1}">
<a aria-controls="wizard1-p-0">
<span class="number">1</span>
<span class="title">Import</span></a>
</li>
<li role="tab" class="last" [ngClass]="{'current': currentStep == 2, 'disabled': currentStep != 2}">
<a aria-controls="wizard1-p-2">
<span class="number">2</span>
<span class="title">Validate</span>
</a>
</li>
</ul>
</div>
<div class="content clearfix">
<ng-container [ngSwitch]="currentStep">
<ng-container *ngSwitchCase="1">
<h3 tabindex="-1" class="title current">Import existing encryption key</h3>
<section role="tabpanel" aria-labelledby="wizard1-h-0" class="body current" aria-hidden="false">
<p class="mg-b-10">
Fasten was unable to find your encryption key on this device, and has detected encrypted data in your database.
<br/>
You will need to provide your encryption key to access your health records.
</p>
<div class="row">
<div class="col-4">
<div class="custom-file">
<input type="file" class="custom-file-input" id="importCustomFile" (change)="importOpenFileHandler($event.target.files)" accept="application/json">
<label class="custom-file-label" for="importCustomFile">Choose file</label>
<div *ngIf="importCustomFileError" class="alert alert-danger">
{{importCustomFileError}}
</div>
</div>
</div>
</div>
</section>
</ng-container>
<ng-container *ngSwitchCase="2">
<h3 tabindex="-1" class="title current">Validate encryption key</h3>
<section role="tabpanel" aria-labelledby="wizard1-h-0" class="body current" aria-hidden="false">
<p class="mg-b-10">
Thank you for providing your encryption key. Fasten will attempt to decrypt your secured records with this key.
</p>
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
</section>
</ng-container>
</ng-container>
</div>
</div>

View File

@ -4,6 +4,8 @@ import {PouchdbCryptConfig, PouchdbCrypto} from '../../../lib/database/plugins/c
import {FastenDbService} from '../../services/fasten-db.service';
import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser';
import {Router} from '@angular/router';
import {ToastService} from '../../services/toast.service';
import {ToastNotification, ToastType} from '../../models/fasten/toast';
export enum CryptoPanelType {
Loading,
@ -24,7 +26,15 @@ export class EncryptionManagerComponent implements OnInit {
generateCryptoConfigUrl: SafeResourceUrl = ""
generateCryptoConfigFilename: string = ""
constructor(private fastenDbService: FastenDbService, private sanitizer: DomSanitizer, private router: Router) { }
generateCustomFileError: string = ""
importCustomFileError: string = ""
currentStep: number
lastStep: number
constructor(private fastenDbService: FastenDbService, private sanitizer: DomSanitizer, private router: Router, private toastService: ToastService) { }
ngOnInit(): void {
@ -39,8 +49,22 @@ export class EncryptionManagerComponent implements OnInit {
}
nextHandler() {
this.currentStep += 1
// if (!this.stepsService.isLastStep()) {
// this.stepsService.moveToNextStep();
// } else {
// this.onSubmit();
// }
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// Generate Wizard Methods
/////////////////////////////////////////////////////////////////////////////////////////////////
async showGenerateCryptoConfig(): Promise<PouchdbCryptConfig> {
this.cryptoPanel = CryptoPanelType.Generate
this.currentStep = 1
this.lastStep = 2
if(!this.currentCryptoConfig){
this.currentCryptoConfig = await PouchdbCrypto.CryptConfig(uuidv4(), this.fastenDbService.current_user)
await PouchdbCrypto.StoreCryptConfig(this.currentCryptoConfig) //store in indexdb
@ -50,17 +74,56 @@ export class EncryptionManagerComponent implements OnInit {
let currentCryptoConfigBlob = new Blob([JSON.stringify(this.currentCryptoConfig)], { type: 'application/json' });
this.generateCryptoConfigUrl = this.sanitizer.bypassSecurityTrustResourceUrl(window.URL.createObjectURL(currentCryptoConfigBlob));
this.generateCryptoConfigFilename = `fasten-${this.fastenDbService.current_user}.key.json`
}
return this.currentCryptoConfig
}
async showImportCryptoConfig(): Promise<any> {
this.cryptoPanel = CryptoPanelType.Import
generateOpenFileHandler(fileList: FileList) {
this.generateCustomFileError = ""
let file = fileList[0];
this.readFileContent(file)
.then((content) => {
let parsedCryptoConfig = JSON.parse(content) as PouchdbCryptConfig
//check if the parsed encryption key matches the currently set encryption key
if(parsedCryptoConfig.key == this.currentCryptoConfig.key &&
parsedCryptoConfig.username == this.currentCryptoConfig.username &&
parsedCryptoConfig.config == this.currentCryptoConfig.config){
return true
} else {
//throw an error & notify user
this.generateCustomFileError = "Crypto configuration file does not match"
throw new Error(this.generateCustomFileError)
}
})
.then(() => {
const toastNotification = new ToastNotification()
toastNotification.type = ToastType.Success
toastNotification.message = "Successfully validated & stored encryption key."
toastNotification.autohide = true
this.toastService.show(toastNotification)
//redirect user to dashboard
return this.router.navigate(['/dashboard']);
})
.catch(console.error)
}
openFileHandler(fileList: FileList) {
/////////////////////////////////////////////////////////////////////////////////////////////////
// Import Wizard Methods
/////////////////////////////////////////////////////////////////////////////////////////////////
async showImportCryptoConfig(): Promise<any> {
this.cryptoPanel = CryptoPanelType.Import
this.currentStep = 1
this.lastStep = 2
}
importOpenFileHandler(fileList: FileList) {
this.importCustomFileError = ""
let file = fileList[0];
this.readFileContent(file)
.then((content) => {
@ -70,13 +133,37 @@ export class EncryptionManagerComponent implements OnInit {
return PouchdbCrypto.StoreCryptConfig(cryptoConfig)
} else {
//throw an error & notify user
console.error("Invalid crypto configuration file")
this.importCustomFileError = "Invalid crypto configuration file"
throw new Error(this.importCustomFileError)
}
})
.then(() => {
//redirect user to dashboard
//go to step 2
this.currentStep = 2
//attempt to initialize pouchdb with specified crypto
this.fastenDbService.ResetDB()
return this.fastenDbService.GetSources()
})
.then(() => {
const toastNotification = new ToastNotification()
toastNotification.type = ToastType.Success
toastNotification.message = "Successfully validated & imported encryption key."
toastNotification.autohide = true
this.toastService.show(toastNotification)
return this.router.navigate(['/dashboard']);
})
.catch((err) => {
console.error(err)
//an error occurred while importing credential
const toastNotification = new ToastNotification()
toastNotification.type = ToastType.Error
toastNotification.message = "Provided encryption key does not match. Please try a different key"
toastNotification.autohide = false
this.toastService.show(toastNotification)
this.currentStep = 1
})
}

View File

@ -241,7 +241,10 @@ export class PouchdbRepository implements IDatabaseRepository {
// All functions below here will return the raw PouchDB responses, and may need to be wrapped in
// new ResourceFhir(result.doc)
///////////////////////////////////////////////////////////////////////////////////////
public ResetDB(){
this.pouchDb = null
this.encryptionInitComplete = false
}
// Get the active PouchDB instance. Throws an error if no PouchDB instance is
// available (ie, user has not yet been configured with call to .configureForUser()).
public async GetDB(skipEncryption: boolean = false): Promise<PouchDB.Database> {