favalib 0.0.8 → 0.0.10
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/Command/commandConstructors.d.mts +2 -2
- package/build/Command/commandConstructors.mjs +2 -2
- package/build/Command/commands/AddSyncDeviceCommand.d.mts +2 -1
- package/build/Command/commands/{ChangeSyncDeviceMetaCommand.d.mts → ChangeDeviceInfoCommand.d.mts} +13 -8
- package/build/Command/commands/ChangeDeviceInfoCommand.mjs +78 -0
- package/build/CryptoProviders/browser/index.d.mts +1 -1
- package/build/CryptoProviders/node/index.d.mts +1 -1
- package/build/TwoFaLib.d.mts +20 -6
- package/build/TwoFaLib.mjs +39 -9
- package/build/interfaces/CommandTypes.d.mts +4 -4
- package/build/interfaces/FavaMeta.d.mts +5 -0
- package/build/interfaces/FavaMeta.mjs +1 -0
- package/build/interfaces/SaveFunction.d.mts +1 -2
- package/build/interfaces/SyncTypes.d.mts +15 -6
- package/build/interfaces/Vault.d.mts +2 -1
- package/build/main.d.mts +3 -3
- package/build/subclasses/PersistentStorageManager.d.mts +10 -8
- package/build/subclasses/PersistentStorageManager.mjs +8 -5
- package/build/subclasses/StorageOperationsManager.d.mts +2 -1
- package/build/subclasses/StorageOperationsManager.mjs +1 -0
- package/build/subclasses/SyncManager.d.mts +16 -9
- package/build/subclasses/SyncManager.mjs +59 -22
- package/build/utils/creationUtils.d.mts +1 -1
- package/build/utils/creationUtils.mjs +7 -2
- package/build/utils/exportImportUtils.mjs +1 -0
- package/build/utils/syncUtils.d.mts +1 -1
- package/package.json +1 -1
- package/build/Command/commands/ChangeSyncDeviceMetaCommand.mjs +0 -37
|
@@ -2,12 +2,12 @@ import AddEntryCommand from './commands/AddEntryCommand.mjs';
|
|
|
2
2
|
import AddSyncDeviceCommand from './commands/AddSyncDeviceCommand.mjs';
|
|
3
3
|
import DeleteEntryCommand from './commands/DeleteEntryCommand.mjs';
|
|
4
4
|
import UpdateEntryCommand from './commands/UpdateEntryCommand.mjs';
|
|
5
|
-
import
|
|
5
|
+
import ChangeSyncDeviceInfoCommand from './commands/ChangeDeviceInfoCommand.mjs';
|
|
6
6
|
declare const commandConstructors: {
|
|
7
7
|
AddEntry: typeof AddEntryCommand;
|
|
8
8
|
DeleteEntry: typeof DeleteEntryCommand;
|
|
9
9
|
UpdateEntry: typeof UpdateEntryCommand;
|
|
10
10
|
AddSyncDevice: typeof AddSyncDeviceCommand;
|
|
11
|
-
|
|
11
|
+
ChangeSyncDeviceInfo: typeof ChangeSyncDeviceInfoCommand;
|
|
12
12
|
};
|
|
13
13
|
export default commandConstructors;
|
|
@@ -2,12 +2,12 @@ import AddEntryCommand from './commands/AddEntryCommand.mjs';
|
|
|
2
2
|
import AddSyncDeviceCommand from './commands/AddSyncDeviceCommand.mjs';
|
|
3
3
|
import DeleteEntryCommand from './commands/DeleteEntryCommand.mjs';
|
|
4
4
|
import UpdateEntryCommand from './commands/UpdateEntryCommand.mjs';
|
|
5
|
-
import
|
|
5
|
+
import ChangeSyncDeviceInfoCommand from './commands/ChangeDeviceInfoCommand.mjs';
|
|
6
6
|
const commandConstructors = {
|
|
7
7
|
AddEntry: AddEntryCommand,
|
|
8
8
|
DeleteEntry: DeleteEntryCommand,
|
|
9
9
|
UpdateEntry: UpdateEntryCommand,
|
|
10
10
|
AddSyncDevice: AddSyncDeviceCommand,
|
|
11
|
-
|
|
11
|
+
ChangeSyncDeviceInfo: ChangeSyncDeviceInfoCommand,
|
|
12
12
|
};
|
|
13
13
|
export default commandConstructors;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type TwoFaLibMediator from '../../TwoFaLibMediator.mjs';
|
|
2
2
|
import Command from '../BaseCommand.mjs';
|
|
3
|
-
import type { DeviceId } from '../../interfaces/SyncTypes.mjs';
|
|
3
|
+
import type { DeviceId, DeviceInfo } from '../../interfaces/SyncTypes.mjs';
|
|
4
4
|
import type { PublicKey } from '../../interfaces/CryptoLib.mjs';
|
|
5
5
|
export interface AddSyncDeviceData {
|
|
6
6
|
deviceId: DeviceId;
|
|
7
7
|
publicKey: PublicKey;
|
|
8
|
+
deviceInfo: DeviceInfo;
|
|
8
9
|
}
|
|
9
10
|
/**
|
|
10
11
|
* Represents a command that when executed add an entry to the vault.
|
package/build/Command/commands/{ChangeSyncDeviceMetaCommand.d.mts → ChangeDeviceInfoCommand.d.mts}
RENAMED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import type TwoFaLibMediator from '../../TwoFaLibMediator.mjs';
|
|
2
2
|
import Command from '../BaseCommand.mjs';
|
|
3
3
|
import type { DeviceFriendlyName, DeviceId, DeviceType } from '../../interfaces/SyncTypes.mjs';
|
|
4
|
-
export interface
|
|
4
|
+
export interface ChangeSyncDeviceInfoData {
|
|
5
5
|
deviceId: DeviceId;
|
|
6
|
-
|
|
7
|
-
deviceFriendlyName
|
|
6
|
+
newDeviceInfo: {
|
|
7
|
+
deviceFriendlyName?: DeviceFriendlyName;
|
|
8
8
|
deviceType: DeviceType;
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Represents a command that when executed changes the
|
|
12
|
+
* Represents a command that when executed changes the device info of a sync device
|
|
13
13
|
*/
|
|
14
|
-
declare class
|
|
14
|
+
declare class ChangeDeviceInfoCommand extends Command<ChangeSyncDeviceInfoData> {
|
|
15
15
|
/**
|
|
16
16
|
* Creates a new ChangeSyncDeviceMetaCommand instance.
|
|
17
17
|
* @inheritdoc
|
|
18
18
|
* @param data - The id of the device to change and the new meta info
|
|
19
19
|
*/
|
|
20
|
-
constructor(data:
|
|
20
|
+
constructor(data: ChangeSyncDeviceInfoData, id?: string, timestamp?: number, version?: string, fromRemote?: boolean);
|
|
21
21
|
/**
|
|
22
|
-
* Executes the command to change the sync device
|
|
22
|
+
* Executes the command to change the sync device info
|
|
23
23
|
* @inheritdoc
|
|
24
24
|
* @throws {InvalidCommandError} If the referenced sync device cannot be found
|
|
25
25
|
*/
|
|
@@ -28,5 +28,10 @@ declare class ChangeSyncDeviceMetaCommand extends Command<ChangeSyncDeviceMetaDa
|
|
|
28
28
|
* @inheritdoc
|
|
29
29
|
*/
|
|
30
30
|
createUndoCommand(): Command;
|
|
31
|
+
/**
|
|
32
|
+
* Validates the command data.
|
|
33
|
+
* @inheritdoc
|
|
34
|
+
*/
|
|
35
|
+
validate(mediator: TwoFaLibMediator): boolean;
|
|
31
36
|
}
|
|
32
|
-
export default
|
|
37
|
+
export default ChangeDeviceInfoCommand;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { InvalidCommandError, TwoFALibError } from '../../TwoFALibError.mjs';
|
|
2
|
+
import Command from '../BaseCommand.mjs';
|
|
3
|
+
/**
|
|
4
|
+
* Represents a command that when executed changes the device info of a sync device
|
|
5
|
+
*/
|
|
6
|
+
class ChangeDeviceInfoCommand extends Command {
|
|
7
|
+
/**
|
|
8
|
+
* Creates a new ChangeSyncDeviceMetaCommand instance.
|
|
9
|
+
* @inheritdoc
|
|
10
|
+
* @param data - The id of the device to change and the new meta info
|
|
11
|
+
*/
|
|
12
|
+
constructor(data, id, timestamp, version, fromRemote = false) {
|
|
13
|
+
super('ChangeDeviceInfo', data, id, timestamp, version, fromRemote);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Executes the command to change the sync device info
|
|
17
|
+
* @inheritdoc
|
|
18
|
+
* @throws {InvalidCommandError} If the referenced sync device cannot be found
|
|
19
|
+
*/
|
|
20
|
+
async execute(mediator) {
|
|
21
|
+
if (!this.validate(mediator)) {
|
|
22
|
+
throw new InvalidCommandError('Failed to validate ChangeDeviceInfo command');
|
|
23
|
+
}
|
|
24
|
+
const lib = mediator.getComponent('lib');
|
|
25
|
+
if (this.data.deviceId === lib.meta.deviceId) {
|
|
26
|
+
// we're changing our own friendly name
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/dot-notation
|
|
28
|
+
lib['favaMeta'].deviceFriendlyName =
|
|
29
|
+
this.data.newDeviceInfo.deviceFriendlyName;
|
|
30
|
+
}
|
|
31
|
+
const syncManager = mediator.getComponent('syncManager');
|
|
32
|
+
if (syncManager) {
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/dot-notation
|
|
34
|
+
const device = syncManager['syncDevices'].find((d) => d.deviceId === this.data.deviceId);
|
|
35
|
+
if (!device) {
|
|
36
|
+
throw new InvalidCommandError('Trying to change info of device that is not found');
|
|
37
|
+
}
|
|
38
|
+
device.deviceInfo = this.data.newDeviceInfo;
|
|
39
|
+
}
|
|
40
|
+
await mediator.getComponent('persistentStorageManager').save();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* @inheritdoc
|
|
44
|
+
*/
|
|
45
|
+
createUndoCommand() {
|
|
46
|
+
throw new TwoFALibError('Not implemented yet');
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Validates the command data.
|
|
50
|
+
* @inheritdoc
|
|
51
|
+
*/
|
|
52
|
+
validate(mediator) {
|
|
53
|
+
const lib = mediator.getComponent('lib');
|
|
54
|
+
if (this.fromRemote) {
|
|
55
|
+
// we can only validate this command locally
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
if (this.data.deviceId !== lib.meta.deviceId) {
|
|
59
|
+
// device ids are not identical
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
if (this.data.newDeviceInfo.deviceType !== lib.meta.deviceType) {
|
|
63
|
+
// Changing device type
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const deviceFriendlyName = this.data.newDeviceInfo.deviceFriendlyName;
|
|
67
|
+
if (deviceFriendlyName !== undefined) {
|
|
68
|
+
if (deviceFriendlyName.length > 256) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (deviceFriendlyName.length < 1) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export default ChangeDeviceInfoCommand;
|
package/build/TwoFaLib.d.mts
CHANGED
|
@@ -1,27 +1,36 @@
|
|
|
1
1
|
import { TypedEventTarget } from 'typescript-event-target';
|
|
2
2
|
import type CryptoLib from './interfaces/CryptoLib.mjs';
|
|
3
3
|
import type { EncryptedPrivateKey, EncryptedSymmetricKey, PrivateKey, PublicKey, Salt, SymmetricKey } from './interfaces/CryptoLib.mjs';
|
|
4
|
-
import type { DeviceFriendlyName,
|
|
4
|
+
import type { DeviceFriendlyName, DeviceType } from './interfaces/SyncTypes.mjs';
|
|
5
5
|
import type { TwoFaLibEventMapEvents } from './interfaces/Events.mjs';
|
|
6
6
|
import type { PassphraseExtraDict } from './interfaces/PassphraseExtraDict.js';
|
|
7
7
|
import type { Vault, VaultSyncState } from './interfaces/Vault.mjs';
|
|
8
|
+
import type { SaveFunction } from './interfaces/SaveFunction.mjs';
|
|
9
|
+
import type { FavaMeta } from './interfaces/FavaMeta.mjs';
|
|
8
10
|
import SyncManager from './subclasses/SyncManager.mjs';
|
|
9
11
|
import ExportImportManager from './subclasses/ExportImportManager.mjs';
|
|
10
12
|
import VaultOperationsManager from './subclasses/VaultOperationsManager.mjs';
|
|
11
|
-
import SaveFunction from './interfaces/SaveFunction.mjs';
|
|
12
13
|
import StorageOperationsManager from './subclasses/StorageOperationsManager.mjs';
|
|
13
14
|
/**
|
|
14
15
|
* The Two-Factor Library, this is the main entry point.
|
|
15
16
|
*/
|
|
16
17
|
declare class TwoFaLib extends TypedEventTarget<TwoFaLibEventMapEvents> {
|
|
17
18
|
static readonly version = "0.0.1";
|
|
18
|
-
readonly
|
|
19
|
+
private readonly favaMeta;
|
|
19
20
|
readonly deviceType: DeviceType;
|
|
20
|
-
deviceFriendlyName
|
|
21
|
+
deviceFriendlyName?: DeviceFriendlyName;
|
|
21
22
|
private mediator;
|
|
22
23
|
private readonly publicKey;
|
|
23
24
|
private readonly privateKey;
|
|
24
25
|
readonly ready: Promise<unknown>;
|
|
26
|
+
/**
|
|
27
|
+
* @returns The meta info for this device.
|
|
28
|
+
*/
|
|
29
|
+
get meta(): {
|
|
30
|
+
deviceId: import("./interfaces/SyncTypes.mjs").DeviceId;
|
|
31
|
+
deviceFriendlyName: string | DeviceFriendlyName;
|
|
32
|
+
deviceType: DeviceType;
|
|
33
|
+
};
|
|
25
34
|
/**
|
|
26
35
|
* Constructs a new instance of TwoFaLib. If a serverUrl is provided, the library will use it for its sync operations.
|
|
27
36
|
* @param deviceType - The identifier for this device type (e.g. 2fa-cli).
|
|
@@ -33,7 +42,7 @@ declare class TwoFaLib extends TypedEventTarget<TwoFaLibEventMapEvents> {
|
|
|
33
42
|
* @param encryptedSymmetricKey - The encrypted symmetric key
|
|
34
43
|
* @param salt - The salt used for key derivation.
|
|
35
44
|
* @param publicKey - The public key of the device.
|
|
36
|
-
* @param
|
|
45
|
+
* @param favaMeta - Meta info about this device containing at least a unique identifier for this device.
|
|
37
46
|
* @param vault - The vault data (entries)
|
|
38
47
|
* @param saveFunction - The function to save the data.
|
|
39
48
|
* @param syncState - The state of the sync, includes the serverUrl
|
|
@@ -41,7 +50,7 @@ declare class TwoFaLib extends TypedEventTarget<TwoFaLibEventMapEvents> {
|
|
|
41
50
|
* @throws {InitializationError} If some parameter has an invalid value
|
|
42
51
|
* @throws {AuthenticationError} If the provided passphrase is incorrect.
|
|
43
52
|
*/
|
|
44
|
-
constructor(deviceType: DeviceType, cryptoLib: CryptoLib, passphraseExtraDict: PassphraseExtraDict, privateKey: PrivateKey, symmetricKey: SymmetricKey, encryptedPrivateKey: EncryptedPrivateKey, encryptedSymmetricKey: EncryptedSymmetricKey, salt: Salt, publicKey: PublicKey,
|
|
53
|
+
constructor(deviceType: DeviceType, cryptoLib: CryptoLib, passphraseExtraDict: PassphraseExtraDict, privateKey: PrivateKey, symmetricKey: SymmetricKey, encryptedPrivateKey: EncryptedPrivateKey, encryptedSymmetricKey: EncryptedSymmetricKey, salt: Salt, publicKey: PublicKey, favaMeta: FavaMeta, vault?: Vault, saveFunction?: SaveFunction, syncState?: VaultSyncState);
|
|
45
54
|
/**
|
|
46
55
|
* @returns The persistent storage manager instance which can be used to store data.
|
|
47
56
|
*/
|
|
@@ -72,6 +81,11 @@ declare class TwoFaLib extends TypedEventTarget<TwoFaLibEventMapEvents> {
|
|
|
72
81
|
* @param force - Force setting the sync server url, even if no connection can be made
|
|
73
82
|
*/
|
|
74
83
|
setSyncServerUrl(serverUrl: string, force?: boolean): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Set a friendly name for this vault (used in syncing)
|
|
86
|
+
* @param deviceFriendlyName Human readable name for the device
|
|
87
|
+
*/
|
|
88
|
+
setDeviceFriendlyName(deviceFriendlyName: DeviceFriendlyName): Promise<void>;
|
|
75
89
|
/**
|
|
76
90
|
* Dispatches a library event.
|
|
77
91
|
* @param eventType - The type of the event to dispatch, uses the TwoFaLibEvent enum.
|
package/build/TwoFaLib.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TypedEventTarget } from 'typescript-event-target';
|
|
2
2
|
import TwoFaLibMediator from './TwoFaLibMediator.mjs';
|
|
3
3
|
import { TwoFaLibEvent } from './TwoFaLibEvent.mjs';
|
|
4
|
-
import { InitializationError, SyncError } from './TwoFALibError.mjs';
|
|
4
|
+
import { InitializationError, SyncError, TwoFALibError, } from './TwoFALibError.mjs';
|
|
5
5
|
import SyncManager, { ConnectionStatus } from './subclasses/SyncManager.mjs';
|
|
6
6
|
import LibraryLoader from './subclasses/LibraryLoader.mjs';
|
|
7
7
|
import ExportImportManager from './subclasses/ExportImportManager.mjs';
|
|
@@ -10,12 +10,23 @@ import VaultDataManager from './subclasses/VaultDataManager.mjs';
|
|
|
10
10
|
import VaultOperationsManager from './subclasses/VaultOperationsManager.mjs';
|
|
11
11
|
import CommandManager from './subclasses/CommandManager.mjs';
|
|
12
12
|
import StorageOperationsManager from './subclasses/StorageOperationsManager.mjs';
|
|
13
|
+
import ChangeDeviceInfoCommand from './Command/commands/ChangeDeviceInfoCommand.mjs';
|
|
13
14
|
/**
|
|
14
15
|
* The Two-Factor Library, this is the main entry point.
|
|
15
16
|
*/
|
|
16
17
|
class TwoFaLib extends TypedEventTarget {
|
|
17
18
|
// TOOD: load this from package.json
|
|
18
19
|
static { this.version = '0.0.1'; }
|
|
20
|
+
/**
|
|
21
|
+
* @returns The meta info for this device.
|
|
22
|
+
*/
|
|
23
|
+
get meta() {
|
|
24
|
+
return {
|
|
25
|
+
deviceId: this.favaMeta.deviceId,
|
|
26
|
+
deviceFriendlyName: this.deviceFriendlyName ?? '',
|
|
27
|
+
deviceType: this.deviceType,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
19
30
|
/**
|
|
20
31
|
* Constructs a new instance of TwoFaLib. If a serverUrl is provided, the library will use it for its sync operations.
|
|
21
32
|
* @param deviceType - The identifier for this device type (e.g. 2fa-cli).
|
|
@@ -27,7 +38,7 @@ class TwoFaLib extends TypedEventTarget {
|
|
|
27
38
|
* @param encryptedSymmetricKey - The encrypted symmetric key
|
|
28
39
|
* @param salt - The salt used for key derivation.
|
|
29
40
|
* @param publicKey - The public key of the device.
|
|
30
|
-
* @param
|
|
41
|
+
* @param favaMeta - Meta info about this device containing at least a unique identifier for this device.
|
|
31
42
|
* @param vault - The vault data (entries)
|
|
32
43
|
* @param saveFunction - The function to save the data.
|
|
33
44
|
* @param syncState - The state of the sync, includes the serverUrl
|
|
@@ -35,23 +46,26 @@ class TwoFaLib extends TypedEventTarget {
|
|
|
35
46
|
* @throws {InitializationError} If some parameter has an invalid value
|
|
36
47
|
* @throws {AuthenticationError} If the provided passphrase is incorrect.
|
|
37
48
|
*/
|
|
38
|
-
constructor(deviceType, cryptoLib, passphraseExtraDict, privateKey, symmetricKey, encryptedPrivateKey, encryptedSymmetricKey, salt, publicKey,
|
|
49
|
+
constructor(deviceType, cryptoLib, passphraseExtraDict, privateKey, symmetricKey, encryptedPrivateKey, encryptedSymmetricKey, salt, publicKey, favaMeta, vault, saveFunction, syncState) {
|
|
39
50
|
super();
|
|
40
|
-
this.deviceFriendlyName = '';
|
|
41
51
|
if (!deviceType) {
|
|
42
52
|
throw new InitializationError('Device type is required');
|
|
43
53
|
}
|
|
44
|
-
if (!deviceId) {
|
|
54
|
+
if (!favaMeta.deviceId) {
|
|
45
55
|
throw new InitializationError('Device id is required');
|
|
46
56
|
}
|
|
47
57
|
if (deviceType.length > 256) {
|
|
48
58
|
throw new InitializationError('Device type is too long, max 256 characters');
|
|
49
59
|
}
|
|
60
|
+
if (favaMeta.deviceFriendlyName &&
|
|
61
|
+
favaMeta.deviceFriendlyName.length > 256) {
|
|
62
|
+
throw new InitializationError('Device friendly name is too long, max 256 characters');
|
|
63
|
+
}
|
|
50
64
|
if (passphraseExtraDict?.length === 0) {
|
|
51
65
|
throw new InitializationError('Passphrase extra dictionary is required and must contain at least one element (eg phone)');
|
|
52
66
|
}
|
|
67
|
+
this.favaMeta = favaMeta;
|
|
53
68
|
this.deviceType = deviceType;
|
|
54
|
-
this.deviceId = deviceId;
|
|
55
69
|
this.publicKey = publicKey;
|
|
56
70
|
this.privateKey = privateKey;
|
|
57
71
|
this.mediator = new TwoFaLibMediator();
|
|
@@ -59,7 +73,7 @@ class TwoFaLib extends TypedEventTarget {
|
|
|
59
73
|
['libraryLoader', new LibraryLoader(cryptoLib)],
|
|
60
74
|
[
|
|
61
75
|
'persistentStorageManager',
|
|
62
|
-
new PersistentStorageManager(this.mediator, passphraseExtraDict,
|
|
76
|
+
new PersistentStorageManager(this.mediator, passphraseExtraDict, favaMeta, privateKey, symmetricKey, encryptedPrivateKey, encryptedSymmetricKey, salt, saveFunction),
|
|
63
77
|
],
|
|
64
78
|
['vaultDataManager', new VaultDataManager(this.mediator)],
|
|
65
79
|
['commandManager', new CommandManager(this.mediator)],
|
|
@@ -71,13 +85,14 @@ class TwoFaLib extends TypedEventTarget {
|
|
|
71
85
|
],
|
|
72
86
|
['dispatchLibEvent', this.dispatchLibEvent.bind(this)],
|
|
73
87
|
['log', this.log.bind(this)],
|
|
88
|
+
['lib', this],
|
|
74
89
|
]);
|
|
75
90
|
if (vault) {
|
|
76
91
|
this.mediator.getComponent('vaultDataManager').replaceVault(vault);
|
|
77
92
|
}
|
|
78
93
|
if (syncState?.serverUrl) {
|
|
79
94
|
// Initiate the syncManager
|
|
80
|
-
this.mediator.registerComponent('syncManager', new SyncManager(this.mediator, this.publicKey, this.privateKey, syncState, this.
|
|
95
|
+
this.mediator.registerComponent('syncManager', new SyncManager(this.mediator, this.publicKey, this.privateKey, this.favaMeta, syncState, this.deviceType));
|
|
81
96
|
}
|
|
82
97
|
else {
|
|
83
98
|
// If no syncmanager we're ready now, otherwise the syncmanager is responsible for emitting the ready event
|
|
@@ -147,7 +162,7 @@ class TwoFaLib extends TypedEventTarget {
|
|
|
147
162
|
devices: [],
|
|
148
163
|
commandSendQueue: [],
|
|
149
164
|
};
|
|
150
|
-
const newSyncManager = new SyncManager(this.mediator, this.publicKey, this.privateKey, newSyncState, this.
|
|
165
|
+
const newSyncManager = new SyncManager(this.mediator, this.publicKey, this.privateKey, this.favaMeta, newSyncState, this.deviceType);
|
|
151
166
|
const success = await new Promise((resolve) => {
|
|
152
167
|
this.addEventListener(TwoFaLibEvent.ConnectionToSyncServerStatusChanged, (event) => {
|
|
153
168
|
if (event.detail.newStatus === ConnectionStatus.CONNECTED) {
|
|
@@ -178,6 +193,21 @@ class TwoFaLib extends TypedEventTarget {
|
|
|
178
193
|
// save
|
|
179
194
|
await this.persistentStorageManager.save();
|
|
180
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Set a friendly name for this vault (used in syncing)
|
|
198
|
+
* @param deviceFriendlyName Human readable name for the device
|
|
199
|
+
*/
|
|
200
|
+
async setDeviceFriendlyName(deviceFriendlyName) {
|
|
201
|
+
const data = {
|
|
202
|
+
deviceId: this.favaMeta.deviceId,
|
|
203
|
+
newDeviceInfo: { deviceType: this.deviceType, deviceFriendlyName },
|
|
204
|
+
};
|
|
205
|
+
const command = ChangeDeviceInfoCommand.create(data);
|
|
206
|
+
if (!command.validate(this.mediator)) {
|
|
207
|
+
throw new TwoFALibError('Device friendly name has invalid length, max 256 characters');
|
|
208
|
+
}
|
|
209
|
+
await this.mediator.getComponent('commandManager').execute(command);
|
|
210
|
+
}
|
|
181
211
|
/**
|
|
182
212
|
* Dispatches a library event.
|
|
183
213
|
* @param eventType - The type of the event to dispatch, uses the TwoFaLibEvent enum.
|
|
@@ -2,7 +2,7 @@ import type { AddEntryData } from '../Command/commands/AddEntryCommand.mjs';
|
|
|
2
2
|
import type { DeleteEntryData } from '../Command/commands/DeleteEntryCommand.mjs';
|
|
3
3
|
import type { UpdateEntryData } from '../Command/commands/UpdateEntryCommand.mjs';
|
|
4
4
|
import type { AddSyncDeviceData } from '../Command/commands/AddSyncDeviceCommand.mjs';
|
|
5
|
-
import type {
|
|
5
|
+
import type { ChangeSyncDeviceInfoData } from '../Command/commands/ChangeDeviceInfoCommand.mjs';
|
|
6
6
|
export type SyncCommand = ({
|
|
7
7
|
type: 'AddEntry';
|
|
8
8
|
data: AddEntryData;
|
|
@@ -16,9 +16,9 @@ export type SyncCommand = ({
|
|
|
16
16
|
type: 'AddSyncDevice';
|
|
17
17
|
data: AddSyncDeviceData;
|
|
18
18
|
} | {
|
|
19
|
-
type: '
|
|
20
|
-
data:
|
|
19
|
+
type: 'ChangeSyncDeviceInfo';
|
|
20
|
+
data: ChangeSyncDeviceInfoData;
|
|
21
21
|
}) & {
|
|
22
22
|
id: string;
|
|
23
23
|
};
|
|
24
|
-
export type CommandData = AddEntryData | DeleteEntryData | UpdateEntryData | AddSyncDeviceData |
|
|
24
|
+
export type CommandData = AddEntryData | DeleteEntryData | UpdateEntryData | AddSyncDeviceData | ChangeSyncDeviceInfoData;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
import type { LockedRepresentationString } from './Vault.mjs';
|
|
2
|
-
type SaveFunction = (newLockedRepresentationString: LockedRepresentationString) => Promise<void> | void;
|
|
3
|
-
export default SaveFunction;
|
|
2
|
+
export type SaveFunction = (newLockedRepresentationString: LockedRepresentationString) => Promise<void> | void;
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import type { Tagged } from 'type-fest';
|
|
2
2
|
import type { JPakeThreePass, Round1Result } from 'jpake-ts';
|
|
3
|
-
import { PublicKey, SyncKey } from './CryptoLib.mjs';
|
|
3
|
+
import type { PublicKey, SyncKey } from './CryptoLib.mjs';
|
|
4
|
+
import type { Vault, VaultSyncState } from './Vault.mjs';
|
|
4
5
|
export type DeviceId = Tagged<string, 'DeviceId'>;
|
|
5
6
|
export type DeviceType = Tagged<string, 'DeviceType'>;
|
|
6
7
|
export type DeviceFriendlyName = Tagged<string, 'DeviceFriendlyName'>;
|
|
8
|
+
export interface DeviceInfo {
|
|
9
|
+
deviceType: DeviceType;
|
|
10
|
+
deviceFriendlyName?: DeviceFriendlyName;
|
|
11
|
+
}
|
|
7
12
|
export interface SyncDevice {
|
|
8
13
|
deviceId: DeviceId;
|
|
9
14
|
publicKey: PublicKey;
|
|
10
|
-
|
|
11
|
-
deviceType: DeviceType;
|
|
12
|
-
deviceFriendlyName: DeviceFriendlyName;
|
|
13
|
-
};
|
|
15
|
+
deviceInfo?: DeviceInfo;
|
|
14
16
|
}
|
|
15
|
-
export type PublicSyncDevice = Omit<SyncDevice, 'publicKey'>;
|
|
17
|
+
export type PublicSyncDevice = Omit<SyncDevice, 'publicKey' | 'deviceInfo'> & Partial<SyncDevice['deviceInfo']>;
|
|
16
18
|
export interface BaseAddDeviceFlow {
|
|
17
19
|
jpak: JPakeThreePass;
|
|
18
20
|
addDevicePassword: Uint8Array;
|
|
@@ -45,3 +47,10 @@ export interface InitiateAddDeviceFlowResult {
|
|
|
45
47
|
timestamp: number;
|
|
46
48
|
pass1Result: Record<keyof Round1Result, string>;
|
|
47
49
|
}
|
|
50
|
+
export interface VaultStateSend {
|
|
51
|
+
deviceId: DeviceId;
|
|
52
|
+
forDeviceId: DeviceId;
|
|
53
|
+
deviceFriendlyName?: DeviceFriendlyName;
|
|
54
|
+
vault: Vault;
|
|
55
|
+
sync: VaultSyncState;
|
|
56
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Tagged } from 'type-fest';
|
|
2
2
|
import type { Encrypted, EncryptedPrivateKey, EncryptedSymmetricKey, Salt } from './CryptoLib.mjs';
|
|
3
3
|
import type Entry from './Entry.mjs';
|
|
4
|
-
import { DeviceId, SyncDevice } from './SyncTypes.mjs';
|
|
4
|
+
import { DeviceFriendlyName, DeviceId, SyncDevice } from './SyncTypes.mjs';
|
|
5
5
|
import { SyncCommandFromClient } from 'favaserver/ClientMessage';
|
|
6
6
|
export type Vault = Entry[];
|
|
7
7
|
export type EncryptedVaultStateString = Encrypted<VaultStateString>;
|
|
@@ -24,6 +24,7 @@ export type VaultSyncStateWithServerUrl = Omit<VaultSyncState, 'serverUrl'> & {
|
|
|
24
24
|
};
|
|
25
25
|
export interface VaultState {
|
|
26
26
|
deviceId: DeviceId;
|
|
27
|
+
deviceFriendlyName?: DeviceFriendlyName;
|
|
27
28
|
vault: Vault;
|
|
28
29
|
sync: VaultSyncState;
|
|
29
30
|
}
|
package/build/main.d.mts
CHANGED
|
@@ -3,11 +3,11 @@ import type Entry from './interfaces/Entry.mjs';
|
|
|
3
3
|
import type { EntryId, NewEntry, EntryMeta, EntryType, TotpPayload, Token, EntryMetaWithToken } from './interfaces/Entry.mjs';
|
|
4
4
|
import type CryptoLib from './interfaces/CryptoLib.mjs';
|
|
5
5
|
import type { Encrypted, EncryptedPrivateKey, EncryptedSymmetricKey, EncryptedPublicKey, PrivateKey, SymmetricKey, PublicKey, Passphrase, Salt } from './interfaces/CryptoLib.mjs';
|
|
6
|
-
import type { PublicSyncDevice, DeviceId, DeviceType, DeviceFriendlyName } from './interfaces/SyncTypes.mjs';
|
|
6
|
+
import type { PublicSyncDevice, DeviceId, DeviceType, DeviceFriendlyName, DeviceInfo } from './interfaces/SyncTypes.mjs';
|
|
7
7
|
import type { EncryptedVaultStateString, LockedRepresentationString } from './interfaces/Vault.mjs';
|
|
8
|
-
import type SaveFunction from './interfaces/SaveFunction.mjs';
|
|
8
|
+
import type { SaveFunction } from './interfaces/SaveFunction.mjs';
|
|
9
9
|
import { TwoFALibError, InitializationError, AuthenticationError, EntryNotFoundError, TokenGenerationError } from './TwoFALibError.mjs';
|
|
10
10
|
import { TwoFaLibEvent } from './TwoFaLibEvent.mjs';
|
|
11
11
|
import { getTwoFaLibVaultCreationUtils } from './utils/creationUtils.mjs';
|
|
12
12
|
export { TwoFaLib, TwoFALibError, getTwoFaLibVaultCreationUtils, InitializationError, AuthenticationError, EntryNotFoundError, TokenGenerationError, TwoFaLibEvent, };
|
|
13
|
-
export type { Entry, EntryId, NewEntry, EntryMeta, EntryMetaWithToken, EntryType, TotpPayload, Token, EncryptedVaultStateString, LockedRepresentationString, CryptoLib, Encrypted, EncryptedPrivateKey, EncryptedPublicKey, EncryptedSymmetricKey, PrivateKey, SymmetricKey, PublicKey, Passphrase, Salt, DeviceId, DeviceType, DeviceFriendlyName, PublicSyncDevice, SaveFunction, };
|
|
13
|
+
export type { Entry, EntryId, NewEntry, EntryMeta, EntryMetaWithToken, EntryType, TotpPayload, Token, EncryptedVaultStateString, LockedRepresentationString, CryptoLib, Encrypted, EncryptedPrivateKey, EncryptedPublicKey, EncryptedSymmetricKey, PrivateKey, SymmetricKey, PublicKey, Passphrase, Salt, DeviceId, DeviceType, DeviceFriendlyName, DeviceInfo, PublicSyncDevice, SaveFunction, };
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import type { EncryptedPrivateKey, EncryptedSymmetricKey, Passphrase, PrivateKey, Salt, SymmetricKey } from '../interfaces/CryptoLib.mjs';
|
|
2
|
-
import { EncryptedVaultStateString } from '../interfaces/Vault.mjs';
|
|
2
|
+
import { EncryptedVaultStateString, LockedRepresentationString } from '../interfaces/Vault.mjs';
|
|
3
3
|
import type TwoFaLibMediator from '../TwoFaLibMediator.mjs';
|
|
4
|
-
import type {
|
|
4
|
+
import type { FavaMeta } from '../interfaces/FavaMeta.mjs';
|
|
5
5
|
import type { PassphraseExtraDict } from '../interfaces/PassphraseExtraDict.js';
|
|
6
|
-
import SaveFunction from '../interfaces/SaveFunction.mjs';
|
|
6
|
+
import type { SaveFunction } from '../interfaces/SaveFunction.mjs';
|
|
7
|
+
import type { DeviceId } from '../interfaces/SyncTypes.mjs';
|
|
7
8
|
/**
|
|
8
9
|
* Manages all storage of data that should be persistent.
|
|
9
10
|
*/
|
|
10
11
|
declare class PersistentStorageManager {
|
|
11
12
|
private mediator;
|
|
12
13
|
private readonly passphraseExtraDict;
|
|
13
|
-
private readonly
|
|
14
|
+
private readonly favaMeta;
|
|
14
15
|
private readonly privateKey;
|
|
15
16
|
private readonly symmetricKey;
|
|
16
17
|
private encryptedPrivateKey;
|
|
@@ -23,7 +24,7 @@ declare class PersistentStorageManager {
|
|
|
23
24
|
* Constructs a new instance of PersistentStorageManager.
|
|
24
25
|
* @param mediator - The mediator for accessing other components.
|
|
25
26
|
* @param passphraseExtraDict - Additional words to be used for passphrase strength evaluation.
|
|
26
|
-
* @param
|
|
27
|
+
* @param favaMeta - Meta info containing at least a unique identifier for this device.
|
|
27
28
|
* @param privateKey - The private key used for cryptographic operations.
|
|
28
29
|
* @param symmetricKey - The symmetric key used for cryptographic operations.
|
|
29
30
|
* @param encryptedPrivateKey - The encrypted private key
|
|
@@ -31,7 +32,7 @@ declare class PersistentStorageManager {
|
|
|
31
32
|
* @param salt - The salt used for key derivation.
|
|
32
33
|
* @param saveFunction - The function to save the data.
|
|
33
34
|
*/
|
|
34
|
-
constructor(mediator: TwoFaLibMediator, passphraseExtraDict: PassphraseExtraDict,
|
|
35
|
+
constructor(mediator: TwoFaLibMediator, passphraseExtraDict: PassphraseExtraDict, favaMeta: FavaMeta, privateKey: PrivateKey, symmetricKey: SymmetricKey, encryptedPrivateKey: EncryptedPrivateKey, encryptedSymmetricKey: EncryptedSymmetricKey, salt: Salt, saveFunction?: SaveFunction | undefined);
|
|
35
36
|
private get cryptoLib();
|
|
36
37
|
private get vaultDataManager();
|
|
37
38
|
private get syncManager();
|
|
@@ -39,9 +40,10 @@ declare class PersistentStorageManager {
|
|
|
39
40
|
* Retrieves an encrypted representation of the library's current state.
|
|
40
41
|
* This can be used for secure storage or transmission of the library's data.
|
|
41
42
|
* @param key - The key to decrypt the locked representation with. If not provided the library's current symmetric key will be used.
|
|
43
|
+
* @param forDeviceId - If the vault is meant for a specific deviceId
|
|
42
44
|
* @returns A promise that resolves with a string representation of the locked state.
|
|
43
45
|
*/
|
|
44
|
-
getEncryptedVaultState(key?: SymmetricKey): Promise<EncryptedVaultStateString>;
|
|
46
|
+
getEncryptedVaultState(key?: SymmetricKey, forDeviceId?: DeviceId): Promise<EncryptedVaultStateString>;
|
|
45
47
|
/**
|
|
46
48
|
* Creates a partially encrypted representation of all data, except for
|
|
47
49
|
* the passphrase, that is needed to load the library. This can be used
|
|
@@ -49,7 +51,7 @@ declare class PersistentStorageManager {
|
|
|
49
51
|
* @returns A promise that resolves with a json encoded string of
|
|
50
52
|
* the partially encrypted library's data.
|
|
51
53
|
*/
|
|
52
|
-
|
|
54
|
+
getLockedRepresentation(): Promise<LockedRepresentationString>;
|
|
53
55
|
/**
|
|
54
56
|
* Sets the save function for the library.
|
|
55
57
|
* @param saveFunction - The save function to set.
|
|
@@ -10,7 +10,7 @@ class PersistentStorageManager {
|
|
|
10
10
|
* Constructs a new instance of PersistentStorageManager.
|
|
11
11
|
* @param mediator - The mediator for accessing other components.
|
|
12
12
|
* @param passphraseExtraDict - Additional words to be used for passphrase strength evaluation.
|
|
13
|
-
* @param
|
|
13
|
+
* @param favaMeta - Meta info containing at least a unique identifier for this device.
|
|
14
14
|
* @param privateKey - The private key used for cryptographic operations.
|
|
15
15
|
* @param symmetricKey - The symmetric key used for cryptographic operations.
|
|
16
16
|
* @param encryptedPrivateKey - The encrypted private key
|
|
@@ -18,10 +18,10 @@ class PersistentStorageManager {
|
|
|
18
18
|
* @param salt - The salt used for key derivation.
|
|
19
19
|
* @param saveFunction - The function to save the data.
|
|
20
20
|
*/
|
|
21
|
-
constructor(mediator, passphraseExtraDict,
|
|
21
|
+
constructor(mediator, passphraseExtraDict, favaMeta, privateKey, symmetricKey, encryptedPrivateKey, encryptedSymmetricKey, salt, saveFunction) {
|
|
22
22
|
this.mediator = mediator;
|
|
23
23
|
this.passphraseExtraDict = passphraseExtraDict;
|
|
24
|
-
this.
|
|
24
|
+
this.favaMeta = favaMeta;
|
|
25
25
|
this.privateKey = privateKey;
|
|
26
26
|
this.symmetricKey = symmetricKey;
|
|
27
27
|
this.encryptedPrivateKey = encryptedPrivateKey;
|
|
@@ -46,13 +46,16 @@ class PersistentStorageManager {
|
|
|
46
46
|
* Retrieves an encrypted representation of the library's current state.
|
|
47
47
|
* This can be used for secure storage or transmission of the library's data.
|
|
48
48
|
* @param key - The key to decrypt the locked representation with. If not provided the library's current symmetric key will be used.
|
|
49
|
+
* @param forDeviceId - If the vault is meant for a specific deviceId
|
|
49
50
|
* @returns A promise that resolves with a string representation of the locked state.
|
|
50
51
|
*/
|
|
51
|
-
async getEncryptedVaultState(key) {
|
|
52
|
+
async getEncryptedVaultState(key, forDeviceId) {
|
|
52
53
|
const vault = this.vaultDataManager.getAllEntries();
|
|
53
54
|
const vaultState = {
|
|
54
55
|
vault,
|
|
55
|
-
deviceId: this.deviceId,
|
|
56
|
+
deviceId: this.favaMeta.deviceId,
|
|
57
|
+
forDeviceId,
|
|
58
|
+
deviceFriendlyName: this.favaMeta.deviceFriendlyName,
|
|
56
59
|
sync: {
|
|
57
60
|
// eslint-disable-next-line @typescript-eslint/dot-notation
|
|
58
61
|
devices: this.syncManager ? this.syncManager['syncDevices'] : [],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type TwoFaLibMediator from '../TwoFaLibMediator.mjs';
|
|
2
2
|
import type { Passphrase } from '../interfaces/CryptoLib.mjs';
|
|
3
|
-
import type SaveFunction from '../interfaces/SaveFunction.mjs';
|
|
3
|
+
import type { SaveFunction } from '../interfaces/SaveFunction.mjs';
|
|
4
4
|
/**
|
|
5
5
|
* Manages the public operations related to the vault storage
|
|
6
6
|
*/
|
|
@@ -17,6 +17,7 @@ declare class StorageOperationsManager {
|
|
|
17
17
|
get persistentStorage(): import("./PersistentStorageManager.mjs").default;
|
|
18
18
|
/**
|
|
19
19
|
* Forces a save.
|
|
20
|
+
* @returns void
|
|
20
21
|
*/
|
|
21
22
|
forceSave(): Promise<void>;
|
|
22
23
|
/**
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { SyncDevice,
|
|
1
|
+
import { SyncDevice, PublicSyncDevice, DeviceType } from '../interfaces/SyncTypes.mjs';
|
|
2
2
|
import type { PrivateKey, PublicKey } from '../interfaces/CryptoLib.mjs';
|
|
3
3
|
import type Command from '../Command/BaseCommand.mjs';
|
|
4
4
|
import type TwoFaLibMediator from '../TwoFaLibMediator.mjs';
|
|
5
5
|
import { VaultSyncStateWithServerUrl } from '../interfaces/Vault.mjs';
|
|
6
|
+
import type { FavaMeta } from '../interfaces/FavaMeta.mjs';
|
|
6
7
|
import { SyncCommandFromServer } from 'favaserver/ServerMessage';
|
|
7
8
|
import { SyncCommandFromClient } from 'favaserver/ClientMessage';
|
|
8
9
|
export declare enum ConnectionStatus {
|
|
@@ -18,18 +19,23 @@ declare class SyncManager {
|
|
|
18
19
|
private readonly mediator;
|
|
19
20
|
private readonly publicKey;
|
|
20
21
|
private readonly privateKey;
|
|
22
|
+
private readonly favaMeta;
|
|
23
|
+
private readonly deviceType;
|
|
21
24
|
private ws?;
|
|
22
25
|
private activeAddDeviceFlow?;
|
|
23
26
|
private readonly reconnectInterval;
|
|
24
27
|
readonly serverUrl: string;
|
|
25
28
|
private syncDevices;
|
|
26
|
-
deviceId: DeviceId;
|
|
27
29
|
private readyEventEmitted;
|
|
28
30
|
private commandSendQueue;
|
|
29
31
|
private reconnectTimeout?;
|
|
30
32
|
private terminateTimeout?;
|
|
31
33
|
private connectionFailedTimeout?;
|
|
32
34
|
private shouldReconnect;
|
|
35
|
+
private requestedResilver;
|
|
36
|
+
private requestedResilverTimeout?;
|
|
37
|
+
private get deviceId();
|
|
38
|
+
private get deviceInfo();
|
|
33
39
|
/**
|
|
34
40
|
* Public getter for the command send queue.
|
|
35
41
|
* @returns The command send queue.
|
|
@@ -45,11 +51,12 @@ declare class SyncManager {
|
|
|
45
51
|
* @param mediator - The mediator for accessing other components.
|
|
46
52
|
* @param publicKey - The public key of the device.
|
|
47
53
|
* @param privateKey - The private key of the device.
|
|
54
|
+
* @param favaMeta - Meta info containing at least a unique identifier for this device.
|
|
48
55
|
* @param syncState - The state of the sync.
|
|
49
|
-
* @param
|
|
56
|
+
* @param deviceType - The identifier for this device type (e.g. 2fa-cli).
|
|
50
57
|
* @throws {InitializationError} If initialization fails (e.g., if the server URL is invalid).
|
|
51
58
|
*/
|
|
52
|
-
constructor(mediator: TwoFaLibMediator, publicKey: PublicKey, privateKey: PrivateKey, syncState: VaultSyncStateWithServerUrl,
|
|
59
|
+
constructor(mediator: TwoFaLibMediator, publicKey: PublicKey, privateKey: PrivateKey, favaMeta: FavaMeta, syncState: VaultSyncStateWithServerUrl, deviceType: DeviceType);
|
|
53
60
|
private get libraryLoader();
|
|
54
61
|
private get cryptoLib();
|
|
55
62
|
private get persistentStorageManager();
|
|
@@ -132,8 +139,8 @@ declare class SyncManager {
|
|
|
132
139
|
respondToAddDeviceFlow(initiatorData: string | Uint8Array | File, initiatorDataType: 'text' | 'qr'): Promise<void>;
|
|
133
140
|
private finishAddDeviceFlowKeyExchangeInitiator;
|
|
134
141
|
private finishAddDeviceFlowKeyExchangeResponder;
|
|
135
|
-
private
|
|
136
|
-
private
|
|
142
|
+
private sendFullVaultDataAndSetDeviceInfo;
|
|
143
|
+
private importInitialVault;
|
|
137
144
|
private importVaultState;
|
|
138
145
|
/**
|
|
139
146
|
* Cancels the active add sync device flow.
|
|
@@ -165,14 +172,14 @@ declare class SyncManager {
|
|
|
165
172
|
private resilver;
|
|
166
173
|
/**
|
|
167
174
|
* Add a sync device
|
|
168
|
-
* @param
|
|
175
|
+
* @param device - The device to add
|
|
169
176
|
* @param saveAfter - Whether to save the new vault after adding it (set to false when adding multiple devices)
|
|
170
177
|
*/
|
|
171
|
-
addSyncDevice(
|
|
178
|
+
addSyncDevice(device: SyncDevice, saveAfter?: boolean): Promise<void>;
|
|
172
179
|
/**
|
|
173
180
|
* Requests a resilver of the vault
|
|
174
181
|
*/
|
|
175
|
-
requestResilver(): void
|
|
182
|
+
requestResilver(): Promise<void>;
|
|
176
183
|
/**
|
|
177
184
|
* Function to call when the server connection should be closed
|
|
178
185
|
*/
|
|
@@ -22,6 +22,15 @@ const generateNonCryptographicRandomString = () => {
|
|
|
22
22
|
* Manages synchronization of 2FA devices and communication with the server.
|
|
23
23
|
*/
|
|
24
24
|
class SyncManager {
|
|
25
|
+
get deviceId() {
|
|
26
|
+
return this.favaMeta.deviceId;
|
|
27
|
+
}
|
|
28
|
+
get deviceInfo() {
|
|
29
|
+
return {
|
|
30
|
+
deviceType: this.deviceType,
|
|
31
|
+
deviceFriendlyName: this.favaMeta.deviceFriendlyName,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
25
34
|
/**
|
|
26
35
|
* Public getter for the command send queue.
|
|
27
36
|
* @returns The command send queue.
|
|
@@ -38,7 +47,7 @@ class SyncManager {
|
|
|
38
47
|
.filter((d) => d.deviceId !== this.deviceId)
|
|
39
48
|
.map((d) => ({
|
|
40
49
|
deviceId: d.deviceId,
|
|
41
|
-
|
|
50
|
+
...d.deviceInfo,
|
|
42
51
|
}));
|
|
43
52
|
}
|
|
44
53
|
/**
|
|
@@ -46,33 +55,37 @@ class SyncManager {
|
|
|
46
55
|
* @param mediator - The mediator for accessing other components.
|
|
47
56
|
* @param publicKey - The public key of the device.
|
|
48
57
|
* @param privateKey - The private key of the device.
|
|
58
|
+
* @param favaMeta - Meta info containing at least a unique identifier for this device.
|
|
49
59
|
* @param syncState - The state of the sync.
|
|
50
|
-
* @param
|
|
60
|
+
* @param deviceType - The identifier for this device type (e.g. 2fa-cli).
|
|
51
61
|
* @throws {InitializationError} If initialization fails (e.g., if the server URL is invalid).
|
|
52
62
|
*/
|
|
53
|
-
constructor(mediator, publicKey, privateKey, syncState,
|
|
63
|
+
constructor(mediator, publicKey, privateKey, favaMeta, syncState, deviceType) {
|
|
54
64
|
this.mediator = mediator;
|
|
55
65
|
this.publicKey = publicKey;
|
|
56
66
|
this.privateKey = privateKey;
|
|
67
|
+
this.favaMeta = favaMeta;
|
|
68
|
+
this.deviceType = deviceType;
|
|
57
69
|
this.reconnectInterval = IN_TESTING ? 100 : 5000; // 5 seconds
|
|
58
70
|
this.readyEventEmitted = false;
|
|
59
71
|
this.commandSendQueue = [];
|
|
60
72
|
this.shouldReconnect = true;
|
|
73
|
+
this.requestedResilver = false;
|
|
61
74
|
const { serverUrl, devices, commandSendQueue } = syncState;
|
|
62
75
|
if (!serverUrl.startsWith('wss://')) {
|
|
63
76
|
if (!serverUrl.startsWith('ws://') && !(IN_DEV || IN_TESTING)) {
|
|
64
77
|
throw new InitializationError('Invalid server URL, protocol must be wss');
|
|
65
78
|
}
|
|
66
79
|
}
|
|
67
|
-
this.deviceId = deviceId;
|
|
68
80
|
this.syncDevices = devices;
|
|
69
81
|
this.commandSendQueue = commandSendQueue;
|
|
70
82
|
this.serverUrl = serverUrl;
|
|
71
83
|
this.initServerConnection();
|
|
72
84
|
// add ourselves to the list of syncdevices if we're missing
|
|
73
85
|
void this.addSyncDevice({
|
|
74
|
-
deviceId: deviceId,
|
|
86
|
+
deviceId: this.favaMeta.deviceId,
|
|
75
87
|
publicKey: this.publicKey,
|
|
88
|
+
deviceInfo: this.deviceInfo,
|
|
76
89
|
}, false);
|
|
77
90
|
// if not yet connected after 2 tries, emit ready event so we can continue
|
|
78
91
|
this.connectionFailedTimeout = setTimeout(() => {
|
|
@@ -132,7 +145,8 @@ class SyncManager {
|
|
|
132
145
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
133
146
|
const syncManager = this;
|
|
134
147
|
ws.addEventListener('error', (event) => {
|
|
135
|
-
|
|
148
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
149
|
+
syncManager.log('warning', `Error in websocket: ${event.toString()}`);
|
|
136
150
|
});
|
|
137
151
|
ws.addEventListener('message', function message(message) {
|
|
138
152
|
try {
|
|
@@ -209,21 +223,27 @@ class SyncManager {
|
|
|
209
223
|
void this.finishAddDeviceFlowKeyExchangeResponder(pass3Result);
|
|
210
224
|
break;
|
|
211
225
|
}
|
|
212
|
-
case '
|
|
226
|
+
case 'publicKeyAndDeviceInfo': {
|
|
213
227
|
const { data } = message;
|
|
214
|
-
const { responderEncryptedPublicKey } = data;
|
|
215
|
-
void this.
|
|
228
|
+
const { responderEncryptedPublicKey, responderEncryptedDeviceInfo } = data;
|
|
229
|
+
void this.sendFullVaultDataAndSetDeviceInfo(responderEncryptedPublicKey, responderEncryptedDeviceInfo);
|
|
216
230
|
break;
|
|
217
231
|
}
|
|
218
232
|
case 'initialVault': {
|
|
219
233
|
const { data } = message;
|
|
220
234
|
const { encryptedVaultData } = data;
|
|
221
|
-
void this.
|
|
235
|
+
void this.importInitialVault(encryptedVaultData);
|
|
222
236
|
break;
|
|
223
237
|
}
|
|
224
238
|
case 'vault': {
|
|
239
|
+
if (!this.requestedResilver) {
|
|
240
|
+
throw new SyncError('Got vault data while no resilver was requested, probably replay attack!');
|
|
241
|
+
}
|
|
225
242
|
const { data } = message;
|
|
226
|
-
const { encryptedVaultData, encryptedSymmetricKey, fromDeviceId } = data;
|
|
243
|
+
const { encryptedVaultData, encryptedSymmetricKey, fromDeviceId, forDeviceId, } = data;
|
|
244
|
+
if (forDeviceId !== this.deviceId) {
|
|
245
|
+
throw new SyncError('Got vault data for the wrong device!');
|
|
246
|
+
}
|
|
227
247
|
void this.cryptoLib
|
|
228
248
|
.decrypt(this.privateKey, encryptedSymmetricKey)
|
|
229
249
|
.then((symmetricKey) => this.importVaultState(encryptedVaultData, symmetricKey, fromDeviceId));
|
|
@@ -415,14 +435,16 @@ class SyncManager {
|
|
|
415
435
|
syncKey,
|
|
416
436
|
};
|
|
417
437
|
const responderEncryptedPublicKey = await this.cryptoLib.encryptSymmetric(syncKey, this.publicKey);
|
|
438
|
+
const responderEncryptedDeviceInfo = await this.cryptoLib.encryptSymmetric(syncKey, JSON.stringify(this.deviceInfo));
|
|
418
439
|
// send our public key
|
|
419
|
-
this.sendToServer('
|
|
440
|
+
this.sendToServer('publicKeyAndDeviceInfo', {
|
|
420
441
|
nonce: await this.getNonce(),
|
|
421
442
|
responderEncryptedPublicKey,
|
|
443
|
+
responderEncryptedDeviceInfo,
|
|
422
444
|
initiatorDeviceId: this.activeAddDeviceFlow.initiatorDeviceId,
|
|
423
445
|
});
|
|
424
446
|
}
|
|
425
|
-
async
|
|
447
|
+
async sendFullVaultDataAndSetDeviceInfo(responderEncryptedPublicKey, responderEncryptedDeviceInfo) {
|
|
426
448
|
if (!this.ws || !this.webSocketConnected) {
|
|
427
449
|
throw new SyncNoServerConnectionError();
|
|
428
450
|
}
|
|
@@ -435,8 +457,10 @@ class SyncManager {
|
|
|
435
457
|
const syncKey = this.activeAddDeviceFlow.syncKey;
|
|
436
458
|
// Decrypt the received public key
|
|
437
459
|
const decryptedPublicKey = await this.cryptoLib.decryptSymmetric(syncKey, responderEncryptedPublicKey);
|
|
460
|
+
// decrypt the received device info
|
|
461
|
+
const responderDeviceInfo = JSON.parse(await this.cryptoLib.decryptSymmetric(syncKey, responderEncryptedDeviceInfo));
|
|
438
462
|
// get the vault data (encrypted with the sync key)
|
|
439
|
-
const encryptedVaultData = await this.persistentStorageManager.getEncryptedVaultState(syncKey);
|
|
463
|
+
const encryptedVaultData = await this.persistentStorageManager.getEncryptedVaultState(syncKey, this.activeAddDeviceFlow.responderDeviceId);
|
|
440
464
|
// Send the encrypted vault data to the server
|
|
441
465
|
this.sendToServer('initialVault', {
|
|
442
466
|
nonce: await this.getNonce(),
|
|
@@ -447,12 +471,13 @@ class SyncManager {
|
|
|
447
471
|
const command = AddSyncDeviceCommand.create({
|
|
448
472
|
deviceId: this.activeAddDeviceFlow.responderDeviceId,
|
|
449
473
|
publicKey: decryptedPublicKey,
|
|
474
|
+
deviceInfo: responderDeviceInfo,
|
|
450
475
|
});
|
|
451
476
|
await this.commandManager.execute(command);
|
|
452
477
|
// all done
|
|
453
478
|
this.activeAddDeviceFlow = undefined;
|
|
454
479
|
}
|
|
455
|
-
async
|
|
480
|
+
async importInitialVault(encryptedVaultState) {
|
|
456
481
|
if (this.activeAddDeviceFlow?.state !== 'responder:syncKeyCreated') {
|
|
457
482
|
throw new SyncInWrongStateError(`Expected responder:syncKeyCreated, got ${this.activeAddDeviceFlow?.state}`);
|
|
458
483
|
}
|
|
@@ -466,6 +491,9 @@ class SyncManager {
|
|
|
466
491
|
if (vaultState.deviceId !== expectedDeviceId) {
|
|
467
492
|
throw new SyncError(`DeviceId mismatch when importing, expected ${expectedDeviceId} got ${vaultState.deviceId}`);
|
|
468
493
|
}
|
|
494
|
+
if (vaultState.forDeviceId !== this.deviceId) {
|
|
495
|
+
throw new SyncError(`For deviceId mismatch when importing, expected ${this.deviceId} got ${vaultState.forDeviceId}`);
|
|
496
|
+
}
|
|
469
497
|
for (const device of vaultState.sync.devices) {
|
|
470
498
|
await this.addSyncDevice(device, false);
|
|
471
499
|
}
|
|
@@ -506,6 +534,7 @@ class SyncManager {
|
|
|
506
534
|
// skip ourselves
|
|
507
535
|
return;
|
|
508
536
|
}
|
|
537
|
+
// unique symmetricKey per command
|
|
509
538
|
const symmetricKey = await this.cryptoLib.createSymmetricKey();
|
|
510
539
|
const encryptedSymmetricKey = await this.cryptoLib.encrypt(device.publicKey, symmetricKey);
|
|
511
540
|
const encryptedCommand = await this.cryptoLib.encryptSymmetric(symmetricKey, JSON.stringify({
|
|
@@ -587,7 +616,7 @@ class SyncManager {
|
|
|
587
616
|
}
|
|
588
617
|
const symmetricKey = await this.cryptoLib.createSymmetricKey();
|
|
589
618
|
const encryptedSymmetricKey = await this.cryptoLib.encrypt(device.publicKey, symmetricKey);
|
|
590
|
-
const encryptedVaultData = await this.persistentStorageManager.getEncryptedVaultState(symmetricKey);
|
|
619
|
+
const encryptedVaultData = await this.persistentStorageManager.getEncryptedVaultState(symmetricKey, device.deviceId);
|
|
591
620
|
this.sendToServer('vault', {
|
|
592
621
|
forDeviceId: device.deviceId,
|
|
593
622
|
nonce: await this.getNonce(),
|
|
@@ -598,17 +627,17 @@ class SyncManager {
|
|
|
598
627
|
}
|
|
599
628
|
/**
|
|
600
629
|
* Add a sync device
|
|
601
|
-
* @param
|
|
630
|
+
* @param device - The device to add
|
|
602
631
|
* @param saveAfter - Whether to save the new vault after adding it (set to false when adding multiple devices)
|
|
603
632
|
*/
|
|
604
|
-
async addSyncDevice(
|
|
605
|
-
if (this.syncDevices.some((d) => d.deviceId ===
|
|
633
|
+
async addSyncDevice(device, saveAfter = true) {
|
|
634
|
+
if (this.syncDevices.some((d) => d.deviceId === device.deviceId)) {
|
|
606
635
|
// we already have this device
|
|
607
636
|
return;
|
|
608
637
|
}
|
|
609
|
-
this.log('info', `Adding syncdevice ${
|
|
638
|
+
this.log('info', `Adding syncdevice ${device.deviceId} to ${this.deviceId}`);
|
|
610
639
|
this.syncDevices.push({
|
|
611
|
-
...
|
|
640
|
+
...device,
|
|
612
641
|
});
|
|
613
642
|
if (saveAfter) {
|
|
614
643
|
await this.persistentStorageManager.save();
|
|
@@ -617,10 +646,18 @@ class SyncManager {
|
|
|
617
646
|
/**
|
|
618
647
|
* Requests a resilver of the vault
|
|
619
648
|
*/
|
|
620
|
-
requestResilver() {
|
|
649
|
+
async requestResilver() {
|
|
621
650
|
this.sendToServer('startResilver', {
|
|
622
651
|
deviceIds: this.syncDevices.map((d) => d.deviceId),
|
|
652
|
+
nonce: await this.getNonce(),
|
|
623
653
|
});
|
|
654
|
+
// Set requestedResilver to true for 60 seconds, after this we no longer
|
|
655
|
+
// accept vault data
|
|
656
|
+
this.requestedResilver = true;
|
|
657
|
+
if (this.requestedResilverTimeout) {
|
|
658
|
+
clearTimeout(this.requestedResilverTimeout);
|
|
659
|
+
}
|
|
660
|
+
this.requestedResilverTimeout = setTimeout(() => (this.requestedResilver = false), 60 * 1000);
|
|
624
661
|
}
|
|
625
662
|
/**
|
|
626
663
|
* Function to call when the server connection should be closed
|
|
@@ -6,7 +6,7 @@ import TwoFaLib from '../TwoFaLib.mjs';
|
|
|
6
6
|
import LibraryLoader from '../subclasses/LibraryLoader.mjs';
|
|
7
7
|
import type { LockedRepresentationString } from '../interfaces/Vault.mjs';
|
|
8
8
|
import type { PassphraseExtraDict } from '../interfaces/PassphraseExtraDict.js';
|
|
9
|
-
import SaveFunction from '../interfaces/SaveFunction.mjs';
|
|
9
|
+
import { SaveFunction } from '../interfaces/SaveFunction.mjs';
|
|
10
10
|
/**
|
|
11
11
|
* Evaluates the strength of a passphrase.
|
|
12
12
|
* @param libraryLoader - An instance of LibraryLoader.
|
|
@@ -65,7 +65,9 @@ const createNewTwoFaLibVault = async (libraryLoader, deviceType, serverUrl, pass
|
|
|
65
65
|
const { publicKey, privateKey, symmetricKey, encryptedPrivateKey, encryptedSymmetricKey, salt, } = await cryptoLib.createKeys(passphrase);
|
|
66
66
|
await validatePassphraseStrength(libraryLoader, passphrase, passphraseExtraDict);
|
|
67
67
|
const deviceId = genUuidV4();
|
|
68
|
-
const twoFaLib = new TwoFaLib(deviceType, cryptoLib, passphraseExtraDict, privateKey, symmetricKey, encryptedPrivateKey, encryptedSymmetricKey, salt, publicKey,
|
|
68
|
+
const twoFaLib = new TwoFaLib(deviceType, cryptoLib, passphraseExtraDict, privateKey, symmetricKey, encryptedPrivateKey, encryptedSymmetricKey, salt, publicKey, {
|
|
69
|
+
deviceId,
|
|
70
|
+
}, [], saveFunction, {
|
|
69
71
|
serverUrl,
|
|
70
72
|
devices: [],
|
|
71
73
|
commandSendQueue: [],
|
|
@@ -107,7 +109,10 @@ const loadTwoFaLibFromLockedRepesentation = async (libraryLoader, deviceType, pa
|
|
|
107
109
|
!vaultState.sync?.devices) {
|
|
108
110
|
throw new InitializationError('encryptedVaultState is incomplete or corrupted');
|
|
109
111
|
}
|
|
110
|
-
return new TwoFaLib(deviceType, cryptoLib, passphraseExtraDict, privateKey, symmetricKey, lockedRepresentation.encryptedPrivateKey, lockedRepresentation.encryptedSymmetricKey, lockedRepresentation.salt, publicKey,
|
|
112
|
+
return new TwoFaLib(deviceType, cryptoLib, passphraseExtraDict, privateKey, symmetricKey, lockedRepresentation.encryptedPrivateKey, lockedRepresentation.encryptedSymmetricKey, lockedRepresentation.salt, publicKey, {
|
|
113
|
+
deviceId: vaultState.deviceId,
|
|
114
|
+
deviceFriendlyName: vaultState.deviceFriendlyName,
|
|
115
|
+
}, vaultState.vault, saveFunction, vaultState.sync);
|
|
111
116
|
};
|
|
112
117
|
/**
|
|
113
118
|
* Returns utility functions useful in creating a new twoFaLib vault
|
|
@@ -162,6 +162,7 @@ export const processImportLines = async (lines, importFromUri) => {
|
|
|
162
162
|
* @returns A promise that resolves to the encrypted data.
|
|
163
163
|
*/
|
|
164
164
|
export const encryptExport = async (openPgpLib, data, password) => {
|
|
165
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
165
166
|
const encrypted = await openPgpLib.encrypt({
|
|
166
167
|
message: await openPgpLib.createMessage({ text: data }),
|
|
167
168
|
passwords: [password],
|
|
@@ -17,7 +17,7 @@ export declare const decodeInitiatorData: (initiatorData: string | Uint8Array |
|
|
|
17
17
|
* @param jsonObj - The JSONified Uint8Array to convert.
|
|
18
18
|
* @returns A Uint8Array containing the values of the JSONified Uint8Array.
|
|
19
19
|
*/
|
|
20
|
-
export declare const jsonifiedUint8ArraytoUint8Array: (jsonObj: Record<string, number>) => Uint8Array
|
|
20
|
+
export declare const jsonifiedUint8ArraytoUint8Array: (jsonObj: Record<string, number>) => Uint8Array<ArrayBuffer>;
|
|
21
21
|
/**
|
|
22
22
|
* Converts a JSON object to a record of Uint8Arrays.
|
|
23
23
|
* @param jsonObj - The JSON object to convert.
|
package/package.json
CHANGED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { InvalidCommandError, TwoFALibError } from '../../TwoFALibError.mjs';
|
|
2
|
-
import Command from '../BaseCommand.mjs';
|
|
3
|
-
/**
|
|
4
|
-
* Represents a command that when executed changes the meta info of a sync device
|
|
5
|
-
*/
|
|
6
|
-
class ChangeSyncDeviceMetaCommand extends Command {
|
|
7
|
-
/**
|
|
8
|
-
* Creates a new ChangeSyncDeviceMetaCommand instance.
|
|
9
|
-
* @inheritdoc
|
|
10
|
-
* @param data - The id of the device to change and the new meta info
|
|
11
|
-
*/
|
|
12
|
-
constructor(data, id, timestamp, version, fromRemote = false) {
|
|
13
|
-
super('ChangeSyncDeviceMeta', data, id, timestamp, version, fromRemote);
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Executes the command to change the sync device metadata
|
|
17
|
-
* @inheritdoc
|
|
18
|
-
* @throws {InvalidCommandError} If the referenced sync device cannot be found
|
|
19
|
-
*/
|
|
20
|
-
execute(mediator) {
|
|
21
|
-
const syncManager = mediator.getComponent('syncManager');
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/dot-notation
|
|
23
|
-
const device = syncManager['syncDevices'].find((d) => d.deviceId === this.data.deviceId);
|
|
24
|
-
if (!device) {
|
|
25
|
-
throw new InvalidCommandError('Trying to change meta of device that is not found');
|
|
26
|
-
}
|
|
27
|
-
device.meta = this.data.newMeta;
|
|
28
|
-
return Promise.resolve();
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* @inheritdoc
|
|
32
|
-
*/
|
|
33
|
-
createUndoCommand() {
|
|
34
|
-
throw new TwoFALibError('Not implemented yet');
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
export default ChangeSyncDeviceMetaCommand;
|