added wizard steps to validate imported encryption keys and validate generated encryption keys.
This commit is contained in:
parent
62b795319e
commit
be93fef13c
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
|
Loading…
Reference in New Issue