StorageAdapters/KeystoreStorageAdapter.js

import axios from 'axios'

import StorageAdapter from './StorageAdapter'
import { InvalidPassword, AccountNotFoundError } from '../Errors'

const DEFAULT_KEYSTORE_SERVER = "https://keystore.oip.li/v2/"

/**
 * The KeystoreStorageAdapter class is built on top of StorageAdapter to provide saving to an [OIP Keystore](https://github.com/oipwg/oip-keystore) server
 * @extends {StorageAdapter}
 */
class KeystoreStorageAdapter extends StorageAdapter {
	/**
	 * Create a new KeystoreStorageAdapter
	 * @param  {string} username     - The username of the account you wish to use
	 * @param  {string} password     - The password of the account you wish to use
	 * @param  {string} [keystore_url="https://keystore.oip.li/v2/"] - The URL of the [OIP Keystore](https://github.com/oipwg/oip-keystore) server to use
	 * @return {KeystoreStorageServer}
	 */
	constructor(username, password, keystore_url){
		super(username, password)

		this._url = keystore_url || DEFAULT_KEYSTORE_SERVER;

		this._keystore = axios.create({
			baseURL: this._url
		})
	}
	/**
	 * Create a new Account on the Keystore Server
	 *
	 * @async
	 * @param  {Object} account_data - The Account Data you wish to save to your new accouny
	 * @param  {string} [email]      - An Email if you would like to attach an email to your account
	 * @return {Promise<Object>} Returns a Promise that will resolve to the Account Data of the new account if successful
	 */
	async create(account_data, email){
		var clonedAccountData = JSON.parse(JSON.stringify(account_data));

		var create

		try {
			create = await this._keystore.post("/create", { email: email })
		} catch(e) {
			if (e.response && e.response.data && e.response.data.type)
				throw new Error(e.response.data.type)

			throw new Error(e.response)
		}

		if (create.data.shared_key){
			this.storage.shared_key = create.data.shared_key
			clonedAccountData.shared_key = create.data.shared_key
		}
		if (create.data.email){
			this.storage.email = create.data.email
			clonedAccountData.email = create.data.email
		}

		clonedAccountData.identifier = create.data.identifier
		this.storage.identifier = create.data.identifier

		if (!this._username)
			this._username = create.data.identifier

		return this._save(clonedAccountData, this.storage.identifier)
	}
	/**
	 * Load an Account from the Keystore Server
	 *
	 * @async
	 * @throws {InvalidPassword} If the password being used for login is invalid
	 * @throws {AccountNotFoundError} If the Account cannot beb found on the storage server
	 * @return {Promise<Object>} Returns a Promise that will resolve to the Decrypted Account Data if successful
	 */
	async load(){
		var load

		// Try to load from the keystore server
		try {
			load = await this._keystore.post("/load", { identifier: this.storage.identifier || this._username })
		} catch(e) {
			throw new AccountNotFoundError("Unable to load Account\n" + e.response.data.type)
		}

		var decrypted
		try {
			decrypted = this.decrypt(load.data.encrypted_data)
		} catch(e) {
			throw new InvalidPassword("Password is not valid!\n" + e)
		}

		if (decrypted.shared_key)
			this.storage.shared_key = decrypted.shared_key

		return decrypted
	}
	/**
	 * Internal Save function to Save an Account to the Keystore Server
	 *
	 * @async
	 * @param  {Object} account_data - The new Account Data you wish to save
	 * @param  {Identifier} identifier - The Identifier of the account you wish to save
	 * @return {Promise<Object>} Returns a Promise that will resolve to the saved Account Data of the updated account if successful
	 */
	async _save(account_data, identifier){
		this.encrypt(account_data);

		this.storage.identifier = identifier
		
		var saved

		try {
			saved = await this._keystore.post("/update", this.storage);
		} catch(e) {
			throw new Error(e.response.data.type)
		}

		return account_data
	}
	/**
	 * Check if the Account exists on the Keystore server. This matches an email to an identifier if the username being used is an email.
	 *
	 * @async
	 * @throws {AccountNotFoundError} If there is no Identifier that matches the Username passed
	 * @return {Promise<Identifier>} Returns a Promise that will resolve to the Accounts Identifier if set
	 */
	async check(){
		var exists
		// If the username is not a valid identifier, try to match it to an email
		try {
			exists = await this._keystore.post("/checkload", { identifier: this._username });
		} catch (e) {
			throw new AccountNotFoundError(e.response.data.type)
		}

		return exists.data.identifier
	}
}

module.exports = KeystoreStorageAdapter;