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;
 | 
			
		||||
  removePublicKey(publicKey: any): any;
 | 
			
		||||
  clearKeysInKeyring(): void;
 | 
			
		||||
  sign(plainText: string): Promise<any>;
 | 
			
		||||
  sign(plainText: string, passphrase: string): Promise<any>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class MutablePgpKeyStore implements MutableKeyStore {
 | 
			
		||||
@ -150,11 +150,10 @@ class MutablePgpKeyStore implements MutableKeyStore {
 | 
			
		||||
    keyring.clear();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async sign(plainText): Promise<any> {
 | 
			
		||||
  async sign(plainText: string, passphrase: string): Promise<any> {
 | 
			
		||||
    const privateKey = this.getPrivateKey();
 | 
			
		||||
    if (!privateKey.isDecrypted()) {
 | 
			
		||||
      const password = window.prompt('password');
 | 
			
		||||
      await privateKey.decrypt(password);
 | 
			
		||||
      await privateKey.decrypt(passphrase);
 | 
			
		||||
    }
 | 
			
		||||
    const opts = {
 | 
			
		||||
      message: openpgp.message.fromText(plainText),
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,7 @@ import { HttpError, rejectBody } from '@app/_helpers/global-error-handler';
 | 
			
		||||
})
 | 
			
		||||
export class AuthService {
 | 
			
		||||
  sessionToken: any;
 | 
			
		||||
  privateKey: any;
 | 
			
		||||
  mutableKeyStore: MutableKeyStore;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
@ -30,6 +31,7 @@ export class AuthService {
 | 
			
		||||
      this.sessionToken = sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'));
 | 
			
		||||
    }
 | 
			
		||||
    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')));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -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> {
 | 
			
		||||
    if (this.sessionToken !== undefined) {
 | 
			
		||||
      try {
 | 
			
		||||
        const response: boolean = await this.getWithToken();
 | 
			
		||||
        return response === true;
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        this.loggingService.sendErrorLevelMessage('Login token failed', this, { error: e });
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      try {
 | 
			
		||||
        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 });
 | 
			
		||||
        this.getWithToken();
 | 
			
		||||
        return true;
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        this.loggingService.sendErrorLevelMessage(
 | 
			
		||||
          `Login token failed: Error ${error.status} - ${error.statusText}`,
 | 
			
		||||
          this,
 | 
			
		||||
          { error }
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    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> {
 | 
			
		||||
    try {
 | 
			
		||||
      const isValidKeyCheck = await this.mutableKeyStore.isValidKey(privateKeyArmored);
 | 
			
		||||
      if (!isValidKeyCheck) {
 | 
			
		||||
        throw Error('The private key is invalid');
 | 
			
		||||
      }
 | 
			
		||||
      // TODO leaving this out for now.
 | 
			
		||||
      // const isEncryptedKeyCheck = await this.mutableKeyStore.isEncryptedPrivateKey(privateKeyArmored);
 | 
			
		||||
      // if (!isEncryptedKeyCheck) {
 | 
			
		||||
      //   throw Error('The private key doesn\'t have a password!');
 | 
			
		||||
      // }
 | 
			
		||||
      const isEncryptedKeyCheck = await this.mutableKeyStore.isEncryptedPrivateKey(
 | 
			
		||||
        privateKeyArmored
 | 
			
		||||
      );
 | 
			
		||||
      if (!isEncryptedKeyCheck) {
 | 
			
		||||
        throw Error('The private key does not have a password!');
 | 
			
		||||
      }
 | 
			
		||||
      const key = await this.mutableKeyStore.importPrivateKey(privateKeyArmored);
 | 
			
		||||
      localStorage.setItem(btoa('CICADA_PRIVATE_KEY'), privateKeyArmored);
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
@ -179,7 +187,6 @@ export class AuthService {
 | 
			
		||||
      });
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    this.loginView();
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@
 | 
			
		||||
            <h1 class="text-white">CICADA</h1>
 | 
			
		||||
          </a>
 | 
			
		||||
        </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">
 | 
			
		||||
            <h4 class="text-dark-50 text-center font-weight-bold">Add Private Key</h4>
 | 
			
		||||
@ -19,13 +19,13 @@
 | 
			
		||||
              <mat-label>Private Key</mat-label>
 | 
			
		||||
              <textarea matInput style="height: 30rem" formControlName="key" placeholder="Enter your private key..."
 | 
			
		||||
                        [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>
 | 
			
		||||
              </div>
 | 
			
		||||
            </mat-form-field>
 | 
			
		||||
 | 
			
		||||
            <button mat-raised-button matRipple color="primary" type="submit" [disabled]="loading">
 | 
			
		||||
              <span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
 | 
			
		||||
            <button mat-raised-button matRipple color="primary" type="submit" [disabled]="keyFormLoading">
 | 
			
		||||
              <span *ngIf="keyFormLoading" class="spinner-border spinner-border-sm mr-1"></span>
 | 
			
		||||
              Add Key
 | 
			
		||||
            </button>
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,43 @@
 | 
			
		||||
        </div>
 | 
			
		||||
        <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">
 | 
			
		||||
            <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>
 | 
			
		||||
@ -40,7 +77,11 @@
 | 
			
		||||
 | 
			
		||||
          <div class="row mt-3">
 | 
			
		||||
            <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 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 { CustomErrorStateMatcher } from '@app/_helpers';
 | 
			
		||||
import { AuthService } from '@app/_services';
 | 
			
		||||
@ -12,68 +12,126 @@ import { Router } from '@angular/router';
 | 
			
		||||
})
 | 
			
		||||
export class AuthComponent implements OnInit {
 | 
			
		||||
  keyForm: FormGroup;
 | 
			
		||||
  submitted: boolean = false;
 | 
			
		||||
  loading: boolean = false;
 | 
			
		||||
  matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher();
 | 
			
		||||
  keyFormSubmitted: boolean = false;
 | 
			
		||||
  keyFormLoading: boolean = false;
 | 
			
		||||
  passwordForm: FormGroup;
 | 
			
		||||
  passwordFormSubmitted: boolean = false;
 | 
			
		||||
  passwordFormLoading: boolean = false;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private authService: AuthService,
 | 
			
		||||
    private formBuilder: FormBuilder,
 | 
			
		||||
    private router: Router
 | 
			
		||||
    private router: Router,
 | 
			
		||||
    private cdr: ChangeDetectorRef
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  async ngOnInit(): Promise<void> {
 | 
			
		||||
    this.keyForm = this.formBuilder.group({
 | 
			
		||||
      key: ['', Validators.required],
 | 
			
		||||
    });
 | 
			
		||||
    await this.authService.init();
 | 
			
		||||
    // if (this.authService.privateKey !== undefined) {
 | 
			
		||||
    //   const setKey = await this.authService.setKey(this.authService.privateKey);
 | 
			
		||||
    //   }
 | 
			
		||||
    // }
 | 
			
		||||
    this.passwordForm = this.formBuilder.group({
 | 
			
		||||
      password: ['', Validators.required],
 | 
			
		||||
    });
 | 
			
		||||
    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 {
 | 
			
		||||
    return this.keyForm.controls;
 | 
			
		||||
  }
 | 
			
		||||
  get passwordFormStub(): any {
 | 
			
		||||
    return this.passwordForm.controls;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async onSubmit(): Promise<void> {
 | 
			
		||||
    this.submitted = true;
 | 
			
		||||
    this.keyFormSubmitted = true;
 | 
			
		||||
 | 
			
		||||
    if (this.keyForm.invalid) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.loading = true;
 | 
			
		||||
    await this.authService.setKey(this.keyFormStub.key.value);
 | 
			
		||||
    this.loading = false;
 | 
			
		||||
    this.keyFormLoading = true;
 | 
			
		||||
    const keySetup = await this.authService.setKey(this.keyFormStub.key.value);
 | 
			
		||||
    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 {
 | 
			
		||||
    // TODO check if we have privatekey
 | 
			
		||||
    // Send us to home if we have a private key
 | 
			
		||||
    // talk to meta somehow
 | 
			
		||||
    // in the error interceptor if 401/403 handle it
 | 
			
		||||
    // if 200 go /home
 | 
			
		||||
    if (this.authService.getPrivateKey()) {
 | 
			
		||||
    if (this.authService.sessionToken === undefined) {
 | 
			
		||||
      this.passwordInput();
 | 
			
		||||
    }
 | 
			
		||||
    const loginStatus = this.authService.login();
 | 
			
		||||
    if (loginStatus) {
 | 
			
		||||
      this.router.navigate(['/home']);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switchWindows(): void {
 | 
			
		||||
  keyInput(): void {
 | 
			
		||||
    this.authService.sessionToken = undefined;
 | 
			
		||||
    const divOne: HTMLElement = document.getElementById('one');
 | 
			
		||||
    const divTwo: HTMLElement = document.getElementById('two');
 | 
			
		||||
    this.toggleDisplay(divOne);
 | 
			
		||||
    this.toggleDisplay(divTwo);
 | 
			
		||||
    this.switchWindows(true, false, false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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;
 | 
			
		||||
    if (style === 'block') {
 | 
			
		||||
      element.style.display = 'none';
 | 
			
		||||
    } else {
 | 
			
		||||
    if (active) {
 | 
			
		||||
      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';
 | 
			
		||||
 | 
			
		||||
export async function signChallenge(challenge, realm, origin, keyStore) {
 | 
			
		||||
export async function signChallenge(challenge, realm, origin, keyStore, password) {
 | 
			
		||||
  const fingerprint = keyStore.getFingerprint();
 | 
			
		||||
  const nonce_array = new Uint8Array(32);
 | 
			
		||||
  crypto.getRandomValues(nonce_array);
 | 
			
		||||
@ -14,7 +14,7 @@ export async function signChallenge(challenge, realm, origin, keyStore) {
 | 
			
		||||
  const a_challenge = btoa(challenge);
 | 
			
		||||
  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 result = hobaResult(a_nonce, a_kid, a_challenge, a_signature);
 | 
			
		||||
 | 
			
		||||
@ -65,6 +65,12 @@ footer.footer { color: black; }
 | 
			
		||||
  left: -9999px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.center-items {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#sidebar {
 | 
			
		||||
  position: sticky;
 | 
			
		||||
  position: -webkit-sticky;
 | 
			
		||||
@ -207,6 +213,11 @@ a[data-toggle="collapse"] { position: relative; }
 | 
			
		||||
 | 
			
		||||
.mat-column-select { overflow: initial; }
 | 
			
		||||
 | 
			
		||||
#passwordLoginButton {
 | 
			
		||||
  height: 3.0rem;
 | 
			
		||||
  padding-top: 0.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button { height: 2.5rem; }
 | 
			
		||||
 | 
			
		||||
.badge-pill { width: 5rem; }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user