Skip to content

Commit

Permalink
Remove masterPassword from Credentials rest
Browse files Browse the repository at this point in the history
Fixes #336
  • Loading branch information
perry-mitchell committed Dec 9, 2023
1 parent f53afbe commit 77fbcdf
Show file tree
Hide file tree
Showing 21 changed files with 104 additions and 75 deletions.
2 changes: 2 additions & 0 deletions source/core/VaultManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export class VaultManager extends EventEmitter {
/**
* Fetch all currently available Live Snapshots of vaults
* @returns An array of snapshot objects
* @deprecated Will be removed in next major - insecure
*/
getLiveSnapshots(): Array<VaultLiveSnapshot> {
return this.unlockedSources.map((source) => source.getLiveSnapshot());
Expand Down Expand Up @@ -353,6 +354,7 @@ export class VaultManager extends EventEmitter {
/**
* Restore all sources from snapshots that were taken previously
* @param snapshots An array of snapshot objects
* @deprecated Will be removed in next major - insecure
*/
async restoreLiveSnapshots(snapshots: Array<VaultLiveSnapshot>) {
await Promise.all(
Expand Down
27 changes: 16 additions & 11 deletions source/core/VaultSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { ChannelQueue } from "@buttercup/channel-queue";
import { Layerr } from "layerr";
import { Vault } from "./Vault.js";
import { Credentials } from "../credentials/Credentials.js";
import { getCredentials, setCredentials } from "../credentials/channel.js";
import { getCredentials, setCredentials } from "../credentials/memory/credentials.js";
import { getMasterPassword, setMasterPassword } from "../credentials/memory/password.js";
import { getUniqueID } from "../tools/encoding.js";
import {
getSourceOfflineArchive,
Expand All @@ -17,8 +18,8 @@ import { TextDatasource } from "../datasources/TextDatasource.js";
import { VaultManager } from "./VaultManager.js";
import { convertFormatAVault } from "../io/formatB/conversion.js";
import { VaultFormatB } from "../index.common.js";
import { VaultFormatID, VaultLiveSnapshot, VaultSourceID, VaultSourceStatus } from "../types.js";
import { getFormatForID } from "../io/formatRouter.js";
import { VaultFormatID, VaultLiveSnapshot, VaultSourceID, VaultSourceStatus } from "../types.js";

interface StateChangeEnqueuedFunction {
(): void | Promise<any>;
Expand Down Expand Up @@ -272,8 +273,8 @@ export class VaultSource extends EventEmitter {
await this.unlock(Credentials.fromPassword(oldPassword));
} else {
// Unlocked, so check password..
const credentials = getCredentials((<Credentials>this._credentials).id);
if (credentials.masterPassword !== oldPassword) {
const masterPassword = getMasterPassword((<Credentials>this._credentials).id);
if (masterPassword !== oldPassword) {
throw new Error("Old password does not match current unlocked instance value");
}
// ..and then update
Expand All @@ -296,8 +297,7 @@ export class VaultSource extends EventEmitter {
this._credentials as Credentials,
oldPassword
);
const newCreds = getCredentials(newCredentials.id);
newCreds.masterPassword = newPassword;
setMasterPassword(newCredentials.id, newPassword);
await this._updateVaultCredentials(newCredentials);
// Re-lock if it was locked earlier
if (wasLocked) {
Expand Down Expand Up @@ -385,19 +385,23 @@ export class VaultSource extends EventEmitter {
/**
* Get a live snapshot of the current unlocked state
* @returns A snapshot object
* @deprecated Will be removed in next major - insecure
*/
getLiveSnapshot(): VaultLiveSnapshot {
if (this.status !== VaultSourceStatus.Unlocked) {
throw new Layerr("Not possible to fetch live snapshot: Vault is not unlocked");
}
const credentials = getCredentials((this._credentials as Credentials).id);
const credentialsID = (this._credentials as Credentials).id;
const credentials = getCredentials(credentialsID);
if (!credentials) {
throw new Layerr("Failed fetching live snapshot: Invalid credentials data");
}
const masterPassword = getMasterPassword(credentialsID);
return {
credentials,
formatID: this.vault._format.getFormat().getFormatID(),
formatSource: this.vault._format.source,
masterPassword,
sourceID: this.id,
version: "1a"
};
Expand Down Expand Up @@ -533,6 +537,7 @@ export class VaultSource extends EventEmitter {
/**
* Restore unlocked state from a live snapshot
* @param snapshot The snapshot taken previously
* @deprecated Will be removed in next major - insecure
*/
async restoreFromLiveSnapshot(snapshot: VaultLiveSnapshot): Promise<void> {
if (this.status !== VaultSourceStatus.Locked) {
Expand All @@ -546,12 +551,12 @@ export class VaultSource extends EventEmitter {
// Setup credentials and datasource
const credentials = (this._credentials = new Credentials(
snapshot.credentials.data,
snapshot.credentials.masterPassword
snapshot.masterPassword
));
setCredentials(credentials.id, snapshot.credentials);
// Initialise datasource
const datasource = (this._datasource = credentialsToDatasource(
Credentials.fromCredentials(credentials, snapshot.credentials.masterPassword)
Credentials.fromCredentials(credentials, snapshot.masterPassword)
));
datasource.sourceID = this.id;
// Setup vault
Expand Down Expand Up @@ -650,7 +655,7 @@ export class VaultSource extends EventEmitter {
`Failed unlocking source: Source in invalid state (${this.status}): ${this.id}`
);
}
const { masterPassword } = getCredentials(vaultCredentials.id);
const masterPassword = getMasterPassword(vaultCredentials.id);
const originalCredentials = this._credentials;
this._status = VaultSource.STATUS_PENDING;
await this._enqueueStateChange(async () => {
Expand Down Expand Up @@ -813,7 +818,7 @@ export class VaultSource extends EventEmitter {
`Failed updating source credentials: Source is not unlocked: ${this.id}`
);
}
const { masterPassword } = getCredentials((<Credentials>this._credentials).id);
const masterPassword = getMasterPassword((<Credentials>this._credentials).id);
this._credentials = Credentials.fromCredentials(
this._datasource.credentials,
masterPassword
Expand Down
53 changes: 27 additions & 26 deletions source/credentials/Credentials.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { generateUUID } from "../tools/uuid.js";
import { credentialsAllowsPurpose, getCredentials, setCredentials } from "./channel.js";
import { credentialsAllowsPurpose, getCredentials, setCredentials } from "./memory/credentials.js";
import { getSharedAppEnv } from "../env/appEnv.js";
import { getMasterPassword, setMasterPassword } from "./memory/password.js";
import { CredentialsData, CredentialsPayload, DatasourceConfiguration } from "../types.js";

/**
Expand Down Expand Up @@ -85,7 +86,8 @@ export class Credentials {
throw new Error("Master password is required for credentials cloning");
}
const credentialsData = getCredentials(credentials.id);
if (credentialsData.masterPassword !== masterPassword) {
const credentialsPassword = getMasterPassword(credentials.id);
if (credentialsPassword !== masterPassword) {
throw new Error("Master password does not match that of the credentials to be cloned");
}
const newData = JSON.parse(JSON.stringify(credentialsData.data));
Expand Down Expand Up @@ -134,29 +136,27 @@ export class Credentials {
* @param masterPassword The password for decryption
* @returns A promise that resolves with the new instance
*/
static fromSecureString(content: string, masterPassword: string): Promise<Credentials> {
static async fromSecureString(content: string, masterPassword: string): Promise<Credentials> {
const decrypt = getSharedAppEnv().getProperty("crypto/v1/decryptText");
return decrypt(unsignEncryptedContent(content), masterPassword)
.then((decryptedContent: string) => JSON.parse(decryptedContent))
.then((credentialsData: any) => {
// Handle compatibility updates for legacy credentials
if (credentialsData.datasource) {
if (typeof credentialsData.datasource === "string") {
credentialsData.datasource = JSON.parse(credentialsData.datasource);
}
// Move username and password INTO the datasource config, as
// they relate to the remote connection/source
if (credentialsData.username) {
credentialsData.datasource.username = credentialsData.username;
delete credentialsData.username;
}
if (credentialsData.password) {
credentialsData.datasource.password = credentialsData.password;
delete credentialsData.password;
}
}
return new Credentials(credentialsData, masterPassword);
});
const decryptedContent = await decrypt(unsignEncryptedContent(content), masterPassword);
const credentialsData = JSON.parse(decryptedContent);
// Handle compatibility updates for legacy credentials
if (credentialsData.datasource) {
if (typeof credentialsData.datasource === "string") {
credentialsData.datasource = JSON.parse(credentialsData.datasource);
}
// Move username and password INTO the datasource config, as
// they relate to the remote connection/source
if (credentialsData.username) {
credentialsData.datasource.username = credentialsData.username;
delete credentialsData.username;
}
if (credentialsData.password) {
credentialsData.datasource.password = credentialsData.password;
delete credentialsData.password;
}
}
return new Credentials(credentialsData, masterPassword);
}

/**
Expand Down Expand Up @@ -190,10 +190,10 @@ export class Credentials {
});
setCredentials(id, {
data: obj,
masterPassword,
purposes: Credentials.allPurposes(),
open: false
});
setMasterPassword(id, masterPassword);
}

/**
Expand Down Expand Up @@ -279,7 +279,8 @@ export class Credentials {
throw new Error("Credential purposes don't allow for secure exports");
}
const encrypt = getSharedAppEnv().getProperty("crypto/v1/encryptText");
const { data, masterPassword } = getCredentials(this.id);
const { data } = getCredentials(this.id);
const masterPassword = getMasterPassword(this.id);
if (typeof masterPassword !== "string") {
throw new Error(
"Cannot convert Credentials to string: master password was not set or is invalid"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CredentialsPayload } from "../types.js";
import { CredentialsPayload } from "../../types.js";

const __store = {};
const __store: Record<string, CredentialsPayload | null> = {};

export function credentialsAllowsPurpose(id: string, purpose: string): boolean {
const { purposes } = getCredentials(id);
Expand Down
14 changes: 14 additions & 0 deletions source/credentials/memory/password.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const __store: Record<string, string | null> = {};

export function getMasterPassword(id: string): string | null {
return __store[id] || null;
}

export function removeMasterPassword(id: string) {
__store[id] = null;
delete __store[id];
}

export function setMasterPassword(id: string, value: string) {
__store[id] = value;
}
2 changes: 1 addition & 1 deletion source/datasources/DropboxDatasource.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TextDatasource } from "./TextDatasource.js";
import { fireInstantiationHandlers, registerDatasource } from "./register.js";
import { Credentials } from "../credentials/Credentials.js";
import { getCredentials } from "../credentials/channel.js";
import { getCredentials } from "../credentials/memory/credentials.js";
import { getSharedAppEnv } from "../env/appEnv.js";
import {
DatasourceConfigurationDropbox,
Expand Down
2 changes: 1 addition & 1 deletion source/datasources/FileDatasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import pify from "pify";
import { TextDatasource } from "./TextDatasource.js";
import { fireInstantiationHandlers, registerDatasource } from "./register.js";
import { Credentials } from "../credentials/Credentials.js";
import { getCredentials } from "../credentials/channel.js";
import { getCredentials } from "../credentials/memory/credentials.js";
import { ATTACHMENT_EXT } from "../tools/attachments.js";
import {
AttachmentDetails,
Expand Down
22 changes: 11 additions & 11 deletions source/datasources/GoogleDriveDatasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import DatasourceAuthManager from "./DatasourceAuthManager.js";
import { TextDatasource } from "./TextDatasource.js";
import { fireInstantiationHandlers, registerDatasource } from "./register.js";
import { Credentials } from "../credentials/Credentials.js";
import { getCredentials } from "../credentials/channel.js";
import { DatasourceConfigurationGoogleDrive } from "../types.js";
import { getCredentials } from "../credentials/memory/credentials.js";
import { DatasourceConfigurationGoogleDrive, DatasourceLoadedData, History } from "../types.js";

const DATASOURCE_TYPE = "googledrive";

Expand All @@ -23,7 +23,7 @@ export default class GoogleDriveDatasource extends TextDatasource {

/**
* Datasource for Google Drive connections
* @param {Credentials} credentials The credentials instance with which to
* @param credentials The credentials instance with which to
* configure the datasource with
*/
constructor(credentials: Credentials) {
Expand Down Expand Up @@ -57,11 +57,11 @@ export default class GoogleDriveDatasource extends TextDatasource {

/**
* Load an archive from the datasource
* @param {Credentials} credentials The credentials for decryption
* @returns {Promise.<LoadedVaultData>} A promise that resolves archive history
* @param credentials The credentials for decryption
* @returns A promise that resolves archive history
* @memberof GoogleDriveDatasource
*/
load(credentials, hasAuthed = false) {
load(credentials: Credentials, hasAuthed: boolean = false): Promise<DatasourceLoadedData> {
if (this.hasContent) {
return super.load(credentials);
}
Expand Down Expand Up @@ -89,12 +89,12 @@ export default class GoogleDriveDatasource extends TextDatasource {

/**
* Save an archive using the datasource
* @param {Array.<String>} history The archive history to save
* @param {Credentials} credentials The credentials to save with
* @returns {Promise} A promise that resolves when saving has completed
* @param history The archive history to save
* @param credentials The credentials to save with
* @returns A promise that resolves when saving has completed
* @memberof GoogleDriveDatasource
*/
save(history, credentials, hasAuthed = false) {
save(history: History, credentials: Credentials, hasAuthed: boolean = false) {
return super
.save(history, credentials)
.then((encryptedContent) => this.client.putFileContents(encryptedContent, this.fileID))
Expand All @@ -116,7 +116,7 @@ export default class GoogleDriveDatasource extends TextDatasource {

/**
* Whether or not the datasource supports bypassing remote fetch operations
* @returns {Boolean} True if content can be set to bypass fetch operations,
* @returns True if content can be set to bypass fetch operations,
* false otherwise
* @memberof GoogleDriveDatasource
*/
Expand Down
2 changes: 1 addition & 1 deletion source/datasources/MemoryDatasource.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TextDatasource } from "./TextDatasource.js";
import { fireInstantiationHandlers, registerDatasource } from "./register.js";
import { Credentials } from "../credentials/Credentials.js";
import { getCredentials } from "../credentials/channel.js";
import { getCredentials } from "../credentials/memory/credentials.js";
import {
AttachmentDetails,
BufferLike,
Expand Down
2 changes: 1 addition & 1 deletion source/datasources/TextDatasource.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import EventEmitter from "eventemitter3";
import hash from "hash.js";
import { Credentials } from "../credentials/Credentials.js";
import { credentialsAllowsPurpose, getCredentials } from "../credentials/channel.js";
import { credentialsAllowsPurpose, getCredentials } from "../credentials/memory/credentials.js";
import { detectFormat, getFormatForID } from "../io/formatRouter.js";
import { fireInstantiationHandlers, registerDatasource } from "./register.js";
import {
Expand Down
2 changes: 1 addition & 1 deletion source/datasources/WebDAVDatasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { TextDatasource } from "./TextDatasource.js";
import { fireInstantiationHandlers, registerDatasource } from "./register.js";
import { getSharedAppEnv } from "../env/appEnv.js";
import { Credentials } from "../credentials/Credentials.js";
import { getCredentials } from "../credentials/channel.js";
import { getCredentials } from "../credentials/memory/credentials.js";
import { ATTACHMENT_EXT } from "../tools/attachments.js";
import {
AttachmentDetails,
Expand Down
7 changes: 4 additions & 3 deletions source/datasources/register.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getCredentials } from "../credentials/channel.js";
import { getCredentials } from "../credentials/memory/credentials.js";
import { getMasterPassword } from "../credentials/memory/password.js";
import { Credentials } from "../credentials/Credentials.js";
import { TextDatasource } from "./TextDatasource.js";

Expand Down Expand Up @@ -61,9 +62,9 @@ export function prepareDatasourceCredentials(
typeOverride: string = null
): Credentials {
const {
data: { datasource },
masterPassword
data: { datasource }
} = getCredentials(credentials.id);
const masterPassword = getMasterPassword(credentials.id);
const datasourceType = typeOverride || datasource.type || "";
const { open = false } = __datasourceFlags[datasourceType] || {};
if (!open) {
Expand Down
6 changes: 3 additions & 3 deletions source/io/VaultFormatA.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import VaultComparator from "./formatA/VaultComparator.js";
import { getSharedAppEnv } from "../env/appEnv.js";
import { decodeStringValue, isEncoded } from "../tools/encoding.js";
import { generateUUID } from "../tools/uuid.js";
import { getCredentials } from "../credentials/channel.js";
import { getMasterPassword } from "../credentials/memory/password.js";
import { historyArrayToString, historyStringToArray } from "./common.js";
import { smartStripRemovedAssets } from "./formatA/merge.js";
import {
Expand Down Expand Up @@ -99,7 +99,7 @@ export class VaultFormatA extends VaultFormat {
static async encodeRaw(rawContent: History, credentials: Credentials): Promise<string> {
const compress = getSharedAppEnv().getProperty("compression/v1/compressText");
const encrypt = getSharedAppEnv().getProperty("crypto/v1/encryptText");
const { masterPassword } = getCredentials(credentials.id);
const masterPassword = getMasterPassword(credentials.id);
return Promise.resolve()
.then(() => historyArrayToString(rawContent))
.then((history) => compress(history))
Expand Down Expand Up @@ -152,7 +152,7 @@ export class VaultFormatA extends VaultFormat {
static parseEncrypted(encryptedContent: string, credentials: Credentials): Promise<History> {
const decompress = getSharedAppEnv().getProperty("compression/v1/decompressText");
const decrypt = getSharedAppEnv().getProperty("crypto/v1/decryptText");
const { masterPassword } = getCredentials(credentials.id);
const masterPassword = getMasterPassword(credentials.id);
return Promise.resolve()
.then(() => {
if (!hasValidSignature(encryptedContent)) {
Expand Down
Loading

0 comments on commit 77fbcdf

Please sign in to comment.