favalib 0.0.9 → 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;
@@ -18,7 +18,7 @@ declare class TwoFaLib extends TypedEventTarget<TwoFaLibEventMapEvents> {
18
18
  static readonly version = "0.0.1";
19
19
  private readonly favaMeta;
20
20
  readonly deviceType: DeviceType;
21
- deviceFriendlyName: DeviceFriendlyName;
21
+ deviceFriendlyName?: DeviceFriendlyName;
22
22
  private mediator;
23
23
  private readonly publicKey;
24
24
  private readonly privateKey;
@@ -28,7 +28,7 @@ declare class TwoFaLib extends TypedEventTarget<TwoFaLibEventMapEvents> {
28
28
  */
29
29
  get meta(): {
30
30
  deviceId: import("./interfaces/SyncTypes.mjs").DeviceId;
31
- deviceFriendlyName: DeviceFriendlyName;
31
+ deviceFriendlyName: string | DeviceFriendlyName;
32
32
  deviceType: DeviceType;
33
33
  };
34
34
  /**
@@ -42,7 +42,7 @@ declare class TwoFaLib extends TypedEventTarget<TwoFaLibEventMapEvents> {
42
42
  * @param encryptedSymmetricKey - The encrypted symmetric key
43
43
  * @param salt - The salt used for key derivation.
44
44
  * @param publicKey - The public key of the device.
45
- * @param favaMeta - Meta info containing at least a unique identifier for this device.
45
+ * @param favaMeta - Meta info about this device containing at least a unique identifier for this device.
46
46
  * @param vault - The vault data (entries)
47
47
  * @param saveFunction - The function to save the data.
48
48
  * @param syncState - The state of the sync, includes the serverUrl
@@ -81,6 +81,11 @@ declare class TwoFaLib extends TypedEventTarget<TwoFaLibEventMapEvents> {
81
81
  * @param force - Force setting the sync server url, even if no connection can be made
82
82
  */
83
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>;
84
89
  /**
85
90
  * Dispatches a library event.
86
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,6 +10,7 @@ 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
  */
@@ -37,7 +38,7 @@ class TwoFaLib extends TypedEventTarget {
37
38
  * @param encryptedSymmetricKey - The encrypted symmetric key
38
39
  * @param salt - The salt used for key derivation.
39
40
  * @param publicKey - The public key of the device.
40
- * @param favaMeta - Meta info containing at least a unique identifier for this device.
41
+ * @param favaMeta - Meta info about this device containing at least a unique identifier for this device.
41
42
  * @param vault - The vault data (entries)
42
43
  * @param saveFunction - The function to save the data.
43
44
  * @param syncState - The state of the sync, includes the serverUrl
@@ -47,7 +48,6 @@ class TwoFaLib extends TypedEventTarget {
47
48
  */
48
49
  constructor(deviceType, cryptoLib, passphraseExtraDict, privateKey, symmetricKey, encryptedPrivateKey, encryptedSymmetricKey, salt, publicKey, favaMeta, vault, saveFunction, syncState) {
49
50
  super();
50
- this.deviceFriendlyName = '';
51
51
  if (!deviceType) {
52
52
  throw new InitializationError('Device type is required');
53
53
  }
@@ -85,13 +85,14 @@ class TwoFaLib extends TypedEventTarget {
85
85
  ],
86
86
  ['dispatchLibEvent', this.dispatchLibEvent.bind(this)],
87
87
  ['log', this.log.bind(this)],
88
+ ['lib', this],
88
89
  ]);
89
90
  if (vault) {
90
91
  this.mediator.getComponent('vaultDataManager').replaceVault(vault);
91
92
  }
92
93
  if (syncState?.serverUrl) {
93
94
  // Initiate the syncManager
94
- this.mediator.registerComponent('syncManager', new SyncManager(this.mediator, this.publicKey, this.privateKey, this.meta, syncState, this.deviceType));
95
+ this.mediator.registerComponent('syncManager', new SyncManager(this.mediator, this.publicKey, this.privateKey, this.favaMeta, syncState, this.deviceType));
95
96
  }
96
97
  else {
97
98
  // If no syncmanager we're ready now, otherwise the syncmanager is responsible for emitting the ready event
@@ -161,7 +162,7 @@ class TwoFaLib extends TypedEventTarget {
161
162
  devices: [],
162
163
  commandSendQueue: [],
163
164
  };
164
- const newSyncManager = new SyncManager(this.mediator, this.publicKey, this.privateKey, this.meta, newSyncState, this.deviceType);
165
+ const newSyncManager = new SyncManager(this.mediator, this.publicKey, this.privateKey, this.favaMeta, newSyncState, this.deviceType);
165
166
  const success = await new Promise((resolve) => {
166
167
  this.addEventListener(TwoFaLibEvent.ConnectionToSyncServerStatusChanged, (event) => {
167
168
  if (event.detail.newStatus === ConnectionStatus.CONNECTED) {
@@ -192,6 +193,21 @@ class TwoFaLib extends TypedEventTarget {
192
193
  // save
193
194
  await this.persistentStorageManager.save();
194
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
+ }
195
211
  /**
196
212
  * Dispatches a library event.
197
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;
@@ -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
+ }
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
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, };
@@ -3,7 +3,8 @@ import { EncryptedVaultStateString, LockedRepresentationString } from '../interf
3
3
  import type TwoFaLibMediator from '../TwoFaLibMediator.mjs';
4
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
  */
@@ -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
@@ -46,13 +46,15 @@ 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
56
  deviceId: this.favaMeta.deviceId,
57
+ forDeviceId,
56
58
  deviceFriendlyName: this.favaMeta.deviceFriendlyName,
57
59
  sync: {
58
60
  // eslint-disable-next-line @typescript-eslint/dot-notation
@@ -1,4 +1,4 @@
1
- import { SyncDevice, PublicSyncDevice, DeviceFriendlyName, DeviceType } 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';
@@ -32,7 +32,10 @@ declare class SyncManager {
32
32
  private terminateTimeout?;
33
33
  private connectionFailedTimeout?;
34
34
  private shouldReconnect;
35
+ private requestedResilver;
36
+ private requestedResilverTimeout?;
35
37
  private get deviceId();
38
+ private get deviceInfo();
36
39
  /**
37
40
  * Public getter for the command send queue.
38
41
  * @returns The command send queue.
@@ -136,8 +139,8 @@ declare class SyncManager {
136
139
  respondToAddDeviceFlow(initiatorData: string | Uint8Array | File, initiatorDataType: 'text' | 'qr'): Promise<void>;
137
140
  private finishAddDeviceFlowKeyExchangeInitiator;
138
141
  private finishAddDeviceFlowKeyExchangeResponder;
139
- private sendFullVaultData;
140
- private importInitialVaultState;
142
+ private sendFullVaultDataAndSetDeviceInfo;
143
+ private importInitialVault;
141
144
  private importVaultState;
142
145
  /**
143
146
  * Cancels the active add sync device flow.
@@ -169,19 +172,14 @@ declare class SyncManager {
169
172
  private resilver;
170
173
  /**
171
174
  * Add a sync device
172
- * @param deviceInfo - The info about the device
175
+ * @param device - The device to add
173
176
  * @param saveAfter - Whether to save the new vault after adding it (set to false when adding multiple devices)
174
177
  */
175
- addSyncDevice(deviceInfo: SyncDevice, saveAfter?: boolean): Promise<void>;
178
+ addSyncDevice(device: SyncDevice, saveAfter?: boolean): Promise<void>;
176
179
  /**
177
180
  * Requests a resilver of the vault
178
181
  */
179
- requestResilver(): void;
180
- /**
181
- * Change the meta of the current device, allows setting a friendly name
182
- * @param deviceFriendlyName Human readable name for the device
183
- */
184
- setDeviceFriendlyName(deviceFriendlyName: DeviceFriendlyName): Promise<void>;
182
+ requestResilver(): Promise<void>;
185
183
  /**
186
184
  * Function to call when the server connection should be closed
187
185
  */
@@ -4,7 +4,6 @@ import { TwoFaLibEvent } from '../TwoFaLibEvent.mjs';
4
4
  import { decodeInitiatorData, jsonToUint8Array } from '../utils/syncUtils.mjs';
5
5
  import { InitializationError, SyncAddDeviceFlowConflictError, SyncError, SyncInWrongStateError, SyncNoServerConnectionError, TwoFALibError, } from '../TwoFALibError.mjs';
6
6
  import AddSyncDeviceCommand from '../Command/commands/AddSyncDeviceCommand.mjs';
7
- import ChangeSyncDeviceMetaCommand from '../Command/commands/ChangeSyncDeviceMetaCommand.mjs';
8
7
  const IN_TESTING = process.env.NODE_ENV === 'test';
9
8
  const IN_DEV = process.env.NODE_ENV === 'development';
10
9
  export var ConnectionStatus;
@@ -26,6 +25,12 @@ class SyncManager {
26
25
  get deviceId() {
27
26
  return this.favaMeta.deviceId;
28
27
  }
28
+ get deviceInfo() {
29
+ return {
30
+ deviceType: this.deviceType,
31
+ deviceFriendlyName: this.favaMeta.deviceFriendlyName,
32
+ };
33
+ }
29
34
  /**
30
35
  * Public getter for the command send queue.
31
36
  * @returns The command send queue.
@@ -42,7 +47,7 @@ class SyncManager {
42
47
  .filter((d) => d.deviceId !== this.deviceId)
43
48
  .map((d) => ({
44
49
  deviceId: d.deviceId,
45
- meta: d.meta,
50
+ ...d.deviceInfo,
46
51
  }));
47
52
  }
48
53
  /**
@@ -65,6 +70,7 @@ class SyncManager {
65
70
  this.readyEventEmitted = false;
66
71
  this.commandSendQueue = [];
67
72
  this.shouldReconnect = true;
73
+ this.requestedResilver = false;
68
74
  const { serverUrl, devices, commandSendQueue } = syncState;
69
75
  if (!serverUrl.startsWith('wss://')) {
70
76
  if (!serverUrl.startsWith('ws://') && !(IN_DEV || IN_TESTING)) {
@@ -79,6 +85,7 @@ class SyncManager {
79
85
  void this.addSyncDevice({
80
86
  deviceId: this.favaMeta.deviceId,
81
87
  publicKey: this.publicKey,
88
+ deviceInfo: this.deviceInfo,
82
89
  }, false);
83
90
  // if not yet connected after 2 tries, emit ready event so we can continue
84
91
  this.connectionFailedTimeout = setTimeout(() => {
@@ -216,21 +223,27 @@ class SyncManager {
216
223
  void this.finishAddDeviceFlowKeyExchangeResponder(pass3Result);
217
224
  break;
218
225
  }
219
- case 'publicKey': {
226
+ case 'publicKeyAndDeviceInfo': {
220
227
  const { data } = message;
221
- const { responderEncryptedPublicKey } = data;
222
- void this.sendFullVaultData(responderEncryptedPublicKey);
228
+ const { responderEncryptedPublicKey, responderEncryptedDeviceInfo } = data;
229
+ void this.sendFullVaultDataAndSetDeviceInfo(responderEncryptedPublicKey, responderEncryptedDeviceInfo);
223
230
  break;
224
231
  }
225
232
  case 'initialVault': {
226
233
  const { data } = message;
227
234
  const { encryptedVaultData } = data;
228
- void this.importInitialVaultState(encryptedVaultData);
235
+ void this.importInitialVault(encryptedVaultData);
229
236
  break;
230
237
  }
231
238
  case 'vault': {
239
+ if (!this.requestedResilver) {
240
+ throw new SyncError('Got vault data while no resilver was requested, probably replay attack!');
241
+ }
232
242
  const { data } = message;
233
- 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
+ }
234
247
  void this.cryptoLib
235
248
  .decrypt(this.privateKey, encryptedSymmetricKey)
236
249
  .then((symmetricKey) => this.importVaultState(encryptedVaultData, symmetricKey, fromDeviceId));
@@ -422,14 +435,16 @@ class SyncManager {
422
435
  syncKey,
423
436
  };
424
437
  const responderEncryptedPublicKey = await this.cryptoLib.encryptSymmetric(syncKey, this.publicKey);
438
+ const responderEncryptedDeviceInfo = await this.cryptoLib.encryptSymmetric(syncKey, JSON.stringify(this.deviceInfo));
425
439
  // send our public key
426
- this.sendToServer('publicKey', {
440
+ this.sendToServer('publicKeyAndDeviceInfo', {
427
441
  nonce: await this.getNonce(),
428
442
  responderEncryptedPublicKey,
443
+ responderEncryptedDeviceInfo,
429
444
  initiatorDeviceId: this.activeAddDeviceFlow.initiatorDeviceId,
430
445
  });
431
446
  }
432
- async sendFullVaultData(responderEncryptedPublicKey) {
447
+ async sendFullVaultDataAndSetDeviceInfo(responderEncryptedPublicKey, responderEncryptedDeviceInfo) {
433
448
  if (!this.ws || !this.webSocketConnected) {
434
449
  throw new SyncNoServerConnectionError();
435
450
  }
@@ -442,8 +457,10 @@ class SyncManager {
442
457
  const syncKey = this.activeAddDeviceFlow.syncKey;
443
458
  // Decrypt the received public key
444
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));
445
462
  // get the vault data (encrypted with the sync key)
446
- const encryptedVaultData = await this.persistentStorageManager.getEncryptedVaultState(syncKey);
463
+ const encryptedVaultData = await this.persistentStorageManager.getEncryptedVaultState(syncKey, this.activeAddDeviceFlow.responderDeviceId);
447
464
  // Send the encrypted vault data to the server
448
465
  this.sendToServer('initialVault', {
449
466
  nonce: await this.getNonce(),
@@ -454,12 +471,13 @@ class SyncManager {
454
471
  const command = AddSyncDeviceCommand.create({
455
472
  deviceId: this.activeAddDeviceFlow.responderDeviceId,
456
473
  publicKey: decryptedPublicKey,
474
+ deviceInfo: responderDeviceInfo,
457
475
  });
458
476
  await this.commandManager.execute(command);
459
477
  // all done
460
478
  this.activeAddDeviceFlow = undefined;
461
479
  }
462
- async importInitialVaultState(encryptedVaultState) {
480
+ async importInitialVault(encryptedVaultState) {
463
481
  if (this.activeAddDeviceFlow?.state !== 'responder:syncKeyCreated') {
464
482
  throw new SyncInWrongStateError(`Expected responder:syncKeyCreated, got ${this.activeAddDeviceFlow?.state}`);
465
483
  }
@@ -473,6 +491,9 @@ class SyncManager {
473
491
  if (vaultState.deviceId !== expectedDeviceId) {
474
492
  throw new SyncError(`DeviceId mismatch when importing, expected ${expectedDeviceId} got ${vaultState.deviceId}`);
475
493
  }
494
+ if (vaultState.forDeviceId !== this.deviceId) {
495
+ throw new SyncError(`For deviceId mismatch when importing, expected ${this.deviceId} got ${vaultState.forDeviceId}`);
496
+ }
476
497
  for (const device of vaultState.sync.devices) {
477
498
  await this.addSyncDevice(device, false);
478
499
  }
@@ -513,6 +534,7 @@ class SyncManager {
513
534
  // skip ourselves
514
535
  return;
515
536
  }
537
+ // unique symmetricKey per command
516
538
  const symmetricKey = await this.cryptoLib.createSymmetricKey();
517
539
  const encryptedSymmetricKey = await this.cryptoLib.encrypt(device.publicKey, symmetricKey);
518
540
  const encryptedCommand = await this.cryptoLib.encryptSymmetric(symmetricKey, JSON.stringify({
@@ -594,7 +616,7 @@ class SyncManager {
594
616
  }
595
617
  const symmetricKey = await this.cryptoLib.createSymmetricKey();
596
618
  const encryptedSymmetricKey = await this.cryptoLib.encrypt(device.publicKey, symmetricKey);
597
- const encryptedVaultData = await this.persistentStorageManager.getEncryptedVaultState(symmetricKey);
619
+ const encryptedVaultData = await this.persistentStorageManager.getEncryptedVaultState(symmetricKey, device.deviceId);
598
620
  this.sendToServer('vault', {
599
621
  forDeviceId: device.deviceId,
600
622
  nonce: await this.getNonce(),
@@ -605,17 +627,17 @@ class SyncManager {
605
627
  }
606
628
  /**
607
629
  * Add a sync device
608
- * @param deviceInfo - The info about the device
630
+ * @param device - The device to add
609
631
  * @param saveAfter - Whether to save the new vault after adding it (set to false when adding multiple devices)
610
632
  */
611
- async addSyncDevice(deviceInfo, saveAfter = true) {
612
- 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)) {
613
635
  // we already have this device
614
636
  return;
615
637
  }
616
- this.log('info', `Adding syncdevice ${deviceInfo.deviceId} to ${this.deviceId}`);
638
+ this.log('info', `Adding syncdevice ${device.deviceId} to ${this.deviceId}`);
617
639
  this.syncDevices.push({
618
- ...deviceInfo,
640
+ ...device,
619
641
  });
620
642
  if (saveAfter) {
621
643
  await this.persistentStorageManager.save();
@@ -624,29 +646,18 @@ class SyncManager {
624
646
  /**
625
647
  * Requests a resilver of the vault
626
648
  */
627
- requestResilver() {
649
+ async requestResilver() {
628
650
  this.sendToServer('startResilver', {
629
651
  deviceIds: this.syncDevices.map((d) => d.deviceId),
652
+ nonce: await this.getNonce(),
630
653
  });
631
- }
632
- /**
633
- * Change the meta of the current device, allows setting a friendly name
634
- * @param deviceFriendlyName Human readable name for the device
635
- */
636
- async setDeviceFriendlyName(deviceFriendlyName) {
637
- if (deviceFriendlyName.length > 256) {
638
- throw new TwoFALibError('Device friendly name is too long, max 256 characters');
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);
639
659
  }
640
- if (deviceFriendlyName.length < 1) {
641
- throw new TwoFALibError('Device friendly name is too short, min 1 character');
642
- }
643
- this.favaMeta.deviceFriendlyName = deviceFriendlyName;
644
- const newMeta = {
645
- deviceId: this.deviceId,
646
- newMeta: { deviceType: this.deviceType, deviceFriendlyName },
647
- };
648
- const command = ChangeSyncDeviceMetaCommand.create(newMeta);
649
- await this.commandManager.execute(command);
660
+ this.requestedResilverTimeout = setTimeout(() => (this.requestedResilver = false), 60 * 1000);
650
661
  }
651
662
  /**
652
663
  * Function to call when the server connection should be closed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "favalib",
3
- "version": "0.0.9",
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;