Compare commits
13 Commits
master
...
spencer/ce
Author | SHA1 | Date | |
---|---|---|---|
|
8e96b070a5 | ||
|
159cedc86e | ||
|
f494a32e20 | ||
|
63cff19bae | ||
|
53ed56460c | ||
|
632669ddf4 | ||
|
ad3767017b | ||
|
a9ad7012d8 | ||
|
8c08607a3a | ||
|
2116a55549 | ||
|
94da4baceb | ||
|
80815192f1 | ||
|
d72629921a |
@ -28,7 +28,7 @@ interface MutableKeyStore extends KeyStore {
|
|||||||
removePublicKeyForId(keyId: string): any;
|
removePublicKeyForId(keyId: string): any;
|
||||||
removePublicKey(publicKey: any): any;
|
removePublicKey(publicKey: any): any;
|
||||||
clearKeysInKeyring(): void;
|
clearKeysInKeyring(): void;
|
||||||
sign(plainText: string): Promise<any>;
|
sign(plainText: string, passphrase: string): Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MutablePgpKeyStore implements MutableKeyStore {
|
class MutablePgpKeyStore implements MutableKeyStore {
|
||||||
@ -150,11 +150,10 @@ class MutablePgpKeyStore implements MutableKeyStore {
|
|||||||
keyring.clear();
|
keyring.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
async sign(plainText): Promise<any> {
|
async sign(plainText: string, passphrase: string): Promise<any> {
|
||||||
const privateKey = this.getPrivateKey();
|
const privateKey = this.getPrivateKey();
|
||||||
if (!privateKey.isDecrypted()) {
|
if (!privateKey.isDecrypted()) {
|
||||||
const password = window.prompt('password');
|
await privateKey.decrypt(passphrase);
|
||||||
await privateKey.decrypt(password);
|
|
||||||
}
|
}
|
||||||
const opts = {
|
const opts = {
|
||||||
message: openpgp.message.fromText(plainText),
|
message: openpgp.message.fromText(plainText),
|
||||||
|
@ -13,6 +13,7 @@ import { HttpError, rejectBody } from '@app/_helpers/global-error-handler';
|
|||||||
})
|
})
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
sessionToken: any;
|
sessionToken: any;
|
||||||
|
privateKey: any;
|
||||||
mutableKeyStore: MutableKeyStore;
|
mutableKeyStore: MutableKeyStore;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -30,6 +31,7 @@ export class AuthService {
|
|||||||
this.sessionToken = sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'));
|
this.sessionToken = sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'));
|
||||||
}
|
}
|
||||||
if (localStorage.getItem(btoa('CICADA_PRIVATE_KEY'))) {
|
if (localStorage.getItem(btoa('CICADA_PRIVATE_KEY'))) {
|
||||||
|
this.privateKey = localStorage.getItem(btoa('CICADA_PRIVATE_KEY'));
|
||||||
await this.mutableKeyStore.importPrivateKey(localStorage.getItem(btoa('CICADA_PRIVATE_KEY')));
|
await this.mutableKeyStore.importPrivateKey(localStorage.getItem(btoa('CICADA_PRIVATE_KEY')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,21 +96,32 @@ export class AuthService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async passwordLogin(password: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const o = await this.getChallenge();
|
||||||
|
await this.loginResponse(o, password);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
this.loggingService.sendErrorLevelMessage(
|
||||||
|
`Login challenge failed: Error ${error.status} - ${error.statusText}`,
|
||||||
|
this,
|
||||||
|
{ error }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
async login(): Promise<boolean> {
|
async login(): Promise<boolean> {
|
||||||
if (this.sessionToken !== undefined) {
|
if (this.sessionToken !== undefined) {
|
||||||
try {
|
try {
|
||||||
const response: boolean = await this.getWithToken();
|
this.getWithToken();
|
||||||
return response === true;
|
return true;
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
this.loggingService.sendErrorLevelMessage('Login token failed', this, { error: e });
|
this.loggingService.sendErrorLevelMessage(
|
||||||
}
|
`Login token failed: Error ${error.status} - ${error.statusText}`,
|
||||||
} else {
|
this,
|
||||||
try {
|
{ error }
|
||||||
const o = await this.getChallenge();
|
);
|
||||||
const response: boolean = await this.loginResponse(o);
|
|
||||||
return response === true;
|
|
||||||
} catch (e) {
|
|
||||||
this.loggingService.sendErrorLevelMessage('Login challenge failed', this, { error: e });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -149,23 +162,18 @@ export class AuthService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loginView(): void {
|
|
||||||
document.getElementById('one').style.display = 'none';
|
|
||||||
document.getElementById('two').style.display = 'block';
|
|
||||||
this.setState('Click button to log in with PGP key ' + this.mutableKeyStore.getPrivateKeyId());
|
|
||||||
}
|
|
||||||
|
|
||||||
async setKey(privateKeyArmored): Promise<boolean> {
|
async setKey(privateKeyArmored): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const isValidKeyCheck = await this.mutableKeyStore.isValidKey(privateKeyArmored);
|
const isValidKeyCheck = await this.mutableKeyStore.isValidKey(privateKeyArmored);
|
||||||
if (!isValidKeyCheck) {
|
if (!isValidKeyCheck) {
|
||||||
throw Error('The private key is invalid');
|
throw Error('The private key is invalid');
|
||||||
}
|
}
|
||||||
// TODO leaving this out for now.
|
const isEncryptedKeyCheck = await this.mutableKeyStore.isEncryptedPrivateKey(
|
||||||
// const isEncryptedKeyCheck = await this.mutableKeyStore.isEncryptedPrivateKey(privateKeyArmored);
|
privateKeyArmored
|
||||||
// if (!isEncryptedKeyCheck) {
|
);
|
||||||
// throw Error('The private key doesn\'t have a password!');
|
if (!isEncryptedKeyCheck) {
|
||||||
// }
|
throw Error('The private key does not have a password!');
|
||||||
|
}
|
||||||
const key = await this.mutableKeyStore.importPrivateKey(privateKeyArmored);
|
const key = await this.mutableKeyStore.importPrivateKey(privateKeyArmored);
|
||||||
localStorage.setItem(btoa('CICADA_PRIVATE_KEY'), privateKeyArmored);
|
localStorage.setItem(btoa('CICADA_PRIVATE_KEY'), privateKeyArmored);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -179,7 +187,6 @@ export class AuthService {
|
|||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.loginView();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<h1 class="text-white">CICADA</h1>
|
<h1 class="text-white">CICADA</h1>
|
||||||
</a>
|
</a>
|
||||||
</mat-card-title>
|
</mat-card-title>
|
||||||
<div id="one" style="display: block" class="card-body p-4">
|
<div id="one" style="display: block" class="card-body p-4 align-items-center">
|
||||||
|
|
||||||
<div class="text-center w-75 m-auto">
|
<div class="text-center w-75 m-auto">
|
||||||
<h4 class="text-dark-50 text-center font-weight-bold">Add Private Key</h4>
|
<h4 class="text-dark-50 text-center font-weight-bold">Add Private Key</h4>
|
||||||
@ -19,13 +19,13 @@
|
|||||||
<mat-label>Private Key</mat-label>
|
<mat-label>Private Key</mat-label>
|
||||||
<textarea matInput style="height: 30rem" formControlName="key" placeholder="Enter your private key..."
|
<textarea matInput style="height: 30rem" formControlName="key" placeholder="Enter your private key..."
|
||||||
[errorStateMatcher]="matcher"></textarea>
|
[errorStateMatcher]="matcher"></textarea>
|
||||||
<div *ngIf="submitted && keyFormStub.key.errors" class="invalid-feedback">
|
<div *ngIf="keyFormSubmitted && keyFormStub.key.errors" class="invalid-feedback">
|
||||||
<mat-error *ngIf="keyFormStub.key.errors.required">Private Key is required.</mat-error>
|
<mat-error *ngIf="keyFormStub.key.errors.required">Private Key is required.</mat-error>
|
||||||
</div>
|
</div>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<button mat-raised-button matRipple color="primary" type="submit" [disabled]="loading">
|
<button mat-raised-button matRipple color="primary" type="submit" [disabled]="keyFormLoading">
|
||||||
<span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
|
<span *ngIf="keyFormLoading" class="spinner-border spinner-border-sm mr-1"></span>
|
||||||
Add Key
|
Add Key
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@ -33,6 +33,43 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="two" style="display: none" class="card-body p-4 align-items-center">
|
<div id="two" style="display: none" class="card-body p-4 align-items-center">
|
||||||
|
|
||||||
|
<div class="text-center w-75 m-auto">
|
||||||
|
<h4 id="passwordState" class="text-dark-50 text-center font-weight-bold"></h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="center-items">
|
||||||
|
<form [formGroup]="passwordForm" (ngSubmit)="onPasswordInput()">
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Password</mat-label>
|
||||||
|
<input matInput type="password" formControlName="password" placeholder="Enter your private key password..."
|
||||||
|
[errorStateMatcher]="matcher">
|
||||||
|
<div *ngIf="passwordForm && passwordFormStub.password.errors" class="invalid-feedback">
|
||||||
|
<mat-error *ngIf="passwordFormStub.password.errors.required">Private Key password is required.</mat-error>
|
||||||
|
</div>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<button id="passwordLoginButton" mat-raised-button matRipple color="primary" type="submit" class="ml-3" [disabled]="passwordFormLoading">
|
||||||
|
<span *ngIf="passwordFormLoading" class="spinner-border spinner-border-sm mr-1"></span>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-12 text-center">
|
||||||
|
<p class="text-muted">Change private key?
|
||||||
|
<a (click)="keyInput()" class="text-muted ml-1">
|
||||||
|
<b>Enter private key</b>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div> <!-- end col-->
|
||||||
|
</div>
|
||||||
|
<!-- end row -->
|
||||||
|
</div>
|
||||||
|
<div id="three" style="display: none" class="card-body p-4 align-items-center">
|
||||||
|
|
||||||
<div class="text-center w-75 m-auto">
|
<div class="text-center w-75 m-auto">
|
||||||
<h4 id="state" class="text-dark-50 text-center font-weight-bold"></h4>
|
<h4 id="state" class="text-dark-50 text-center font-weight-bold"></h4>
|
||||||
<button mat-raised-button matRipple color="primary" type="submit" (click)="login()"> Login </button>
|
<button mat-raised-button matRipple color="primary" type="submit" (click)="login()"> Login </button>
|
||||||
@ -40,7 +77,11 @@
|
|||||||
|
|
||||||
<div class="row mt-3">
|
<div class="row mt-3">
|
||||||
<div class="col-12 text-center">
|
<div class="col-12 text-center">
|
||||||
<p class="text-muted">Change private key? <a (click)="switchWindows()" class="text-muted ml-1"><b>Enter private key</b></a></p>
|
<p class="text-muted">Change private key?
|
||||||
|
<a (click)="keyInput()" class="text-muted ml-1">
|
||||||
|
<b>Enter private key</b>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
</div> <!-- end col-->
|
</div> <!-- end col-->
|
||||||
</div>
|
</div>
|
||||||
<!-- end row -->
|
<!-- end row -->
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
import { CustomErrorStateMatcher } from '@app/_helpers';
|
import { CustomErrorStateMatcher } from '@app/_helpers';
|
||||||
import { AuthService } from '@app/_services';
|
import { AuthService } from '@app/_services';
|
||||||
@ -12,68 +12,126 @@ import { Router } from '@angular/router';
|
|||||||
})
|
})
|
||||||
export class AuthComponent implements OnInit {
|
export class AuthComponent implements OnInit {
|
||||||
keyForm: FormGroup;
|
keyForm: FormGroup;
|
||||||
submitted: boolean = false;
|
|
||||||
loading: boolean = false;
|
|
||||||
matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher();
|
matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher();
|
||||||
|
keyFormSubmitted: boolean = false;
|
||||||
|
keyFormLoading: boolean = false;
|
||||||
|
passwordForm: FormGroup;
|
||||||
|
passwordFormSubmitted: boolean = false;
|
||||||
|
passwordFormLoading: boolean = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private router: Router
|
private router: Router,
|
||||||
|
private cdr: ChangeDetectorRef
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
this.keyForm = this.formBuilder.group({
|
this.keyForm = this.formBuilder.group({
|
||||||
key: ['', Validators.required],
|
key: ['', Validators.required],
|
||||||
});
|
});
|
||||||
await this.authService.init();
|
this.passwordForm = this.formBuilder.group({
|
||||||
// if (this.authService.privateKey !== undefined) {
|
password: ['', Validators.required],
|
||||||
// const setKey = await this.authService.setKey(this.authService.privateKey);
|
});
|
||||||
// }
|
if (this.authService.privateKey !== undefined) {
|
||||||
// }
|
const setKey = await this.authService.setKey(this.authService.privateKey);
|
||||||
|
if (setKey) {
|
||||||
|
this.passwordInput();
|
||||||
|
}
|
||||||
|
if (setKey && this.authService.sessionToken !== undefined) {
|
||||||
|
this.loginView();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get keyFormStub(): any {
|
get keyFormStub(): any {
|
||||||
return this.keyForm.controls;
|
return this.keyForm.controls;
|
||||||
}
|
}
|
||||||
|
get passwordFormStub(): any {
|
||||||
|
return this.passwordForm.controls;
|
||||||
|
}
|
||||||
|
|
||||||
async onSubmit(): Promise<void> {
|
async onSubmit(): Promise<void> {
|
||||||
this.submitted = true;
|
this.keyFormSubmitted = true;
|
||||||
|
|
||||||
if (this.keyForm.invalid) {
|
if (this.keyForm.invalid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loading = true;
|
this.keyFormLoading = true;
|
||||||
await this.authService.setKey(this.keyFormStub.key.value);
|
const keySetup = await this.authService.setKey(this.keyFormStub.key.value);
|
||||||
this.loading = false;
|
if (keySetup) {
|
||||||
|
this.passwordInput();
|
||||||
|
}
|
||||||
|
this.keyFormLoading = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
async onPasswordInput(): Promise<void> {
|
||||||
|
this.passwordFormSubmitted = true;
|
||||||
|
|
||||||
|
if (this.passwordForm.invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.passwordFormLoading = true;
|
||||||
|
const passwordLogin = await this.authService.passwordLogin(
|
||||||
|
this.passwordFormStub.password.value
|
||||||
|
);
|
||||||
|
if (passwordLogin) {
|
||||||
|
this.loginView();
|
||||||
|
}
|
||||||
|
this.passwordFormLoading = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
login(): void {
|
login(): void {
|
||||||
// TODO check if we have privatekey
|
if (this.authService.sessionToken === undefined) {
|
||||||
// Send us to home if we have a private key
|
this.passwordInput();
|
||||||
// talk to meta somehow
|
}
|
||||||
// in the error interceptor if 401/403 handle it
|
const loginStatus = this.authService.login();
|
||||||
// if 200 go /home
|
if (loginStatus) {
|
||||||
if (this.authService.getPrivateKey()) {
|
|
||||||
this.router.navigate(['/home']);
|
this.router.navigate(['/home']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switchWindows(): void {
|
keyInput(): void {
|
||||||
this.authService.sessionToken = undefined;
|
this.authService.sessionToken = undefined;
|
||||||
const divOne: HTMLElement = document.getElementById('one');
|
this.switchWindows(true, false, false);
|
||||||
const divTwo: HTMLElement = document.getElementById('two');
|
|
||||||
this.toggleDisplay(divOne);
|
|
||||||
this.toggleDisplay(divTwo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleDisplay(element: any): void {
|
passwordInput(): void {
|
||||||
|
this.authService.sessionToken = undefined;
|
||||||
|
this.switchWindows(false, true, false);
|
||||||
|
this.setPasswordState(
|
||||||
|
'Enter Password to log in with PGP key ' + this.authService.mutableKeyStore.getPrivateKeyId()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
loginView(): void {
|
||||||
|
this.switchWindows(false, false, true);
|
||||||
|
this.authService.setState('Click button to log in');
|
||||||
|
}
|
||||||
|
|
||||||
|
switchWindows(divOneStatus: boolean, divTwoStatus: boolean, divThreeStatus: boolean): void {
|
||||||
|
const divOne = document.getElementById('one');
|
||||||
|
const divTwo = document.getElementById('two');
|
||||||
|
const divThree = document.getElementById('three');
|
||||||
|
this.toggleDisplay(divOne, divOneStatus);
|
||||||
|
this.toggleDisplay(divTwo, divTwoStatus);
|
||||||
|
this.toggleDisplay(divThree, divThreeStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDisplay(element: any, active: boolean): void {
|
||||||
const style: string = window.getComputedStyle(element).display;
|
const style: string = window.getComputedStyle(element).display;
|
||||||
if (style === 'block') {
|
if (active) {
|
||||||
element.style.display = 'none';
|
|
||||||
} else {
|
|
||||||
element.style.display = 'block';
|
element.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
element.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setPasswordState(s): void {
|
||||||
|
document.getElementById('passwordState').innerHTML = s;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { hobaResult, hobaToSign } from '@src/assets/js/hoba.js';
|
|||||||
|
|
||||||
const alg = '969';
|
const alg = '969';
|
||||||
|
|
||||||
export async function signChallenge(challenge, realm, origin, keyStore) {
|
export async function signChallenge(challenge, realm, origin, keyStore, password) {
|
||||||
const fingerprint = keyStore.getFingerprint();
|
const fingerprint = keyStore.getFingerprint();
|
||||||
const nonce_array = new Uint8Array(32);
|
const nonce_array = new Uint8Array(32);
|
||||||
crypto.getRandomValues(nonce_array);
|
crypto.getRandomValues(nonce_array);
|
||||||
@ -14,7 +14,7 @@ export async function signChallenge(challenge, realm, origin, keyStore) {
|
|||||||
const a_challenge = btoa(challenge);
|
const a_challenge = btoa(challenge);
|
||||||
const message = hobaToSign(a_nonce, a_kid, a_challenge, realm, origin, alg);
|
const message = hobaToSign(a_nonce, a_kid, a_challenge, realm, origin, alg);
|
||||||
|
|
||||||
const signature = await keyStore.sign(message);
|
const signature = await keyStore.sign(message, password);
|
||||||
const a_signature = btoa(signature);
|
const a_signature = btoa(signature);
|
||||||
|
|
||||||
const result = hobaResult(a_nonce, a_kid, a_challenge, a_signature);
|
const result = hobaResult(a_nonce, a_kid, a_challenge, a_signature);
|
||||||
|
@ -65,6 +65,12 @@ footer.footer { color: black; }
|
|||||||
left: -9999px;
|
left: -9999px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.center-items {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
#sidebar {
|
#sidebar {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
position: -webkit-sticky;
|
position: -webkit-sticky;
|
||||||
@ -207,6 +213,11 @@ a[data-toggle="collapse"] { position: relative; }
|
|||||||
|
|
||||||
.mat-column-select { overflow: initial; }
|
.mat-column-select { overflow: initial; }
|
||||||
|
|
||||||
|
#passwordLoginButton {
|
||||||
|
height: 3.0rem;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
button { height: 2.5rem; }
|
button { height: 2.5rem; }
|
||||||
|
|
||||||
.badge-pill { width: 5rem; }
|
.badge-pill { width: 5rem; }
|
||||||
|
Loading…
Reference in New Issue
Block a user