Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-architecture project #139

Merged
merged 1 commit into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 18 additions & 22 deletions src/middleware/configure.ts → src/command-handlers/configure.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
import Database from 'better-sqlite3';
import winston from 'winston';
import { encryptAES } from '../crypto/encrypt';
import { deleteLocalKey, setLocalKey, warnUnreachableKeychainDisabled } from '../crypto/keychainManager';
import { Secrets } from '../types';

interface ConfigureSaveMasterPassword {
db: Database.Database;
secrets: Secrets;
shouldNotSaveMasterPassword: boolean;
}

interface ConfigureDisableAutoSync {
db: Database.Database;
secrets: Secrets;
disableAutoSync: boolean;
}

export const configureSaveMasterPassword = (params: ConfigureSaveMasterPassword) => {
const { db, secrets } = params;
let shouldNotSaveMasterPassword = params.shouldNotSaveMasterPassword;
import { encryptAES } from '../modules/crypto/encrypt';
import { deleteLocalKey, setLocalKey, warnUnreachableKeychainDisabled } from '../modules/crypto/keychainManager';
import { connectAndPrepare } from '../modules/database';
import { parseBooleanString } from '../utils';

export const configureSaveMasterPassword = async (boolean: string) => {
Mikescops marked this conversation as resolved.
Show resolved Hide resolved
let shouldNotSaveMasterPassword = !parseBooleanString(boolean);
const { db, secrets } = await connectAndPrepare({
autoSync: false,
shouldNotSaveMasterPasswordIfNoDeviceKeys: shouldNotSaveMasterPassword,
});

if (shouldNotSaveMasterPassword) {
// Forget the local key stored in the OS keychain because the master password and the DB are enough to retrieve the
Expand Down Expand Up @@ -54,12 +45,17 @@ export const configureSaveMasterPassword = (params: ConfigureSaveMasterPassword)
db.prepare('UPDATE device SET masterPasswordEncrypted = ?, shouldNotSaveMasterPassword = ? WHERE login = ?')
.bind(masterPasswordEncrypted, shouldNotSaveMasterPassword ? 1 : 0, secrets.login)
.run();

db.close();
};

export const configureDisableAutoSync = (params: ConfigureDisableAutoSync) => {
const { db, secrets, disableAutoSync } = params;
export const configureDisableAutoSync = async (boolean: string) => {
const disableAutoSync = parseBooleanString(boolean);
const { db, secrets } = await connectAndPrepare({ autoSync: false });

db.prepare('UPDATE device SET autoSync = ? WHERE login = ?')
.bind(disableAutoSync ? 0 : 1, secrets.login)
.run();

db.close();
};
3 changes: 1 addition & 2 deletions src/command-handlers/devices.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { connectAndPrepare } from '../database';
import { connectAndPrepare, reset } from '../modules/database';
import { deactivateDevices, listDevices, ListDevicesOutput } from '../endpoints';
import { reset } from '../middleware';
import { askConfirmReset, unixTimestampToHumanReadable } from '../utils';

type OutputDevice = ListDevicesOutput['devices'][number] & {
Expand Down
8 changes: 8 additions & 0 deletions src/command-handlers/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
export * from './configure';
export * from './devices';
export * from './logout';
export * from './passwords';
export * from './secureNotes';
export * from './sync';
export * from './teamDevices';
export * from './teamLogs';
export * from './teamMembers';
export * from './teamReport';
42 changes: 42 additions & 0 deletions src/command-handlers/logout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Database } from 'better-sqlite3';
import winston from 'winston';
import { deactivateDevices } from '../endpoints';
import { connectAndPrepare, connect, reset } from '../modules/database';
import { Secrets, DeviceConfiguration } from '../types';
import { askConfirmReset } from '../utils';

export const runLogout = async () => {
const resetConfirmation = await askConfirmReset();
if (!resetConfirmation) {
return;
}

let db: Database;
let secrets: Secrets | undefined;
let deviceConfiguration: DeviceConfiguration | null | undefined;
try {
({ db, secrets, deviceConfiguration } = await connectAndPrepare({
autoSync: false,
failIfNoDB: true,
}));
} catch (error) {
let errorMessage = 'unknown error';
if (error instanceof Error) {
errorMessage = error.message;
}
winston.debug(`Unable to read device configuration during logout: ${errorMessage}`);

db = connect();
db.serialize();
}
if (secrets && deviceConfiguration) {
await deactivateDevices({
deviceIds: [deviceConfiguration.accessKey],
login: deviceConfiguration.login,
secrets,
});
}
reset({ db, secrets });
console.log('The local Dashlane local storage has been reset and you have been logged out');
db.close();
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,47 @@ import { Clipboard } from '@napi-rs/clipboard';
import { authenticator } from 'otplib';
import winston from 'winston';
import { AuthentifiantTransactionContent, BackupEditTransaction, Secrets, VaultCredential } from '../types';
import { decryptTransaction } from '../crypto';
import { decryptTransaction } from '../modules/crypto';
import { askCredentialChoice } from '../utils';
import { connectAndPrepare } from '../modules/database';

export const runPassword = async (filters: string[] | null, options: { output: string | null }) => {
const { db, secrets } = await connectAndPrepare({});

if (options.output === 'json') {
console.log(
JSON.stringify(
await selectCredentials({
filters,
secrets,
output: options.output,
db,
}),
null,
4
)
);
} else {
await getPassword({
filters,
secrets,
output: options.output,
db,
});
}
db.close();
};

export const runOtp = async (filters: string[] | null, options: { print: boolean }) => {
const { db, secrets } = await connectAndPrepare({});
await getOtp({
filters,
secrets,
output: options.print ? 'otp' : 'clipboard',
db,
});
db.close();
};

interface GetCredential {
filters: string[] | null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import Database from 'better-sqlite3';
import winston from 'winston';
import { BackupEditTransaction, Secrets, SecureNoteTransactionContent, VaultNote } from '../types';
import { decryptTransaction } from '../crypto';
import { decryptTransaction } from '../modules/crypto';
import { askSecureNoteChoice } from '../utils';
import { connectAndPrepare } from '../modules/database';

export const runSecureNote = async (filter: string | null) => {
const { db, secrets } = await connectAndPrepare({});
await getNote({
titleFilter: filter,
secrets,
db,
});
db.close();
};

interface GetSecureNote {
titleFilter: string | null;
Expand Down
14 changes: 11 additions & 3 deletions src/middleware/sync.ts → src/command-handlers/sync.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import Database from 'better-sqlite3';
import winston from 'winston';
import { decrypt } from '../crypto/decrypt';
import { encryptAES } from '../crypto/encrypt';
import { replaceMasterPassword } from '../crypto/keychainManager';
import { connectAndPrepare } from '../modules/database';
import { decrypt } from '../modules/crypto/decrypt';
import { encryptAES } from '../modules/crypto/encrypt';
import { replaceMasterPassword } from '../modules/crypto/keychainManager';
import { getLatestContent } from '../endpoints';
import type { DeviceConfiguration, Secrets } from '../types';
import { notEmpty } from '../utils';
import { askReplaceIncorrectMasterPassword } from '../utils/dialogs';

export const runSync = async () => {
const { db, secrets, deviceConfiguration } = await connectAndPrepare({ autoSync: false });
await sync({ db, secrets, deviceConfiguration });
winston.info('Successfully synced');
db.close();
};

interface Sync {
db: Database.Database;
secrets: Secrets;
Expand Down
3 changes: 1 addition & 2 deletions src/command-handlers/teamDevices.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { connectAndPrepare } from '../database';
import { connectAndPrepare } from '../modules/database';
import { listTeamDevices } from '../endpoints';
import { unixTimestampToHumanReadable } from '../utils';

Expand Down Expand Up @@ -36,7 +36,6 @@ export async function listAllTeamDevices(options: { json: boolean }) {
};
});

// print results
console.table(result);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
import winston from 'winston';
import { getAuditLogQueryResults, startAuditLogsQuery, StartAuditLogsQueryParams } from '../endpoints';
import { connectAndPrepare } from '../modules/database';
import { StartAuditLogsQueryParams, startAuditLogsQuery, getAuditLogQueryResults } from '../endpoints';
import { getTeamDeviceCredentials } from '../utils';

export const runTeamLogs = async (options: { start: string; end: string; type: string; category: string }) => {
const teamDeviceCredentials = getTeamDeviceCredentials();

const { start, type, category } = options;
const end = options.end === 'now' ? Date.now().toString() : options.end;

const { db } = await connectAndPrepare({ autoSync: false });
await getAuditLogs({
teamDeviceCredentials,
startDateRangeUnix: parseInt(start),
endDateRangeUnix: parseInt(end),
logType: type,
category,
});
db.close();
};

const MAX_RESULT = 1000;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { getTeamMembers as getTeamMembersRequest } from '../endpoints';
import { TeamDeviceCredentials } from '../types';
import { getTeamDeviceCredentials } from '../utils';

interface GetTeamMembersParams {
teamDeviceCredentials: TeamDeviceCredentials;
page: number;
limit: number;
}

export const getTeamMembers = async (params: GetTeamMembersParams) => {
const { teamDeviceCredentials, page, limit } = params;
export const runTeamMembers = async (params: GetTeamMembersParams) => {
const { page, limit } = params;
const teamDeviceCredentials = getTeamDeviceCredentials();

const response = await getTeamMembersRequest({
teamDeviceCredentials,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { getTeamReport as getTeamReportRequest } from '../endpoints';
import { TeamDeviceCredentials } from '../types';
import { getTeamDeviceCredentials } from '../utils';

interface GetTeamMembersParams {
teamDeviceCredentials: TeamDeviceCredentials;
days: number;
}

export const getTeamReport = async (params: GetTeamMembersParams) => {
const { teamDeviceCredentials, days } = params;
export const runTeamReport = async (params: GetTeamMembersParams) => {
const { days } = params;
const teamDeviceCredentials = getTeamDeviceCredentials();

const response = await getTeamReportRequest({
teamDeviceCredentials,
Expand Down
18 changes: 18 additions & 0 deletions src/commands/configure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Command } from 'commander';
import { configureDisableAutoSync, configureSaveMasterPassword } from '../command-handlers';

export const configureCommands = (params: { program: Command }) => {
const { program } = params;

const configureGroup = program.command('configure').alias('c').description('Configure the CLI');

configureGroup
.command('disable-auto-sync <boolean>')
.description('Disable automatic synchronization which is done once per hour (default: false)')
.action(configureDisableAutoSync);

configureGroup
.command('save-master-password <boolean>')
.description('Should the encrypted master password be saved and the OS keychain be used (default: true)')
.action(configureSaveMasterPassword);
};
22 changes: 22 additions & 0 deletions src/commands/devices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Command } from 'commander';
import { listAllDevices, removeAllDevices } from '../command-handlers';

export const devicesCommands = (params: { program: Command }) => {
const { program } = params;

const devicesGroup = program.command('devices').alias('d').description('Operations on devices');

devicesGroup
.command('list')
.option('--json', 'Output in JSON format')
.description('Lists all registered devices that can access your account')
.action(listAllDevices);

devicesGroup
.command('remove')
.option('--all', 'remove all devices including this one (dangerous)')
.option('--others', 'remove all other devices')
.argument('[device ids...]', 'ids of the devices to remove')
.description('De-registers a list of devices. De-registering the CLI will implies doing a "dcli logout"')
.action(removeAllDevices);
};
56 changes: 56 additions & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Command } from 'commander';
import { devicesCommands } from './devices';
import { teamCommands } from './team';
import { configureCommands } from './configure';
import { runSync, runOtp, runPassword, runSecureNote, runLogout } from '../command-handlers';

export const rootCommands = (params: { program: Command }) => {
const { program } = params;

program
.command('sync')
.alias('s')
.description('Manually synchronize the local vault with Dashlane')
.action(runSync);

program
.command('password')
.alias('p')
.description('Retrieve a password from the local vault and copy it to the clipboard')
.option(
'-o, --output <type>',
'How to print the passwords among `clipboard, password, json`. The JSON option outputs all the matching credentials',
'clipboard'
)
.argument(
'[filters...]',
'Filter credentials based on any parameter using <param>=<value>; if <param> is not specified in the filter, will default to url and title'
)
.action(runPassword);

program
.command('otp')
.alias('o')
.description('Retrieve an OTP code from local vault and copy it to the clipboard')
.option('--print', 'Prints just the OTP code, instead of copying it to the clipboard')
.argument(
'[filters...]',
'Filter credentials based on any parameter using <param>=<value>; if <param> is not specified in the filter, will default to url and title'
)
.action(runOtp);

program
.command('note')
.alias('n')
.description('Retrieve a secure note from the local vault and open it')
.argument('[filter]', 'Filter notes based on their title')
.action(runSecureNote);

devicesCommands({ program });

teamCommands({ program });

configureCommands({ program });

program.command('logout').description('Logout and clean your local database and OS keychain').action(runLogout);
};
Loading