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.
@@ -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 ChangeSyncDeviceMetaCommand from './commands/ChangeSyncDeviceMetaCommand.mjs';
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
- ChangeSyncDeviceMeta: typeof ChangeSyncDeviceMetaCommand;
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 ChangeSyncDeviceMetaCommand from './commands/ChangeSyncDeviceMetaCommand.mjs';
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
- ChangeSyncDeviceMeta: ChangeSyncDeviceMetaCommand,
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.
@@ -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 ChangeSyncDeviceMetaData {
4
+ export interface ChangeSyncDeviceInfoData {
5
5
  deviceId: DeviceId;
6
- newMeta: {
7
- deviceFriendlyName: 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 meta info of a sync device
12
+ * Represents a command that when executed changes the device info of a sync device
13
13
  */
14
- declare class ChangeSyncDeviceMetaCommand extends Command<ChangeSyncDeviceMetaData> {
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: ChangeSyncDeviceMetaData, id?: string, timestamp?: number, version?: string, fromRemote?: boolean);
20
+ constructor(data: ChangeSyncDeviceInfoData, id?: string, timestamp?: number, version?: string, fromRemote?: boolean);
21
21
  /**
22
- * Executes the command to change the sync device metadata
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 ChangeSyncDeviceMetaCommand;
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;
@@ -14,7 +14,7 @@ declare class BrowserCryptoLib implements CryptoLib {
14
14
  /**
15
15
  * @inheritdoc
16
16
  */
17
- getRandomBytes(count: number): Promise<Uint8Array>;
17
+ getRandomBytes(count: number): Promise<Uint8Array<ArrayBuffer>>;
18
18
  /**
19
19
  * @inheritdoc
20
20
  */
@@ -7,7 +7,7 @@ declare class NodeCryptoLib implements CryptoLib {
7
7
  /**
8
8
  * @inheritdoc
9
9
  */
10
- getRandomBytes(count: number): Promise<Uint8Array>;
10
+ getRandomBytes(count: number): Promise<Uint8Array<ArrayBufferLike>>;
11
11
  /**
12
12
  * @inheritdoc
13
13
  */
@@ -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, DeviceId, DeviceType } from './interfaces/SyncTypes.mjs';
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 deviceId: DeviceId;
19
+ private readonly favaMeta;
19
20
  readonly deviceType: DeviceType;
20
- deviceFriendlyName: 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 deviceId - A unique identifier for this device.
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, deviceId: DeviceId, vault?: Vault, saveFunction?: SaveFunction, syncState?: VaultSyncState);
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.
@@ -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 deviceId - A unique identifier for this device.
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, deviceId, vault, saveFunction, syncState) {
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, deviceId, privateKey, symmetricKey, encryptedPrivateKey, encryptedSymmetricKey, salt, saveFunction),
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.deviceId));
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.deviceId);
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 { ChangeSyncDeviceMetaData } from '../Command/commands/ChangeSyncDeviceMetaCommand.mjs';
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: 'ChangeSyncDeviceMeta';
20
- data: ChangeSyncDeviceMetaData;
19
+ type: 'ChangeSyncDeviceInfo';
20
+ data: ChangeSyncDeviceInfoData;
21
21
  }) & {
22
22
  id: string;
23
23
  };
24
- export type CommandData = AddEntryData | DeleteEntryData | UpdateEntryData | AddSyncDeviceData | ChangeSyncDeviceMetaData;
24
+ export type CommandData = AddEntryData | DeleteEntryData | UpdateEntryData | AddSyncDeviceData | ChangeSyncDeviceInfoData;
@@ -0,0 +1,5 @@
1
+ import type { DeviceFriendlyName, DeviceId } from './SyncTypes.mjs';
2
+ export interface FavaMeta {
3
+ deviceId: DeviceId;
4
+ deviceFriendlyName?: DeviceFriendlyName;
5
+ }
@@ -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
- meta?: {
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 { DeviceId } from '../interfaces/SyncTypes.mjs';
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 deviceId;
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 deviceId - The unique identifier of the device.
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, deviceId: DeviceId, privateKey: PrivateKey, symmetricKey: SymmetricKey, encryptedPrivateKey: EncryptedPrivateKey, encryptedSymmetricKey: EncryptedSymmetricKey, salt: Salt, saveFunction?: SaveFunction | undefined);
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
- private getLockedRepresentation;
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 deviceId - The unique identifier of the device.
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, deviceId, privateKey, symmetricKey, encryptedPrivateKey, encryptedSymmetricKey, salt, saveFunction) {
21
+ constructor(mediator, passphraseExtraDict, favaMeta, privateKey, symmetricKey, encryptedPrivateKey, encryptedSymmetricKey, salt, saveFunction) {
22
22
  this.mediator = mediator;
23
23
  this.passphraseExtraDict = passphraseExtraDict;
24
- this.deviceId = deviceId;
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
  /**
@@ -17,6 +17,7 @@ class StorageOperationsManager {
17
17
  }
18
18
  /**
19
19
  * Forces a save.
20
+ * @returns void
20
21
  */
21
22
  async forceSave() {
22
23
  return this.persistentStorage.save();
@@ -1,8 +1,9 @@
1
- import { SyncDevice, DeviceId, PublicSyncDevice } from '../interfaces/SyncTypes.mjs';
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 deviceId - The unique identifier of the device.
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, deviceId: DeviceId);
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 sendFullVaultData;
136
- private importInitialVaultState;
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 deviceInfo - The info about the device
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(deviceInfo: SyncDevice, saveAfter?: boolean): Promise<void>;
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
- meta: d.meta,
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 deviceId - The unique identifier of the device.
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, deviceId) {
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
- syncManager.log('warning', `Error in websocket: ${event}`);
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 'publicKey': {
226
+ case 'publicKeyAndDeviceInfo': {
213
227
  const { data } = message;
214
- const { responderEncryptedPublicKey } = data;
215
- void this.sendFullVaultData(responderEncryptedPublicKey);
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.importInitialVaultState(encryptedVaultData);
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('publicKey', {
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 sendFullVaultData(responderEncryptedPublicKey) {
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 importInitialVaultState(encryptedVaultState) {
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 deviceInfo - The info about the device
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(deviceInfo, saveAfter = true) {
605
- if (this.syncDevices.some((d) => d.deviceId === deviceInfo.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 ${deviceInfo.deviceId} to ${this.deviceId}`);
638
+ this.log('info', `Adding syncdevice ${device.deviceId} to ${this.deviceId}`);
610
639
  this.syncDevices.push({
611
- ...deviceInfo,
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, deviceId, [], saveFunction, {
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, vaultState.deviceId, vaultState.vault, saveFunction, vaultState.sync);
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,6 +1,6 @@
1
1
  {
2
2
  "name": "favalib",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "exports": {
@@ -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;