Newer
Older
CrypticOreWallet / src / app / StorageManager.tsx
@Drew Lemmy Drew Lemmy on 5 Sep 2020 2 KB feat: start of master password dialog
import React, { Component } from "react";

import { toHex } from "@utils";
import { aesGcmEncrypt, aesGcmDecrypt } from "@utils/crypto";

import { MasterPasswordDialog } from "./MasterPasswordDialog";
import { MasterPasswordSetupDialog } from "./MasterPasswordSetupDialog";

import Debug from "debug";
const debug = Debug("kristweb:storage");

type StorageManagerData = {
  /** Whether or not the user has logged in, either as a guest, or with a
   * master password. */
  isLoggedIn: boolean;

  /** Whether or not the user is browsing KristWeb as a guest. */
  isGuest: boolean;
  
  /** Secure random string that is encrypted with the master password to create
   * the "tester" string. */
  salt?: string;
  /** The `salt` encrypted with the master password, to test the password is
   * correct. */
  tester?: string;

  /** Whether or not the user has configured and saved a master password
   * before (whether or not salt+tester are present in local storage). */
  hasMasterPassword: boolean;
}

export class StorageManager extends Component<unknown, StorageManagerData> {
  constructor(props: unknown) {
    super(props);

    // Check current data stored in local storage.
    const salt = localStorage.getItem("salt") || undefined;
    const tester = localStorage.getItem("tester") || undefined;

    this.state = {
      isLoggedIn: false,
      isGuest: true,

      // Salt and tester from local storage (or undefined)
      salt, tester,
      // There is a master password configured if both `salt` and `tester` exist
      hasMasterPassword: !!salt && !!tester
    };

    debug("hasMasterPassword: %b", this.state.hasMasterPassword);
  }

  async setMasterPassword(password: string): Promise<void> {
    if (!password) throw new Error("Password is required.");

    // Generate the salt (to be encrypted with the master password)
    const salt = window.crypto.getRandomValues(new Uint8Array(32));

    // Generate the encryption tester
    const tester = await aesGcmEncrypt(toHex(salt), password);

    debug("master password salt: %x    tester: %s", salt, tester);
  }

  /** Render the master password login/setup dialog */
  render(): JSX.Element | null {
    const { isLoggedIn, hasMasterPassword } = this.state;
    if (isLoggedIn) return null; // Don't show the dialog again

    if (hasMasterPassword) // Let the user log in with existing master password
      return <MasterPasswordDialog />;
    else // Have the user set a password up first
      return <MasterPasswordSetupDialog storageManager={this} />;
  }
}