favacli 0.0.21 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/BaseCommand.d.mts +3 -3
- package/build/BaseCommand.mjs +6 -5
- package/build/BaseListOutputCommand.d.mts +2 -2
- package/build/BaseListOutputCommand.mjs +1 -2
- package/build/commands/entries/add.d.mts +1 -1
- package/build/commands/entries/add.mjs +4 -2
- package/build/commands/entries/list.d.mts +2 -2
- package/build/commands/entries/list.mjs +4 -4
- package/build/commands/entries/search.d.mts +2 -2
- package/build/commands/entries/search.mjs +4 -4
- package/build/commands/export/text.d.mts +2 -2
- package/build/commands/export/text.mjs +33 -36
- package/build/commands/sync/connect.d.mts +1 -1
- package/build/commands/sync/connect.mjs +11 -5
- package/build/commands/sync/getInfo.d.mts +1 -1
- package/build/commands/sync/getInfo.mjs +5 -5
- package/build/commands/sync/listDevices.d.mts +1 -1
- package/build/commands/sync/listDevices.mjs +3 -3
- package/build/commands/sync/removeDevice.d.mts +16 -0
- package/build/commands/sync/removeDevice.mjs +69 -0
- package/build/commands/sync/resilver.d.mts +1 -1
- package/build/commands/sync/resilver.mjs +3 -3
- package/build/commands/sync/setFriendlyName.d.mts +1 -1
- package/build/commands/sync/setFriendlyName.mjs +3 -3
- package/build/commands/sync/setServerUrl.d.mts +1 -1
- package/build/commands/sync/setServerUrl.mjs +2 -2
- package/build/commands/vault/create.d.mts +1 -1
- package/build/commands/vault/create.mjs +16 -17
- package/build/commands/vault/delete.d.mts +1 -1
- package/build/commands/vault/delete.mjs +1 -1
- package/build/commands/vault/restorePassword.d.mts +10 -0
- package/build/commands/vault/restorePassword.mjs +60 -0
- package/build/main.mjs +5 -1
- package/build/test.d.mts +1 -0
- package/build/test.mjs +15 -0
- package/build/utils/loadVault.d.mts +1 -1
- package/build/utils/loadVault.mjs +23 -9
- package/package.json +9 -9
package/build/BaseCommand.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from 'clipanion';
|
|
2
2
|
import type { Jsonifiable } from 'type-fest';
|
|
3
|
-
import type { LockedRepresentationString,
|
|
3
|
+
import type { LockedRepresentationString, FavaLib } from 'favalib';
|
|
4
4
|
import { Settings } from './utils/init.mjs';
|
|
5
5
|
export interface ErrorInCommand {
|
|
6
6
|
timestamp: number;
|
|
@@ -8,14 +8,14 @@ export interface ErrorInCommand {
|
|
|
8
8
|
}
|
|
9
9
|
declare abstract class BaseCommand extends Command {
|
|
10
10
|
abstract exec(): Promise<Jsonifiable>;
|
|
11
|
-
abstract
|
|
11
|
+
abstract requireFavaLib: boolean;
|
|
12
12
|
errors: ErrorInCommand[];
|
|
13
13
|
preFormattedOutput: boolean;
|
|
14
14
|
json: boolean | undefined;
|
|
15
15
|
verbose: boolean | undefined;
|
|
16
16
|
lockedRepresentationString: LockedRepresentationString;
|
|
17
17
|
settings: Settings;
|
|
18
|
-
|
|
18
|
+
favaLib: FavaLib;
|
|
19
19
|
output(string: string): void;
|
|
20
20
|
execute(): Promise<number>;
|
|
21
21
|
private addError;
|
package/build/BaseCommand.mjs
CHANGED
|
@@ -21,17 +21,18 @@ class BaseCommand extends Command {
|
|
|
21
21
|
async execute() {
|
|
22
22
|
const { lockedRepresentationString, settings } = await init();
|
|
23
23
|
this.settings = settings;
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
this.lockedRepresentationString = lockedRepresentationString;
|
|
25
|
+
if (lockedRepresentationString && this.requireFavaLib) {
|
|
26
|
+
this.favaLib = await loadVault(lockedRepresentationString, settings, this.addError.bind(this), this.verbose);
|
|
26
27
|
}
|
|
27
28
|
else {
|
|
28
|
-
if (this.
|
|
29
|
+
if (this.requireFavaLib) {
|
|
29
30
|
throw new Error('No vault loaded, was it created?');
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
const result = await this.exec();
|
|
33
|
-
if (this.
|
|
34
|
-
this.
|
|
34
|
+
if (this.favaLib?.sync) {
|
|
35
|
+
this.favaLib.sync.closeServerConnection();
|
|
35
36
|
}
|
|
36
37
|
if (this.json) {
|
|
37
38
|
// output is already formatted, don't add the result & errors bit
|
|
@@ -5,12 +5,12 @@ import formatters from './formatters/index.mjs';
|
|
|
5
5
|
import { Jsonifiable } from 'type-fest';
|
|
6
6
|
export type Formatter = (entries: (EntryMeta | EntryMetaWithToken)[], errors: ErrorInCommand[]) => Jsonifiable;
|
|
7
7
|
declare abstract class BaseListOutputCommand extends BaseCommand {
|
|
8
|
-
abstract getList(): EntryMeta
|
|
8
|
+
abstract getList(): Promise<(EntryMeta | EntryMetaWithToken)[]>;
|
|
9
9
|
format: (typeof formatters)[0]["name"] | undefined;
|
|
10
10
|
exec(): Promise<string | number | boolean | {
|
|
11
11
|
[x: string]: Jsonifiable | undefined;
|
|
12
12
|
} | {
|
|
13
13
|
toJSON: () => Jsonifiable;
|
|
14
|
-
} |
|
|
14
|
+
} | readonly Jsonifiable[] | null>;
|
|
15
15
|
}
|
|
16
16
|
export default BaseListOutputCommand;
|
|
@@ -11,7 +11,6 @@ class BaseListOutputCommand extends BaseCommand {
|
|
|
11
11
|
description: 'formatter',
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
15
14
|
async exec() {
|
|
16
15
|
this.preFormattedOutput = this.format !== undefined;
|
|
17
16
|
let formatter = (json) => json;
|
|
@@ -22,7 +21,7 @@ class BaseListOutputCommand extends BaseCommand {
|
|
|
22
21
|
}
|
|
23
22
|
formatter = selectedFormatter;
|
|
24
23
|
}
|
|
25
|
-
const list = this.getList();
|
|
24
|
+
const list = await this.getList();
|
|
26
25
|
if (list.length === 0) {
|
|
27
26
|
this.context.stdout.write('No entries\n');
|
|
28
27
|
return formatter([], this.errors);
|
|
@@ -2,7 +2,7 @@ import BaseCommand from '../../BaseCommand.mjs';
|
|
|
2
2
|
declare class EntriesAddCommand extends BaseCommand {
|
|
3
3
|
static paths: string[][];
|
|
4
4
|
static usage: import("clipanion").Usage;
|
|
5
|
-
|
|
5
|
+
requireFavaLib: boolean;
|
|
6
6
|
name: string;
|
|
7
7
|
issuer: string;
|
|
8
8
|
secret: string;
|
|
@@ -3,7 +3,7 @@ import BaseCommand from '../../BaseCommand.mjs';
|
|
|
3
3
|
class EntriesAddCommand extends BaseCommand {
|
|
4
4
|
constructor() {
|
|
5
5
|
super(...arguments);
|
|
6
|
-
this.
|
|
6
|
+
this.requireFavaLib = true;
|
|
7
7
|
this.name = Option.String('--name', { required: true });
|
|
8
8
|
this.issuer = Option.String('--issuer', { required: true });
|
|
9
9
|
this.secret = Option.String('--secret', { required: true });
|
|
@@ -32,10 +32,12 @@ class EntriesAddCommand extends BaseCommand {
|
|
|
32
32
|
],
|
|
33
33
|
}); }
|
|
34
34
|
async exec() {
|
|
35
|
-
await this.
|
|
35
|
+
await this.favaLib.vault.addEntry({
|
|
36
36
|
name: this.name,
|
|
37
37
|
issuer: this.issuer,
|
|
38
38
|
type: 'TOTP',
|
|
39
|
+
match: null,
|
|
40
|
+
matchType: null,
|
|
39
41
|
payload: {
|
|
40
42
|
secret: this.secret,
|
|
41
43
|
period: Number.parseInt(this.period, 10),
|
|
@@ -2,8 +2,8 @@ import BaseListOutputCommand from '../../BaseListOutputCommand.mjs';
|
|
|
2
2
|
declare class EntriesListCommand extends BaseListOutputCommand {
|
|
3
3
|
static paths: string[][];
|
|
4
4
|
static usage: import("clipanion").Usage;
|
|
5
|
-
|
|
5
|
+
requireFavaLib: boolean;
|
|
6
6
|
withTokens: boolean | undefined;
|
|
7
|
-
getList(): import("favalib").EntryMeta[]
|
|
7
|
+
getList(): Promise<import("favalib").EntryMeta[]>;
|
|
8
8
|
}
|
|
9
9
|
export default EntriesListCommand;
|
|
@@ -3,7 +3,7 @@ import BaseListOutputCommand from '../../BaseListOutputCommand.mjs';
|
|
|
3
3
|
class EntriesListCommand extends BaseListOutputCommand {
|
|
4
4
|
constructor() {
|
|
5
5
|
super(...arguments);
|
|
6
|
-
this.
|
|
6
|
+
this.requireFavaLib = true;
|
|
7
7
|
this.withTokens = Option.Boolean('--withTokens', {
|
|
8
8
|
description: 'Include current TOTP tokens in the output',
|
|
9
9
|
});
|
|
@@ -22,12 +22,12 @@ class EntriesListCommand extends BaseListOutputCommand {
|
|
|
22
22
|
['List entries with current TOTP tokens', 'entries list --withTokens'],
|
|
23
23
|
],
|
|
24
24
|
}); }
|
|
25
|
-
getList() {
|
|
25
|
+
async getList() {
|
|
26
26
|
if (this.withTokens) {
|
|
27
|
-
return this.
|
|
27
|
+
return this.favaLib.vault.listEntriesMetas(true);
|
|
28
28
|
}
|
|
29
29
|
else {
|
|
30
|
-
return this.
|
|
30
|
+
return this.favaLib.vault.listEntriesMetas(false);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -2,9 +2,9 @@ import BaseListOutputCommand from '../../BaseListOutputCommand.mjs';
|
|
|
2
2
|
declare class EntriesSearchCommand extends BaseListOutputCommand {
|
|
3
3
|
static paths: string[][];
|
|
4
4
|
static usage: import("clipanion").Usage;
|
|
5
|
-
|
|
5
|
+
requireFavaLib: boolean;
|
|
6
6
|
withTokens: boolean | undefined;
|
|
7
7
|
query: string;
|
|
8
|
-
getList(): import("favalib").EntryMeta[]
|
|
8
|
+
getList(): Promise<import("favalib").EntryMeta[]>;
|
|
9
9
|
}
|
|
10
10
|
export default EntriesSearchCommand;
|
|
@@ -3,7 +3,7 @@ import BaseListOutputCommand from '../../BaseListOutputCommand.mjs';
|
|
|
3
3
|
class EntriesSearchCommand extends BaseListOutputCommand {
|
|
4
4
|
constructor() {
|
|
5
5
|
super(...arguments);
|
|
6
|
-
this.
|
|
6
|
+
this.requireFavaLib = true;
|
|
7
7
|
this.withTokens = Option.Boolean('--withTokens', {
|
|
8
8
|
description: 'Include current TOTP tokens in the output',
|
|
9
9
|
});
|
|
@@ -27,12 +27,12 @@ class EntriesSearchCommand extends BaseListOutputCommand {
|
|
|
27
27
|
],
|
|
28
28
|
],
|
|
29
29
|
}); }
|
|
30
|
-
getList() {
|
|
30
|
+
async getList() {
|
|
31
31
|
if (this.withTokens) {
|
|
32
|
-
return this.
|
|
32
|
+
return this.favaLib.vault.searchEntriesMetas(this.query, true);
|
|
33
33
|
}
|
|
34
34
|
else {
|
|
35
|
-
return this.
|
|
35
|
+
return this.favaLib.vault.searchEntriesMetas(this.query, false);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import BaseCommand from '../../BaseCommand.mjs';
|
|
2
2
|
declare class ExportTextCommand extends BaseCommand {
|
|
3
3
|
static paths: string[][];
|
|
4
|
-
|
|
4
|
+
requireFavaLib: boolean;
|
|
5
5
|
pathOption: string | undefined;
|
|
6
|
-
|
|
6
|
+
passwordSource: string | undefined;
|
|
7
7
|
static usage: import("clipanion").Usage;
|
|
8
8
|
exec(): Promise<{
|
|
9
9
|
success: boolean;
|
|
@@ -3,16 +3,16 @@ import { Option } from 'clipanion';
|
|
|
3
3
|
import * as t from 'typanion';
|
|
4
4
|
import keytar from 'keytar';
|
|
5
5
|
import BaseCommand from '../../BaseCommand.mjs';
|
|
6
|
-
import { password, input } from '@inquirer/prompts';
|
|
6
|
+
import { password as passwordInput, input } from '@inquirer/prompts';
|
|
7
7
|
class ExportTextCommand extends BaseCommand {
|
|
8
8
|
constructor() {
|
|
9
9
|
super(...arguments);
|
|
10
|
-
this.
|
|
10
|
+
this.requireFavaLib = true;
|
|
11
11
|
this.pathOption = Option.String('--path', {
|
|
12
12
|
description: 'File path for the export, if not set will output to stdout',
|
|
13
13
|
});
|
|
14
|
-
this.
|
|
15
|
-
description: 'Source of
|
|
14
|
+
this.passwordSource = Option.String('--password-source', {
|
|
15
|
+
description: 'Source of password, either "stdin" or "stored"',
|
|
16
16
|
validator: t.isEnum(['stdin', 'stored']),
|
|
17
17
|
});
|
|
18
18
|
}
|
|
@@ -25,63 +25,60 @@ class ExportTextCommand extends BaseCommand {
|
|
|
25
25
|
|
|
26
26
|
You can specify a file path for the export using the --path flag.
|
|
27
27
|
|
|
28
|
-
You can specify the
|
|
29
|
-
- "stdin": You'll be prompted to enter a
|
|
30
|
-
- "stored": Will use the previously stored
|
|
28
|
+
You can specify the password source with --password-source flag:
|
|
29
|
+
- "stdin": You'll be prompted to enter a password
|
|
30
|
+
- "stored": Will use the previously stored password, if none is found, will be promted for a password
|
|
31
31
|
|
|
32
|
-
If no
|
|
32
|
+
If no password source is specified, it will export without encryption (no password).
|
|
33
33
|
|
|
34
|
-
WARNING: Exporting your 2FA secrets in plain text (without
|
|
34
|
+
WARNING: Exporting your 2FA secrets in plain text (without password) is not secure.
|
|
35
35
|
`,
|
|
36
36
|
examples: [
|
|
37
37
|
['Export entries as unencrypted text (UNSECURE)', 'export text'],
|
|
38
38
|
['Export to specific file', 'export text --path=/path/to/export.txt'],
|
|
39
|
+
['Export using stored password', 'export text --password-source=stored'],
|
|
39
40
|
[
|
|
40
|
-
'Export
|
|
41
|
-
'export text --
|
|
42
|
-
],
|
|
43
|
-
[
|
|
44
|
-
'Export with manual passphrase entry',
|
|
45
|
-
'export text --passphrase-source=stdin',
|
|
41
|
+
'Export with manual password entry',
|
|
42
|
+
'export text --password-source=stdin',
|
|
46
43
|
],
|
|
47
44
|
],
|
|
48
45
|
}); }
|
|
49
46
|
async exec() {
|
|
50
|
-
const entriesCount = this.
|
|
51
|
-
// Get
|
|
52
|
-
let
|
|
53
|
-
// If
|
|
54
|
-
if (this.
|
|
47
|
+
const entriesCount = this.favaLib.vault.listEntries().length;
|
|
48
|
+
// Get password based on source
|
|
49
|
+
let password = undefined;
|
|
50
|
+
// If password source is "stored", try to get from keytar without prompting
|
|
51
|
+
if (this.passwordSource === 'stored') {
|
|
55
52
|
try {
|
|
56
|
-
|
|
57
|
-
if (!
|
|
58
|
-
this.context.stderr.write('No stored
|
|
59
|
-
|
|
60
|
-
message: 'Enter
|
|
53
|
+
password = await keytar.getPassword('favacli', 'export-password');
|
|
54
|
+
if (!password) {
|
|
55
|
+
this.context.stderr.write('No stored password found. Please enter a password to store.\n');
|
|
56
|
+
password = await passwordInput({
|
|
57
|
+
message: 'Enter password to store and use for encryption:',
|
|
61
58
|
});
|
|
62
|
-
// Store the
|
|
63
|
-
await keytar.setPassword('favacli', 'export-
|
|
64
|
-
this.context.stdout.write('
|
|
59
|
+
// Store the password for future use
|
|
60
|
+
await keytar.setPassword('favacli', 'export-password', password);
|
|
61
|
+
this.context.stdout.write('Password stored.\n');
|
|
65
62
|
}
|
|
66
63
|
}
|
|
67
64
|
catch (error) {
|
|
68
65
|
if (error instanceof Error) {
|
|
69
|
-
this.context.stderr.write(`Failed to access stored
|
|
66
|
+
this.context.stderr.write(`Failed to access stored password: ${error.message}}\n`);
|
|
70
67
|
}
|
|
71
68
|
else {
|
|
72
|
-
this.context.stderr.write(`Failed to access stored
|
|
69
|
+
this.context.stderr.write(`Failed to access stored password: Unknown error\n`);
|
|
73
70
|
}
|
|
74
71
|
return { success: false };
|
|
75
72
|
}
|
|
76
73
|
}
|
|
77
74
|
// Default behavior or explicit "stdin"
|
|
78
|
-
else if (this.
|
|
79
|
-
|
|
80
|
-
message: 'Enter
|
|
75
|
+
else if (this.passwordSource === 'stdin') {
|
|
76
|
+
password = await passwordInput({
|
|
77
|
+
message: 'Enter password to use for encryption:',
|
|
81
78
|
});
|
|
82
79
|
}
|
|
83
|
-
// Add warning and confirmation if no
|
|
84
|
-
else if (!this.
|
|
80
|
+
// Add warning and confirmation if no password is provided
|
|
81
|
+
else if (!this.passwordSource) {
|
|
85
82
|
this.context.stderr.write('WARNING: You are about to export 2FA secrets in plain text without encryption.\n');
|
|
86
83
|
this.context.stderr.write('This is NOT SECURE and could expose your 2FA secrets if the file is accessed by others.\n');
|
|
87
84
|
const confirm = await input({
|
|
@@ -92,7 +89,7 @@ class ExportTextCommand extends BaseCommand {
|
|
|
92
89
|
return { success: false };
|
|
93
90
|
}
|
|
94
91
|
}
|
|
95
|
-
const content = await this.
|
|
92
|
+
const content = await this.favaLib.exportImport.exportEntries('text', password, true);
|
|
96
93
|
if (this.pathOption) {
|
|
97
94
|
try {
|
|
98
95
|
await fs.writeFile(this.pathOption, content);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import BaseCommand from '../../BaseCommand.mjs';
|
|
2
2
|
declare class ConnectCommand extends BaseCommand {
|
|
3
3
|
static paths: string[][];
|
|
4
|
-
|
|
4
|
+
requireFavaLib: boolean;
|
|
5
5
|
static usage: import("clipanion").Usage;
|
|
6
6
|
exec(): Promise<{
|
|
7
7
|
success: boolean;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { input } from '@inquirer/prompts';
|
|
2
2
|
import BaseCommand from '../../BaseCommand.mjs';
|
|
3
|
-
import {
|
|
3
|
+
import { FavaLibEvent } from 'favalib';
|
|
4
4
|
class ConnectCommand extends BaseCommand {
|
|
5
5
|
constructor() {
|
|
6
6
|
super(...arguments);
|
|
7
|
-
this.
|
|
7
|
+
this.requireFavaLib = true;
|
|
8
8
|
}
|
|
9
9
|
static { this.paths = [['sync', 'connect']]; }
|
|
10
10
|
static { this.usage = BaseCommand.Usage({
|
|
@@ -18,18 +18,24 @@ class ConnectCommand extends BaseCommand {
|
|
|
18
18
|
examples: [['Connect to an existing vault', 'sync connect']],
|
|
19
19
|
}); }
|
|
20
20
|
async exec() {
|
|
21
|
-
if (!this.
|
|
21
|
+
if (!this.favaLib.sync) {
|
|
22
22
|
throw new Error('No server url set');
|
|
23
23
|
}
|
|
24
24
|
const connectionString = await input({
|
|
25
25
|
message: 'Enter connection string:',
|
|
26
26
|
});
|
|
27
|
+
const friendlyName = (await input({
|
|
28
|
+
message: 'Enter a friendly name for this device (optional):',
|
|
29
|
+
})).trim();
|
|
30
|
+
if (friendlyName) {
|
|
31
|
+
await this.favaLib.setDeviceFriendlyName(friendlyName);
|
|
32
|
+
}
|
|
27
33
|
const connectFinished = new Promise((resolve) => {
|
|
28
|
-
this.
|
|
34
|
+
this.favaLib.addEventListener(FavaLibEvent.ConnectToExistingVaultFinished, () => {
|
|
29
35
|
resolve();
|
|
30
36
|
});
|
|
31
37
|
});
|
|
32
|
-
await this.
|
|
38
|
+
await this.favaLib.sync.respondToAddDeviceFlow(connectionString, 'text');
|
|
33
39
|
await connectFinished;
|
|
34
40
|
return { success: true };
|
|
35
41
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import BaseCommand from '../../BaseCommand.mjs';
|
|
2
2
|
declare class GetInfoCommand extends BaseCommand {
|
|
3
3
|
static paths: string[][];
|
|
4
|
-
|
|
4
|
+
requireFavaLib: boolean;
|
|
5
5
|
static usage: import("clipanion").Usage;
|
|
6
6
|
exec(): Promise<{
|
|
7
7
|
connected: boolean;
|
|
@@ -2,7 +2,7 @@ import BaseCommand from '../../BaseCommand.mjs';
|
|
|
2
2
|
class GetInfoCommand extends BaseCommand {
|
|
3
3
|
constructor() {
|
|
4
4
|
super(...arguments);
|
|
5
|
-
this.
|
|
5
|
+
this.requireFavaLib = true;
|
|
6
6
|
}
|
|
7
7
|
static { this.paths = [['sync', 'get-info']]; }
|
|
8
8
|
static { this.usage = BaseCommand.Usage({
|
|
@@ -12,12 +12,12 @@ class GetInfoCommand extends BaseCommand {
|
|
|
12
12
|
examples: [['Get sync info', 'sync get-info']],
|
|
13
13
|
}); }
|
|
14
14
|
exec() {
|
|
15
|
-
if (!this.
|
|
15
|
+
if (!this.favaLib.sync) {
|
|
16
16
|
throw new Error('No server url set');
|
|
17
17
|
}
|
|
18
|
-
const connected = this.
|
|
19
|
-
const friendlyName = this.
|
|
20
|
-
const serverUrl = this.
|
|
18
|
+
const connected = this.favaLib.sync.webSocketConnected || false;
|
|
19
|
+
const friendlyName = this.favaLib.meta.deviceFriendlyName || '(none)';
|
|
20
|
+
const serverUrl = this.favaLib.sync.serverUrl || '(none)';
|
|
21
21
|
this.output(`Connected: ${connected ? 'yes' : 'no'}\n`);
|
|
22
22
|
this.output(`Device friendly name: ${friendlyName}\n`);
|
|
23
23
|
this.output(`Sync server URL: ${serverUrl}\n`);
|
|
@@ -2,7 +2,7 @@ import BaseCommand from '../../BaseCommand.mjs';
|
|
|
2
2
|
import type { PublicSyncDevice } from 'favalib';
|
|
3
3
|
declare class ListDevicesCommand extends BaseCommand {
|
|
4
4
|
static paths: string[][];
|
|
5
|
-
|
|
5
|
+
requireFavaLib: boolean;
|
|
6
6
|
static usage: import("clipanion").Usage;
|
|
7
7
|
exec(): Promise<{
|
|
8
8
|
devices: PublicSyncDevice[];
|
|
@@ -3,7 +3,7 @@ import BaseCommand from '../../BaseCommand.mjs';
|
|
|
3
3
|
class ListDevicesCommand extends BaseCommand {
|
|
4
4
|
constructor() {
|
|
5
5
|
super(...arguments);
|
|
6
|
-
this.
|
|
6
|
+
this.requireFavaLib = true;
|
|
7
7
|
}
|
|
8
8
|
static { this.paths = [['sync', 'list-devices']]; }
|
|
9
9
|
static { this.usage = BaseCommand.Usage({
|
|
@@ -13,10 +13,10 @@ class ListDevicesCommand extends BaseCommand {
|
|
|
13
13
|
examples: [['List all synced devices', 'sync list-devices']],
|
|
14
14
|
}); }
|
|
15
15
|
async exec() {
|
|
16
|
-
if (!this.
|
|
16
|
+
if (!this.favaLib.sync) {
|
|
17
17
|
throw new Error('No server url set');
|
|
18
18
|
}
|
|
19
|
-
const devices = this.
|
|
19
|
+
const devices = this.favaLib.sync.getSyncDevices();
|
|
20
20
|
const headers = [
|
|
21
21
|
{ value: 'deviceId', align: 'left', width: 38 },
|
|
22
22
|
{ value: 'deviceType', align: 'left', width: 38 },
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import BaseCommand from '../../BaseCommand.mjs';
|
|
2
|
+
declare class RemoveDeviceCommand extends BaseCommand {
|
|
3
|
+
static paths: string[][];
|
|
4
|
+
requireFavaLib: boolean;
|
|
5
|
+
static usage: import("clipanion").Usage;
|
|
6
|
+
deviceId: string | undefined;
|
|
7
|
+
force: boolean | undefined;
|
|
8
|
+
exec(): Promise<{
|
|
9
|
+
success: boolean;
|
|
10
|
+
cancelled?: undefined;
|
|
11
|
+
} | {
|
|
12
|
+
success: boolean;
|
|
13
|
+
cancelled: boolean;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
export default RemoveDeviceCommand;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Option } from 'clipanion';
|
|
2
|
+
import { select, confirm } from '@inquirer/prompts';
|
|
3
|
+
import BaseCommand from '../../BaseCommand.mjs';
|
|
4
|
+
class RemoveDeviceCommand extends BaseCommand {
|
|
5
|
+
constructor() {
|
|
6
|
+
super(...arguments);
|
|
7
|
+
this.requireFavaLib = true;
|
|
8
|
+
this.deviceId = Option.String('--device-id', { required: false });
|
|
9
|
+
this.force = Option.Boolean('--force', {
|
|
10
|
+
description: 'Remove without confirmation',
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
static { this.paths = [['sync', 'remove-device']]; }
|
|
14
|
+
static { this.usage = BaseCommand.Usage({
|
|
15
|
+
category: 'Sync',
|
|
16
|
+
description: 'Remove a device from the vault sync set',
|
|
17
|
+
details: `Removes a synced device from your vault. The removal is synced to all other
|
|
18
|
+
connected devices. Pass --device-id to skip the interactive picker.`,
|
|
19
|
+
examples: [
|
|
20
|
+
['Pick a device to remove interactively', 'sync remove-device'],
|
|
21
|
+
['Remove a specific device', 'sync remove-device --device-id <id>'],
|
|
22
|
+
],
|
|
23
|
+
}); }
|
|
24
|
+
async exec() {
|
|
25
|
+
if (!this.favaLib.sync) {
|
|
26
|
+
throw new Error('No server url set');
|
|
27
|
+
}
|
|
28
|
+
const devices = this.favaLib.sync.getSyncDevices();
|
|
29
|
+
if (devices.length === 0) {
|
|
30
|
+
this.output('No devices to remove.\n');
|
|
31
|
+
return { success: false };
|
|
32
|
+
}
|
|
33
|
+
// Resolve target device
|
|
34
|
+
let targetId;
|
|
35
|
+
if (this.deviceId) {
|
|
36
|
+
const match = devices.find((d) => d.deviceId === this.deviceId);
|
|
37
|
+
if (!match) {
|
|
38
|
+
throw new Error(`No synced device found with id: ${this.deviceId}`);
|
|
39
|
+
}
|
|
40
|
+
targetId = match.deviceId;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
targetId = await select({
|
|
44
|
+
message: 'Select a device to remove:',
|
|
45
|
+
choices: devices.map((d) => ({
|
|
46
|
+
name: `${d.deviceFriendlyName ?? '(no name)'} (${d.deviceType ?? 'unknown'}) — ${d.deviceId}`,
|
|
47
|
+
value: d.deviceId,
|
|
48
|
+
})),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
// Confirmation (skippable with --force)
|
|
52
|
+
const target = devices.find((d) => d.deviceId === targetId);
|
|
53
|
+
const label = target?.deviceFriendlyName ?? targetId;
|
|
54
|
+
if (!this.force) {
|
|
55
|
+
const ok = await confirm({
|
|
56
|
+
message: `Remove "${label}"? This will sync to all devices.`,
|
|
57
|
+
default: false,
|
|
58
|
+
});
|
|
59
|
+
if (!ok) {
|
|
60
|
+
this.output('Device removal cancelled.\n');
|
|
61
|
+
return { success: false, cancelled: true };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
await this.favaLib.removeSyncDevice(targetId);
|
|
65
|
+
this.output(`Removed device: ${label}\n`);
|
|
66
|
+
return { success: true };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export default RemoveDeviceCommand;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import BaseCommand from '../../BaseCommand.mjs';
|
|
2
2
|
declare class ResilverCommand extends BaseCommand {
|
|
3
3
|
static paths: string[][];
|
|
4
|
-
|
|
4
|
+
requireFavaLib: boolean;
|
|
5
5
|
static usage: import("clipanion").Usage;
|
|
6
6
|
exec(): Promise<{
|
|
7
7
|
success: boolean;
|
|
@@ -2,7 +2,7 @@ import BaseCommand from '../../BaseCommand.mjs';
|
|
|
2
2
|
class ResilverCommand extends BaseCommand {
|
|
3
3
|
constructor() {
|
|
4
4
|
super(...arguments);
|
|
5
|
-
this.
|
|
5
|
+
this.requireFavaLib = true;
|
|
6
6
|
}
|
|
7
7
|
static { this.paths = [['sync', 'resilver']]; }
|
|
8
8
|
static { this.usage = BaseCommand.Usage({
|
|
@@ -12,10 +12,10 @@ class ResilverCommand extends BaseCommand {
|
|
|
12
12
|
examples: [['Resilver a vault', 'sync resilver']],
|
|
13
13
|
}); }
|
|
14
14
|
async exec() {
|
|
15
|
-
if (!this.
|
|
15
|
+
if (!this.favaLib.sync) {
|
|
16
16
|
throw new Error('No server url set');
|
|
17
17
|
}
|
|
18
|
-
await this.
|
|
18
|
+
await this.favaLib.sync.requestResilver();
|
|
19
19
|
// TODO: do this based on events
|
|
20
20
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
21
21
|
return { success: true };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import BaseCommand from '../../BaseCommand.mjs';
|
|
2
2
|
declare class SetFriendlyNameCommand extends BaseCommand {
|
|
3
3
|
static paths: string[][];
|
|
4
|
-
|
|
4
|
+
requireFavaLib: boolean;
|
|
5
5
|
static usage: import("clipanion").Usage;
|
|
6
6
|
name: string;
|
|
7
7
|
exec(): Promise<{
|
|
@@ -3,7 +3,7 @@ import BaseCommand from '../../BaseCommand.mjs';
|
|
|
3
3
|
class SetFriendlyNameCommand extends BaseCommand {
|
|
4
4
|
constructor() {
|
|
5
5
|
super(...arguments);
|
|
6
|
-
this.
|
|
6
|
+
this.requireFavaLib = true;
|
|
7
7
|
this.name = Option.String('--name', { required: true });
|
|
8
8
|
}
|
|
9
9
|
static { this.paths = [['sync', 'set-friendly-name']]; }
|
|
@@ -17,10 +17,10 @@ class SetFriendlyNameCommand extends BaseCommand {
|
|
|
17
17
|
],
|
|
18
18
|
}); }
|
|
19
19
|
async exec() {
|
|
20
|
-
if (!this.
|
|
20
|
+
if (!this.favaLib.sync) {
|
|
21
21
|
throw new Error('No server url set');
|
|
22
22
|
}
|
|
23
|
-
await this.
|
|
23
|
+
await this.favaLib.setDeviceFriendlyName(this.name);
|
|
24
24
|
this.output(`Device name set to: ${this.name}\n`);
|
|
25
25
|
return { success: true };
|
|
26
26
|
}
|
|
@@ -2,7 +2,7 @@ import BaseCommand from '../../BaseCommand.mjs';
|
|
|
2
2
|
declare class SetServerUrlCommand extends BaseCommand {
|
|
3
3
|
static paths: string[][];
|
|
4
4
|
static usage: import("clipanion").Usage;
|
|
5
|
-
|
|
5
|
+
requireFavaLib: boolean;
|
|
6
6
|
serverUrl: string;
|
|
7
7
|
force: boolean | undefined;
|
|
8
8
|
exec(): Promise<{
|
|
@@ -3,7 +3,7 @@ import BaseCommand from '../../BaseCommand.mjs';
|
|
|
3
3
|
class SetServerUrlCommand extends BaseCommand {
|
|
4
4
|
constructor() {
|
|
5
5
|
super(...arguments);
|
|
6
|
-
this.
|
|
6
|
+
this.requireFavaLib = true;
|
|
7
7
|
this.serverUrl = Option.String({ required: true });
|
|
8
8
|
this.force = Option.Boolean('--force', {
|
|
9
9
|
description: 'Set serverUrl even if connection fails',
|
|
@@ -23,7 +23,7 @@ class SetServerUrlCommand extends BaseCommand {
|
|
|
23
23
|
],
|
|
24
24
|
}); }
|
|
25
25
|
async exec() {
|
|
26
|
-
await this.
|
|
26
|
+
await this.favaLib.setSyncServerUrl(this.serverUrl, this.force);
|
|
27
27
|
return { success: true };
|
|
28
28
|
}
|
|
29
29
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import BaseCommand from '../../BaseCommand.mjs';
|
|
2
2
|
declare class VaultCreateCommand extends BaseCommand {
|
|
3
3
|
static paths: string[][];
|
|
4
|
-
|
|
4
|
+
requireFavaLib: boolean;
|
|
5
5
|
static usage: import("clipanion").Usage;
|
|
6
6
|
exec(): Promise<{
|
|
7
7
|
success: boolean;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import keytar from 'keytar';
|
|
3
3
|
import BaseCommand from '../../BaseCommand.mjs';
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import { password } from '@inquirer/prompts';
|
|
7
|
-
const cryptoLib = new NodeCryptoProvider();
|
|
4
|
+
import { getFavaLibVaultCreationUtils } from 'favalib';
|
|
5
|
+
import NodePlatformProvider from 'favalib/platformProviders/node';
|
|
6
|
+
import { password as passwordInput } from '@inquirer/prompts';
|
|
8
7
|
class VaultCreateCommand extends BaseCommand {
|
|
9
8
|
constructor() {
|
|
10
9
|
super(...arguments);
|
|
11
|
-
this.
|
|
10
|
+
this.requireFavaLib = false;
|
|
12
11
|
}
|
|
13
12
|
static { this.paths = [['vault', 'create']]; }
|
|
14
13
|
static { this.usage = BaseCommand.Usage({
|
|
@@ -17,28 +16,28 @@ class VaultCreateCommand extends BaseCommand {
|
|
|
17
16
|
details: `
|
|
18
17
|
This command creates a new encrypted vault file to store your 2FA entries.
|
|
19
18
|
|
|
20
|
-
You will be prompted to enter a
|
|
21
|
-
The
|
|
19
|
+
You will be prompted to enter a password that will be used to encrypt the vault.
|
|
20
|
+
The password will be securely stored in your system's keychain.
|
|
22
21
|
`,
|
|
23
22
|
examples: [['Create a new vault', 'vault create']],
|
|
24
23
|
}); }
|
|
25
24
|
async exec() {
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
message: 'Enter your vault
|
|
25
|
+
const favaLibVaultCreationUtils = getFavaLibVaultCreationUtils(NodePlatformProvider, 'cli', ['cli'], (newLockedRepresentationString) => fs.writeFile(this.settings.vaultLocation, newLockedRepresentationString));
|
|
26
|
+
const password = (await passwordInput({
|
|
27
|
+
message: 'Enter your vault password:',
|
|
29
28
|
mask: '*',
|
|
30
29
|
}));
|
|
31
|
-
const
|
|
32
|
-
message: 'Repeat your vault
|
|
30
|
+
const repeatPassword = (await passwordInput({
|
|
31
|
+
message: 'Repeat your vault password:',
|
|
33
32
|
mask: '*',
|
|
34
33
|
}));
|
|
35
|
-
if (
|
|
36
|
-
throw new Error("
|
|
34
|
+
if (password != repeatPassword) {
|
|
35
|
+
throw new Error("Passwords don't match");
|
|
37
36
|
}
|
|
38
|
-
const {
|
|
37
|
+
const { favaLib } = await favaLibVaultCreationUtils.createNewFavaLibVault(password);
|
|
39
38
|
await Promise.all([
|
|
40
|
-
|
|
41
|
-
keytar.setPassword('favacli', 'vault-
|
|
39
|
+
favaLib.storage.forceSave(),
|
|
40
|
+
keytar.setPassword('favacli', 'vault-password', password),
|
|
42
41
|
]);
|
|
43
42
|
return { success: true };
|
|
44
43
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import BaseCommand from '../../BaseCommand.mjs';
|
|
2
2
|
declare class VaultDeleteCommand extends BaseCommand {
|
|
3
3
|
static paths: string[][];
|
|
4
|
-
|
|
4
|
+
requireFavaLib: boolean;
|
|
5
5
|
static usage: import("clipanion").Usage;
|
|
6
6
|
force: boolean | undefined;
|
|
7
7
|
exec(): Promise<{
|
|
@@ -5,7 +5,7 @@ import BaseCommand from '../../BaseCommand.mjs';
|
|
|
5
5
|
class VaultDeleteCommand extends BaseCommand {
|
|
6
6
|
constructor() {
|
|
7
7
|
super(...arguments);
|
|
8
|
-
this.
|
|
8
|
+
this.requireFavaLib = false;
|
|
9
9
|
this.force = Option.Boolean('--force', {
|
|
10
10
|
description: 'Delete the vault without confirmation',
|
|
11
11
|
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import BaseCommand from '../../BaseCommand.mjs';
|
|
2
|
+
declare class VaultRestorePasswordCommand extends BaseCommand {
|
|
3
|
+
static paths: string[][];
|
|
4
|
+
requireFavaLib: boolean;
|
|
5
|
+
static usage: import("clipanion").Usage;
|
|
6
|
+
exec(): Promise<{
|
|
7
|
+
success: boolean;
|
|
8
|
+
}>;
|
|
9
|
+
}
|
|
10
|
+
export default VaultRestorePasswordCommand;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import keytar from 'keytar';
|
|
2
|
+
import BaseCommand from '../../BaseCommand.mjs';
|
|
3
|
+
import { getFavaLibVaultCreationUtils } from 'favalib';
|
|
4
|
+
import NodePlatformProvider from 'favalib/platformProviders/node';
|
|
5
|
+
import { password as passwordInput } from '@inquirer/prompts';
|
|
6
|
+
class VaultRestorePasswordCommand extends BaseCommand {
|
|
7
|
+
constructor() {
|
|
8
|
+
super(...arguments);
|
|
9
|
+
this.requireFavaLib = false;
|
|
10
|
+
}
|
|
11
|
+
static { this.paths = [['vault', 'restore-password']]; }
|
|
12
|
+
static { this.usage = BaseCommand.Usage({
|
|
13
|
+
category: 'Vault',
|
|
14
|
+
description: "Store an existing vault's password in the system keychain",
|
|
15
|
+
details: `
|
|
16
|
+
Use this when the vault file exists on this device but its password is not
|
|
17
|
+
stored in the system keychain (e.g. after copying the vault from another
|
|
18
|
+
device, or after the keychain was cleared).
|
|
19
|
+
|
|
20
|
+
You will be prompted for the vault password. It is verified against the
|
|
21
|
+
existing vault and, if correct, stored in your system's keychain so other
|
|
22
|
+
commands can decrypt the vault. This does NOT change or re-encrypt the vault.
|
|
23
|
+
`,
|
|
24
|
+
examples: [
|
|
25
|
+
['Restore the vault password in the keychain', 'vault restore-password'],
|
|
26
|
+
],
|
|
27
|
+
}); }
|
|
28
|
+
async exec() {
|
|
29
|
+
if (!this.lockedRepresentationString) {
|
|
30
|
+
throw new Error(`No vault found at "${this.settings.vaultLocation}". Run "vault create" to create one.`);
|
|
31
|
+
}
|
|
32
|
+
const password = (await passwordInput({
|
|
33
|
+
message: 'Enter your vault password:',
|
|
34
|
+
mask: '*',
|
|
35
|
+
}));
|
|
36
|
+
// No save function is passed: we only load the vault to validate the
|
|
37
|
+
// password, we never write it back.
|
|
38
|
+
const favaLibVaultCreationUtils = getFavaLibVaultCreationUtils(NodePlatformProvider, 'cli', ['cli']);
|
|
39
|
+
// Validate the entered password by decrypting the existing vault (offline).
|
|
40
|
+
let favaLib;
|
|
41
|
+
try {
|
|
42
|
+
favaLib =
|
|
43
|
+
await favaLibVaultCreationUtils.loadFavaLibFromLockedRepesentation(this.lockedRepresentationString, password);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
if (err instanceof Error && err.message === 'Invalid password') {
|
|
47
|
+
throw new Error('Incorrect password — nothing was stored.');
|
|
48
|
+
}
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
|
+
await keytar.setPassword('favacli', 'vault-password', password);
|
|
52
|
+
// Loading may have opened a sync connection; close it so the process exits.
|
|
53
|
+
if (favaLib.sync) {
|
|
54
|
+
favaLib.sync.closeServerConnection();
|
|
55
|
+
}
|
|
56
|
+
this.output('Vault password stored in the system keychain.\n');
|
|
57
|
+
return { success: true };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export default VaultRestorePasswordCommand;
|
package/build/main.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Cli, Builtins } from 'clipanion';
|
|
3
3
|
import VaultCreateCommand from './commands/vault/create.mjs';
|
|
4
4
|
import VaultDeleteCommand from './commands/vault/delete.mjs';
|
|
5
|
+
import VaultRestorePasswordCommand from './commands/vault/restorePassword.mjs';
|
|
5
6
|
import EntriesAddCommand from './commands/entries/add.mjs';
|
|
6
7
|
import EntriesListCommand from './commands/entries/list.mjs';
|
|
7
8
|
import EntriesSearchCommand from './commands/entries/search.mjs';
|
|
@@ -9,6 +10,7 @@ import SyncSetServerUrlCommand from './commands/sync/setServerUrl.mjs';
|
|
|
9
10
|
import SyncConnect from './commands/sync/connect.mjs';
|
|
10
11
|
import SyncResilver from './commands/sync/resilver.mjs';
|
|
11
12
|
import SyncListDevices from './commands/sync/listDevices.mjs';
|
|
13
|
+
import SyncRemoveDeviceCommand from './commands/sync/removeDevice.mjs';
|
|
12
14
|
import SyncSetFriendlyNameCommand from './commands/sync/setFriendlyName.mjs';
|
|
13
15
|
import ExportTextCommand from './commands/export/text.mjs';
|
|
14
16
|
import SyncGetInfoCommand from './commands/sync/getInfo.mjs';
|
|
@@ -21,10 +23,11 @@ const [, , ...args] = process.argv;
|
|
|
21
23
|
const cli = new Cli({
|
|
22
24
|
binaryLabel: 'FavaCli',
|
|
23
25
|
binaryName: `favacli`,
|
|
24
|
-
binaryVersion: '0.0.
|
|
26
|
+
binaryVersion: '0.0.23',
|
|
25
27
|
});
|
|
26
28
|
cli.register(VaultCreateCommand);
|
|
27
29
|
cli.register(VaultDeleteCommand);
|
|
30
|
+
cli.register(VaultRestorePasswordCommand);
|
|
28
31
|
cli.register(EntriesAddCommand);
|
|
29
32
|
cli.register(EntriesListCommand);
|
|
30
33
|
cli.register(EntriesSearchCommand);
|
|
@@ -32,6 +35,7 @@ cli.register(SyncSetServerUrlCommand);
|
|
|
32
35
|
cli.register(SyncConnect);
|
|
33
36
|
cli.register(SyncResilver);
|
|
34
37
|
cli.register(SyncListDevices);
|
|
38
|
+
cli.register(SyncRemoveDeviceCommand);
|
|
35
39
|
cli.register(ExportTextCommand);
|
|
36
40
|
cli.register(SyncSetFriendlyNameCommand);
|
|
37
41
|
cli.register(SyncGetInfoCommand);
|
package/build/test.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/build/test.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getFavaLibVaultCreationUtils, } from 'favalib';
|
|
2
|
+
// almost empty for now
|
|
3
|
+
const SwiftPlatformProvider = {
|
|
4
|
+
CryptoLib: class {
|
|
5
|
+
},
|
|
6
|
+
};
|
|
7
|
+
const favaLibVaultCreationUtils = getFavaLibVaultCreationUtils(SwiftPlatformProvider, 'ios-app', ['iphone', 'apple'], () => {
|
|
8
|
+
/* no-op for now */
|
|
9
|
+
});
|
|
10
|
+
const getPasswordStrength = async (password) => {
|
|
11
|
+
const result = await favaLibVaultCreationUtils.getPasswordStrength(password);
|
|
12
|
+
return result.score;
|
|
13
|
+
};
|
|
14
|
+
// example usage
|
|
15
|
+
console.log(await getPasswordStrength('welcome123'));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { type LockedRepresentationString } from 'favalib';
|
|
2
2
|
import { Settings } from './init.mjs';
|
|
3
|
-
declare const loadVault: (vaultData: LockedRepresentationString, settings: Settings, addError: (err: Error) => void, verbose?: boolean) => Promise<import("favalib").
|
|
3
|
+
declare const loadVault: (vaultData: LockedRepresentationString, settings: Settings, addError: (err: Error) => void, verbose?: boolean) => Promise<import("favalib").FavaLib>;
|
|
4
4
|
export default loadVault;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import keytar from 'keytar';
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
const cryptoLib = new NodeCryptoProvider();
|
|
3
|
+
import { getFavaLibVaultCreationUtils, FavaLibEvent, } from 'favalib';
|
|
4
|
+
import NodePlatformProvider from 'favalib/platformProviders/node';
|
|
6
5
|
const loadVault = async (vaultData, settings, addError, verbose = false) => {
|
|
7
6
|
const saveFunction = async (newLockedRepresentationString) => {
|
|
8
7
|
const tempFile = `${settings.vaultLocation}.tmp`;
|
|
@@ -30,10 +29,25 @@ const loadVault = async (vaultData, settings, addError, verbose = false) => {
|
|
|
30
29
|
throw error;
|
|
31
30
|
}
|
|
32
31
|
};
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
const favaLibVaultCreationUtils = getFavaLibVaultCreationUtils(NodePlatformProvider, 'cli', ['cli'], saveFunction);
|
|
33
|
+
let storedPassword;
|
|
34
|
+
try {
|
|
35
|
+
storedPassword = await keytar.getPassword('favacli', 'vault-password');
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
throw new Error(`Failed to read the vault password from the system keychain: ${err instanceof Error ? err.message : String(err)}. Make sure your OS keychain service is available (on Linux this requires a ` +
|
|
39
|
+
`Secret Service provider such as gnome-keyring or KWallet via libsecret).`);
|
|
40
|
+
}
|
|
41
|
+
if (!storedPassword) {
|
|
42
|
+
throw new Error(`No vault password found in the system keychain. The vault file at ` +
|
|
43
|
+
`"${settings.vaultLocation}" exists, but the password used to decrypt it is not ` +
|
|
44
|
+
`stored on this device. This usually happens when the vault was copied from another ` +
|
|
45
|
+
`device or the keychain entry was removed. Run "favacli vault restore-password" to ` +
|
|
46
|
+
`re-store it.`);
|
|
47
|
+
}
|
|
48
|
+
const password = storedPassword;
|
|
49
|
+
const favaLib = await favaLibVaultCreationUtils.loadFavaLibFromLockedRepesentation(vaultData, password);
|
|
50
|
+
favaLib.addEventListener(FavaLibEvent.Log, (ev) => {
|
|
37
51
|
if (ev.detail.severity === 'warning') {
|
|
38
52
|
addError(new Error(ev.detail.message));
|
|
39
53
|
return;
|
|
@@ -42,7 +56,7 @@ const loadVault = async (vaultData, settings, addError, verbose = false) => {
|
|
|
42
56
|
console.log(ev.detail.message);
|
|
43
57
|
}
|
|
44
58
|
});
|
|
45
|
-
await
|
|
46
|
-
return
|
|
59
|
+
await favaLib.ready;
|
|
60
|
+
return favaLib;
|
|
47
61
|
};
|
|
48
62
|
export default loadVault;
|
package/package.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "favacli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.23",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "",
|
|
7
7
|
"license": "ISC",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@inquirer/prompts": "^
|
|
10
|
-
"bufferutil": "^4.0
|
|
9
|
+
"@inquirer/prompts": "^8.4.3",
|
|
10
|
+
"bufferutil": "^4.1.0",
|
|
11
11
|
"clipanion": "^4.0.0-rc.4",
|
|
12
|
-
"env-paths": "^
|
|
13
|
-
"favalib": "^0.0.11",
|
|
12
|
+
"env-paths": "^4.0.0",
|
|
14
13
|
"keytar": "^7.9.0",
|
|
15
|
-
"tty-table": "^
|
|
16
|
-
"typanion": "^3.14.0"
|
|
14
|
+
"tty-table": "^5.0.0",
|
|
15
|
+
"typanion": "^3.14.0",
|
|
16
|
+
"favalib": "0.0.17"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"type-fest": "^
|
|
19
|
+
"type-fest": "^5.6.0"
|
|
20
20
|
},
|
|
21
21
|
"bin": {
|
|
22
22
|
"favacli": "build/main.mjs"
|
|
@@ -27,4 +27,4 @@
|
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": ">=20"
|
|
29
29
|
}
|
|
30
|
-
}
|
|
30
|
+
}
|