favalib 0.0.15 → 0.0.17
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 -0
- package/build/Command/commandConstructors.mjs +2 -0
- package/build/Command/commands/RemoveSyncDeviceCommand.d.mts +33 -0
- package/build/Command/commands/RemoveSyncDeviceCommand.mjs +41 -0
- package/build/FavaLib.d.mts +8 -2
- package/build/FavaLib.mjs +13 -0
- package/build/interfaces/CommandTypes.d.mts +5 -1
- package/build/interfaces/CryptoLib.d.mts +2 -9
- package/build/interfaces/Entry.d.mts +3 -0
- package/build/interfaces/SyncTypes.d.mts +2 -1
- package/build/interfaces/Vault.d.mts +3 -3
- package/build/platformProviders/browser/cryptoLib.d.mts +1 -1
- package/build/platformProviders/browser/cryptoLib.mjs +1 -1
- package/build/platformProviders/browser/openPgpLib.mjs +1 -0
- package/build/platformProviders/node/cryptoLib.d.mts +1 -1
- package/build/platformProviders/node/cryptoLib.mjs +1 -1
- package/build/platformProviders/node/openPgpLib.mjs +1 -0
- package/build/subclasses/SyncManager.d.mts +8 -1
- package/build/subclasses/SyncManager.mjs +19 -0
- package/build/subclasses/VaultDataManager.d.mts +2 -2
- package/build/subclasses/VaultDataManager.mjs +4 -4
- package/build/subclasses/VaultOperationsManager.d.mts +4 -4
- package/build/subclasses/VaultOperationsManager.mjs +20 -24
- package/build/utils/creationUtils.d.mts +2 -2
- package/build/utils/creationUtils.mjs +2 -4
- package/build/utils/exportImportUtils.mjs +17 -2
- package/package.json +21 -20
|
@@ -3,11 +3,13 @@ import AddSyncDeviceCommand from './commands/AddSyncDeviceCommand.mjs';
|
|
|
3
3
|
import DeleteEntryCommand from './commands/DeleteEntryCommand.mjs';
|
|
4
4
|
import UpdateEntryCommand from './commands/UpdateEntryCommand.mjs';
|
|
5
5
|
import ChangeDeviceInfoCommand from './commands/ChangeDeviceInfoCommand.mjs';
|
|
6
|
+
import RemoveSyncDeviceCommand from './commands/RemoveSyncDeviceCommand.mjs';
|
|
6
7
|
declare const commandConstructors: {
|
|
7
8
|
AddEntry: typeof AddEntryCommand;
|
|
8
9
|
DeleteEntry: typeof DeleteEntryCommand;
|
|
9
10
|
UpdateEntry: typeof UpdateEntryCommand;
|
|
10
11
|
AddSyncDevice: typeof AddSyncDeviceCommand;
|
|
11
12
|
ChangeDeviceInfo: typeof ChangeDeviceInfoCommand;
|
|
13
|
+
RemoveSyncDevice: typeof RemoveSyncDeviceCommand;
|
|
12
14
|
};
|
|
13
15
|
export default commandConstructors;
|
|
@@ -3,11 +3,13 @@ import AddSyncDeviceCommand from './commands/AddSyncDeviceCommand.mjs';
|
|
|
3
3
|
import DeleteEntryCommand from './commands/DeleteEntryCommand.mjs';
|
|
4
4
|
import UpdateEntryCommand from './commands/UpdateEntryCommand.mjs';
|
|
5
5
|
import ChangeDeviceInfoCommand from './commands/ChangeDeviceInfoCommand.mjs';
|
|
6
|
+
import RemoveSyncDeviceCommand from './commands/RemoveSyncDeviceCommand.mjs';
|
|
6
7
|
const commandConstructors = {
|
|
7
8
|
AddEntry: AddEntryCommand,
|
|
8
9
|
DeleteEntry: DeleteEntryCommand,
|
|
9
10
|
UpdateEntry: UpdateEntryCommand,
|
|
10
11
|
AddSyncDevice: AddSyncDeviceCommand,
|
|
11
12
|
ChangeDeviceInfo: ChangeDeviceInfoCommand,
|
|
13
|
+
RemoveSyncDevice: RemoveSyncDeviceCommand,
|
|
12
14
|
};
|
|
13
15
|
export default commandConstructors;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type FavaLibMediator from '../../FavaLibMediator.mjs';
|
|
2
|
+
import Command from '../BaseCommand.mjs';
|
|
3
|
+
import type { DeviceId } from '../../interfaces/SyncTypes.mjs';
|
|
4
|
+
export interface RemoveSyncDeviceData {
|
|
5
|
+
deviceId: DeviceId;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Represents a command that when executed removes a sync device from the vault.
|
|
9
|
+
*/
|
|
10
|
+
declare class RemoveSyncDeviceCommand extends Command<RemoveSyncDeviceData> {
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new RemoveSyncDeviceCommand instance.
|
|
13
|
+
* @inheritdoc
|
|
14
|
+
* @param data - The id of the device to be removed.
|
|
15
|
+
*/
|
|
16
|
+
constructor(data: RemoveSyncDeviceData, id?: string, timestamp?: number, version?: string, fromRemote?: boolean);
|
|
17
|
+
/**
|
|
18
|
+
* Executes the command to remove a sync device
|
|
19
|
+
* @inheritdoc
|
|
20
|
+
* @throws {InvalidCommandError} If the command data is invalid.
|
|
21
|
+
*/
|
|
22
|
+
execute(mediator: FavaLibMediator): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* @inheritdoc
|
|
25
|
+
*/
|
|
26
|
+
createUndoCommand(): Command;
|
|
27
|
+
/**
|
|
28
|
+
* Validates the command data.
|
|
29
|
+
* @returns True if the command data is valid, false otherwise.
|
|
30
|
+
*/
|
|
31
|
+
validate(): boolean;
|
|
32
|
+
}
|
|
33
|
+
export default RemoveSyncDeviceCommand;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { InvalidCommandError, FavaLibError } from '../../FavaLibError.mjs';
|
|
2
|
+
import Command from '../BaseCommand.mjs';
|
|
3
|
+
/**
|
|
4
|
+
* Represents a command that when executed removes a sync device from the vault.
|
|
5
|
+
*/
|
|
6
|
+
class RemoveSyncDeviceCommand extends Command {
|
|
7
|
+
/**
|
|
8
|
+
* Creates a new RemoveSyncDeviceCommand instance.
|
|
9
|
+
* @inheritdoc
|
|
10
|
+
* @param data - The id of the device to be removed.
|
|
11
|
+
*/
|
|
12
|
+
constructor(data, id, timestamp, version, fromRemote = false) {
|
|
13
|
+
super('RemoveSyncDevice', data, id, timestamp, version, fromRemote);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Executes the command to remove a sync device
|
|
17
|
+
* @inheritdoc
|
|
18
|
+
* @throws {InvalidCommandError} If the command data is invalid.
|
|
19
|
+
*/
|
|
20
|
+
async execute(mediator) {
|
|
21
|
+
if (!this.validate()) {
|
|
22
|
+
throw new InvalidCommandError('Invalid RemoveSyncDevice command');
|
|
23
|
+
}
|
|
24
|
+
const syncManager = mediator.getComponent('syncManager');
|
|
25
|
+
await syncManager.removeSyncDevice(this.data.deviceId);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* @inheritdoc
|
|
29
|
+
*/
|
|
30
|
+
createUndoCommand() {
|
|
31
|
+
throw new FavaLibError('Not implemented yet');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validates the command data.
|
|
35
|
+
* @returns True if the command data is valid, false otherwise.
|
|
36
|
+
*/
|
|
37
|
+
validate() {
|
|
38
|
+
return this.data.deviceId !== undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export default RemoveSyncDeviceCommand;
|
package/build/FavaLib.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TypedEventTarget } from 'typescript-event-target';
|
|
2
2
|
import type { PlatformProviders } from './interfaces/PlatformProviders.mjs';
|
|
3
3
|
import type { EncryptedPrivateKey, EncryptedSymmetricKey, PrivateKey, PublicKey, Salt, SymmetricKey } from './interfaces/CryptoLib.mjs';
|
|
4
|
-
import type { DeviceFriendlyName, DeviceType } from './interfaces/SyncTypes.mjs';
|
|
4
|
+
import type { DeviceFriendlyName, DeviceId, DeviceType } from './interfaces/SyncTypes.mjs';
|
|
5
5
|
import type { FavaLibEventMapEvents } from './interfaces/Events.mjs';
|
|
6
6
|
import type { PasswordExtraDict } from './interfaces/PasswordExtraDict.js';
|
|
7
7
|
import type { Vault, VaultSyncState } from './interfaces/Vault.mjs';
|
|
@@ -27,7 +27,7 @@ declare class FavaLib extends TypedEventTarget<FavaLibEventMapEvents> {
|
|
|
27
27
|
* @returns The meta info for this device.
|
|
28
28
|
*/
|
|
29
29
|
get meta(): {
|
|
30
|
-
deviceId:
|
|
30
|
+
deviceId: DeviceId;
|
|
31
31
|
deviceFriendlyName: string | DeviceFriendlyName;
|
|
32
32
|
deviceType: DeviceType;
|
|
33
33
|
};
|
|
@@ -86,6 +86,12 @@ declare class FavaLib extends TypedEventTarget<FavaLibEventMapEvents> {
|
|
|
86
86
|
* @param deviceFriendlyName Human readable name for the device
|
|
87
87
|
*/
|
|
88
88
|
setDeviceFriendlyName(deviceFriendlyName: DeviceFriendlyName): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Remove a sync device from the vault. Synced to all other devices.
|
|
91
|
+
* @param deviceId The id of the device to remove
|
|
92
|
+
* @throws {FavaLibError} If trying to remove the current device.
|
|
93
|
+
*/
|
|
94
|
+
removeSyncDevice(deviceId: DeviceId): Promise<void>;
|
|
89
95
|
/**
|
|
90
96
|
* Dispatches a library event.
|
|
91
97
|
* @param eventType - The type of the event to dispatch, uses the FavaLibEvent enum.
|
package/build/FavaLib.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import VaultOperationsManager from './subclasses/VaultOperationsManager.mjs';
|
|
|
11
11
|
import CommandManager from './subclasses/CommandManager.mjs';
|
|
12
12
|
import StorageOperationsManager from './subclasses/StorageOperationsManager.mjs';
|
|
13
13
|
import ChangeDeviceInfoCommand from './Command/commands/ChangeDeviceInfoCommand.mjs';
|
|
14
|
+
import RemoveSyncDeviceCommand from './Command/commands/RemoveSyncDeviceCommand.mjs';
|
|
14
15
|
/**
|
|
15
16
|
* The Two-Factor Library, this is the main entry point.
|
|
16
17
|
*/
|
|
@@ -212,6 +213,18 @@ class FavaLib extends TypedEventTarget {
|
|
|
212
213
|
}
|
|
213
214
|
await this.mediator.getComponent('commandManager').execute(command);
|
|
214
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Remove a sync device from the vault. Synced to all other devices.
|
|
218
|
+
* @param deviceId The id of the device to remove
|
|
219
|
+
* @throws {FavaLibError} If trying to remove the current device.
|
|
220
|
+
*/
|
|
221
|
+
async removeSyncDevice(deviceId) {
|
|
222
|
+
if (deviceId === this.favaMeta.deviceId) {
|
|
223
|
+
throw new FavaLibError('Cannot remove the current device');
|
|
224
|
+
}
|
|
225
|
+
const command = RemoveSyncDeviceCommand.create({ deviceId });
|
|
226
|
+
await this.mediator.getComponent('commandManager').execute(command);
|
|
227
|
+
}
|
|
215
228
|
/**
|
|
216
229
|
* Dispatches a library event.
|
|
217
230
|
* @param eventType - The type of the event to dispatch, uses the FavaLibEvent enum.
|
|
@@ -3,6 +3,7 @@ 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
5
|
import type { ChangeDeviceInfoData } from '../Command/commands/ChangeDeviceInfoCommand.mjs';
|
|
6
|
+
import type { RemoveSyncDeviceData } from '../Command/commands/RemoveSyncDeviceCommand.mjs';
|
|
6
7
|
export type SyncCommand = ({
|
|
7
8
|
type: 'AddEntry';
|
|
8
9
|
data: AddEntryData;
|
|
@@ -18,7 +19,10 @@ export type SyncCommand = ({
|
|
|
18
19
|
} | {
|
|
19
20
|
type: 'ChangeDeviceInfo';
|
|
20
21
|
data: ChangeDeviceInfoData;
|
|
22
|
+
} | {
|
|
23
|
+
type: 'RemoveSyncDevice';
|
|
24
|
+
data: RemoveSyncDeviceData;
|
|
21
25
|
}) & {
|
|
22
26
|
id: string;
|
|
23
27
|
};
|
|
24
|
-
export type CommandData = AddEntryData | DeleteEntryData | UpdateEntryData | AddSyncDeviceData | ChangeDeviceInfoData;
|
|
28
|
+
export type CommandData = AddEntryData | DeleteEntryData | UpdateEntryData | AddSyncDeviceData | ChangeDeviceInfoData | RemoveSyncDeviceData;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Tagged } from 'type-fest';
|
|
2
|
-
|
|
2
|
+
import type { Encrypted, EncryptedSymmetricKey, PublicKey, SymmetricKey } from 'favatypes';
|
|
3
|
+
export type { Encrypted, EncryptedPublicKey, EncryptedSymmetricKey, PublicKey, SymmetricKey, } from 'favatypes';
|
|
3
4
|
/** Represents a password */
|
|
4
5
|
export type Password = Tagged<string, 'Password'>;
|
|
5
6
|
/** Represents a passwordHash */
|
|
@@ -8,18 +9,10 @@ export type PasswordHash = Tagged<string, 'PasswordHash'>;
|
|
|
8
9
|
export type Salt = Tagged<string, 'Salt'>;
|
|
9
10
|
/** Represents a private key */
|
|
10
11
|
export type PrivateKey = Tagged<string, 'PrivateKey'>;
|
|
11
|
-
/** Represents a public key */
|
|
12
|
-
export type PublicKey = Tagged<string, 'PublicKey'>;
|
|
13
|
-
/** Represents an symmetric key */
|
|
14
|
-
export type SymmetricKey = Tagged<string, 'SymmetricKey'>;
|
|
15
12
|
/** Represents a sync (symmetric) key (base64 encoded) */
|
|
16
13
|
export type SyncKey = Tagged<SymmetricKey, 'SyncKey'>;
|
|
17
14
|
/** Represents an encrypted private key (base64 encoded) */
|
|
18
15
|
export type EncryptedPrivateKey = Encrypted<PrivateKey>;
|
|
19
|
-
/** Represents an encrypted symmetric key (base64 encoded) */
|
|
20
|
-
export type EncryptedSymmetricKey = Encrypted<SymmetricKey>;
|
|
21
|
-
/** Represents an encrypted public key (base64 encoded) */
|
|
22
|
-
export type EncryptedPublicKey = Encrypted<PublicKey>;
|
|
23
16
|
/**
|
|
24
17
|
* Interface for cryptographic operations.
|
|
25
18
|
* The implementation in CryptoProviders/node is the reference implementation
|
|
@@ -2,11 +2,14 @@ import type { LiteralUnion } from 'type-fest';
|
|
|
2
2
|
import type { Tagged } from 'type-fest';
|
|
3
3
|
export type EntryId = Tagged<string, 'TotpId'>;
|
|
4
4
|
export type EntryType = LiteralUnion<'TOTP', string>;
|
|
5
|
+
export type MatchType = 'BaseDomain';
|
|
5
6
|
export interface EntryMeta {
|
|
6
7
|
id: EntryId;
|
|
7
8
|
name: string;
|
|
8
9
|
issuer: string;
|
|
9
10
|
type: EntryType;
|
|
11
|
+
match: string | null;
|
|
12
|
+
matchType: MatchType | null;
|
|
10
13
|
addedAt: number;
|
|
11
14
|
updatedAt: number | null;
|
|
12
15
|
}
|
|
@@ -2,7 +2,8 @@ import type { Tagged } from 'type-fest';
|
|
|
2
2
|
import type { JPakeThreePass, Round1Result } from 'jpake-ts';
|
|
3
3
|
import type { PublicKey, SyncKey } from './CryptoLib.mjs';
|
|
4
4
|
import type { Vault, VaultSyncState } from './Vault.mjs';
|
|
5
|
-
|
|
5
|
+
import type { DeviceId } from 'favatypes';
|
|
6
|
+
export type { DeviceId } from 'favatypes';
|
|
6
7
|
export type DeviceType = Tagged<string, 'DeviceType'>;
|
|
7
8
|
export type DeviceFriendlyName = Tagged<string, 'DeviceFriendlyName'>;
|
|
8
9
|
export interface DeviceInfo {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { Tagged } from 'type-fest';
|
|
2
|
-
import type {
|
|
2
|
+
import type { EncryptedPrivateKey, EncryptedSymmetricKey, Salt } from './CryptoLib.mjs';
|
|
3
3
|
import type Entry from './Entry.mjs';
|
|
4
4
|
import type { DeviceFriendlyName, DeviceId, SyncDevice } from './SyncTypes.mjs';
|
|
5
5
|
import type { SyncCommandFromClient } from 'favaserver/ClientMessage';
|
|
6
|
+
import type { EncryptedVaultStateString } from 'favatypes';
|
|
7
|
+
export type { EncryptedVaultStateString, VaultStateString } from 'favatypes';
|
|
6
8
|
export type Vault = Entry[];
|
|
7
|
-
export type EncryptedVaultStateString = Encrypted<VaultStateString>;
|
|
8
9
|
export interface LockedRepresentation {
|
|
9
10
|
encryptedPrivateKey: EncryptedPrivateKey;
|
|
10
11
|
encryptedSymmetricKey: EncryptedSymmetricKey;
|
|
@@ -28,4 +29,3 @@ export interface VaultState {
|
|
|
28
29
|
vault: Vault;
|
|
29
30
|
sync: VaultSyncState;
|
|
30
31
|
}
|
|
31
|
-
export type VaultStateString = Tagged<string, 'VaultState'>;
|
|
@@ -38,8 +38,8 @@ declare class BrowserCryptoLib implements CryptoLib {
|
|
|
38
38
|
*/
|
|
39
39
|
decryptKeys(encryptedPrivateKey: EncryptedPrivateKey, encryptedSymmetricKey: EncryptedSymmetricKey, salt: Salt, password: Password): Promise<{
|
|
40
40
|
privateKey: PrivateKey;
|
|
41
|
-
publicKey: PublicKey;
|
|
42
41
|
symmetricKey: SymmetricKey;
|
|
42
|
+
publicKey: PublicKey;
|
|
43
43
|
}>;
|
|
44
44
|
/**
|
|
45
45
|
* @inheritdoc
|
|
@@ -79,7 +79,7 @@ class BrowserCryptoLib {
|
|
|
79
79
|
// recreate passwordHash
|
|
80
80
|
const passwordHash = await generatePasswordHash(salt, password);
|
|
81
81
|
const { privateKey, publicKey } = await this.decryptPrivateKey(encryptedPrivateKey, passwordHash);
|
|
82
|
-
const symmetricKey =
|
|
82
|
+
const symmetricKey = await this.decrypt(privateKey, encryptedSymmetricKey);
|
|
83
83
|
return { privateKey, publicKey, symmetricKey };
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
@@ -13,6 +13,7 @@ export class BrowserOpenPgpLib {
|
|
|
13
13
|
if (!this.openPgpModule) {
|
|
14
14
|
this.openPgpModule = await import('openpgp');
|
|
15
15
|
// enable Authenticated Encryption with Associated Data
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
16
17
|
this.openPgpModule.config.aeadProtect = true;
|
|
17
18
|
}
|
|
18
19
|
return this.openPgpModule;
|
|
@@ -31,8 +31,8 @@ declare class NodeCryptoLib implements CryptoLib {
|
|
|
31
31
|
*/
|
|
32
32
|
decryptKeys(encryptedPrivateKey: EncryptedPrivateKey, encryptedSymmetricKey: EncryptedSymmetricKey, salt: Salt, password: Password): Promise<{
|
|
33
33
|
privateKey: PrivateKey;
|
|
34
|
-
publicKey: PublicKey;
|
|
35
34
|
symmetricKey: SymmetricKey;
|
|
35
|
+
publicKey: PublicKey;
|
|
36
36
|
}>;
|
|
37
37
|
/**
|
|
38
38
|
* @inheritdoc
|
|
@@ -119,7 +119,7 @@ class NodeCryptoLib {
|
|
|
119
119
|
format: 'pem',
|
|
120
120
|
});
|
|
121
121
|
// Decrypt the symmetric key
|
|
122
|
-
const symmetricKey =
|
|
122
|
+
const symmetricKey = await this.decrypt(privateKey, encryptedSymmetricKey);
|
|
123
123
|
return { privateKey, publicKey, symmetricKey };
|
|
124
124
|
}
|
|
125
125
|
/**
|
|
@@ -13,6 +13,7 @@ export class NodeOpenPgpLib {
|
|
|
13
13
|
if (!this.openPgpModule) {
|
|
14
14
|
this.openPgpModule = await import('openpgp');
|
|
15
15
|
// enable Authenticated Encryption with Associated Data
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
16
17
|
this.openPgpModule.config.aeadProtect = true;
|
|
17
18
|
}
|
|
18
19
|
return this.openPgpModule;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SyncDevice, PublicSyncDevice, DeviceType } from '../interfaces/SyncTypes.mjs';
|
|
1
|
+
import { SyncDevice, PublicSyncDevice, DeviceType, DeviceId } 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 FavaLibMediator from '../FavaLibMediator.mjs';
|
|
@@ -176,6 +176,13 @@ declare class SyncManager {
|
|
|
176
176
|
* @param saveAfter - Whether to save the new vault after adding it (set to false when adding multiple devices)
|
|
177
177
|
*/
|
|
178
178
|
addSyncDevice(device: SyncDevice, saveAfter?: boolean): Promise<void>;
|
|
179
|
+
/**
|
|
180
|
+
* Remove a sync device
|
|
181
|
+
* @param deviceId - The id of the device to remove
|
|
182
|
+
* @param saveAfter - Whether to save the vault after removing the device
|
|
183
|
+
* @returns The removed device, or undefined if it was not present
|
|
184
|
+
*/
|
|
185
|
+
removeSyncDevice(deviceId: DeviceId, saveAfter?: boolean): Promise<SyncDevice | undefined>;
|
|
179
186
|
/**
|
|
180
187
|
* Requests a resilver of the vault
|
|
181
188
|
*/
|
|
@@ -644,6 +644,25 @@ class SyncManager {
|
|
|
644
644
|
await this.persistentStorageManager.save();
|
|
645
645
|
}
|
|
646
646
|
}
|
|
647
|
+
/**
|
|
648
|
+
* Remove a sync device
|
|
649
|
+
* @param deviceId - The id of the device to remove
|
|
650
|
+
* @param saveAfter - Whether to save the vault after removing the device
|
|
651
|
+
* @returns The removed device, or undefined if it was not present
|
|
652
|
+
*/
|
|
653
|
+
async removeSyncDevice(deviceId, saveAfter = true) {
|
|
654
|
+
const index = this.syncDevices.findIndex((d) => d.deviceId === deviceId);
|
|
655
|
+
if (index === -1) {
|
|
656
|
+
// we don't have this device, nothing to remove
|
|
657
|
+
return undefined;
|
|
658
|
+
}
|
|
659
|
+
this.log('info', `Removing syncdevice ${deviceId} from ${this.deviceId}`);
|
|
660
|
+
const [removed] = this.syncDevices.splice(index, 1);
|
|
661
|
+
if (saveAfter) {
|
|
662
|
+
await this.persistentStorageManager.save();
|
|
663
|
+
}
|
|
664
|
+
return removed;
|
|
665
|
+
}
|
|
647
666
|
/**
|
|
648
667
|
* Requests a resilver of the vault
|
|
649
668
|
*/
|
|
@@ -36,11 +36,11 @@ declare class VaultDataManager {
|
|
|
36
36
|
* Generate a time-based one-time password (TOTP) for a specific entry.
|
|
37
37
|
* @param id - The unique identifier of the entry.
|
|
38
38
|
* @param timestamp - Optional timestamp to use for token generation (default is current time).
|
|
39
|
-
* @returns
|
|
39
|
+
* @returns A promise resolving to an object containing the token and the validity period.
|
|
40
40
|
* @throws {EntryNotFoundError} If no entry exists with the given ID.
|
|
41
41
|
* @throws {TokenGenerationError} If token generation fails due to invalid entry data or technical issues.
|
|
42
42
|
*/
|
|
43
|
-
generateTokenForEntry(id: EntryId, timestamp?: number): Token
|
|
43
|
+
generateTokenForEntry(id: EntryId, timestamp?: number): Promise<Token>;
|
|
44
44
|
/**
|
|
45
45
|
* Add a new entry to the vault.
|
|
46
46
|
* @param entry - The entry data to add
|
|
@@ -50,13 +50,13 @@ class VaultDataManager {
|
|
|
50
50
|
* Generate a time-based one-time password (TOTP) for a specific entry.
|
|
51
51
|
* @param id - The unique identifier of the entry.
|
|
52
52
|
* @param timestamp - Optional timestamp to use for token generation (default is current time).
|
|
53
|
-
* @returns
|
|
53
|
+
* @returns A promise resolving to an object containing the token and the validity period.
|
|
54
54
|
* @throws {EntryNotFoundError} If no entry exists with the given ID.
|
|
55
55
|
* @throws {TokenGenerationError} If token generation fails due to invalid entry data or technical issues.
|
|
56
56
|
*/
|
|
57
|
-
generateTokenForEntry(id, timestamp) {
|
|
57
|
+
async generateTokenForEntry(id, timestamp) {
|
|
58
58
|
const entry = this.vault.find((e) => e.id === id);
|
|
59
|
-
if (
|
|
59
|
+
if (entry?.type !== 'TOTP') {
|
|
60
60
|
throw new EntryNotFoundError('TOTP entry not found');
|
|
61
61
|
}
|
|
62
62
|
const { secret, period, algorithm, digits } = entry.payload;
|
|
@@ -69,7 +69,7 @@ class VaultDataManager {
|
|
|
69
69
|
algorithm: algorithm,
|
|
70
70
|
timestamp: timestamp ?? Date.now(),
|
|
71
71
|
};
|
|
72
|
-
const { otp, expires } = TOTP.generate(secret, totpOptions);
|
|
72
|
+
const { otp, expires } = await TOTP.generate(secret, totpOptions);
|
|
73
73
|
return { otp, validFrom: expires - period * 1000, validTill: expires };
|
|
74
74
|
}
|
|
75
75
|
/**
|
|
@@ -37,7 +37,7 @@ declare class VaultOperationsManager {
|
|
|
37
37
|
* @param includeTokens - When true, includes current tokens with the metas.
|
|
38
38
|
* @returns An array of matching entry metas, optionally with tokens.
|
|
39
39
|
*/
|
|
40
|
-
searchEntriesMetas(query: string, includeTokens: true): EntryMetaWithToken[]
|
|
40
|
+
searchEntriesMetas(query: string, includeTokens: true): Promise<EntryMetaWithToken[]>;
|
|
41
41
|
/**
|
|
42
42
|
* @inheritdoc
|
|
43
43
|
*/
|
|
@@ -52,7 +52,7 @@ declare class VaultOperationsManager {
|
|
|
52
52
|
* @param includeTokens - When true, includes current tokens with the metas.
|
|
53
53
|
* @returns An array of all entry metas, optionally with tokens.
|
|
54
54
|
*/
|
|
55
|
-
listEntriesMetas(includeTokens: true): EntryMetaWithToken[]
|
|
55
|
+
listEntriesMetas(includeTokens: true): Promise<EntryMetaWithToken[]>;
|
|
56
56
|
/**
|
|
57
57
|
* @inheritdoc
|
|
58
58
|
*/
|
|
@@ -61,11 +61,11 @@ declare class VaultOperationsManager {
|
|
|
61
61
|
* Generate a time-based one-time password (TOTP) for a specific entry.
|
|
62
62
|
* @param id - The unique identifier of the entry.
|
|
63
63
|
* @param timestamp - Optional timestamp to use for token generation (default is current time).
|
|
64
|
-
* @returns
|
|
64
|
+
* @returns A promise resolving to an object containing the token and between which timestamps it is valid
|
|
65
65
|
* @throws {EntryNotFoundError} If no entry exists with the given ID.
|
|
66
66
|
* @throws {TokenGenerationError} If token generation fails due to invalid entry data or technical issues.
|
|
67
67
|
*/
|
|
68
|
-
generateTokenForEntry(id: EntryId, timestamp?: number): Token
|
|
68
|
+
generateTokenForEntry(id: EntryId, timestamp?: number): Promise<Token>;
|
|
69
69
|
/**
|
|
70
70
|
* Add a new entry to the library.
|
|
71
71
|
* @param entry - The entry data to add (without an ID, as it will be generated).
|
|
@@ -7,6 +7,8 @@ const getMetaForEntry = (entry) => ({
|
|
|
7
7
|
name: entry.name,
|
|
8
8
|
issuer: entry.issuer,
|
|
9
9
|
type: entry.type,
|
|
10
|
+
match: entry.match,
|
|
11
|
+
matchType: entry.matchType,
|
|
10
12
|
addedAt: entry.addedAt,
|
|
11
13
|
updatedAt: entry.updatedAt,
|
|
12
14
|
});
|
|
@@ -63,20 +65,17 @@ class VaultOperationsManager {
|
|
|
63
65
|
*/
|
|
64
66
|
searchEntriesMetas(query, includeTokens) {
|
|
65
67
|
const lowercaseQuery = query.toLowerCase();
|
|
66
|
-
const entries = this.vaultDataManager
|
|
67
|
-
|
|
68
|
+
const entries = this.vaultDataManager
|
|
69
|
+
.getAllEntries()
|
|
68
70
|
.filter((entry) => entry.name.toLowerCase().includes(lowercaseQuery) ||
|
|
69
|
-
entry.issuer.toLowerCase().includes(lowercaseQuery))
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
return meta;
|
|
79
|
-
});
|
|
71
|
+
entry.issuer.toLowerCase().includes(lowercaseQuery));
|
|
72
|
+
if (includeTokens) {
|
|
73
|
+
return Promise.all(entries.map(async (entry) => ({
|
|
74
|
+
...getMetaForEntry(entry),
|
|
75
|
+
token: await this.generateTokenForEntry(entry.id),
|
|
76
|
+
})));
|
|
77
|
+
}
|
|
78
|
+
return entries.map((entry) => getMetaForEntry(entry));
|
|
80
79
|
}
|
|
81
80
|
/**
|
|
82
81
|
* Retrieve a list of all entry IDs in the library.
|
|
@@ -90,22 +89,19 @@ class VaultOperationsManager {
|
|
|
90
89
|
*/
|
|
91
90
|
listEntriesMetas(includeTokens) {
|
|
92
91
|
const entries = this.vaultDataManager.getAllEntries();
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
return meta;
|
|
102
|
-
});
|
|
92
|
+
if (includeTokens) {
|
|
93
|
+
return Promise.all(entries.map(async (entry) => ({
|
|
94
|
+
...getMetaForEntry(entry),
|
|
95
|
+
token: await this.generateTokenForEntry(entry.id),
|
|
96
|
+
})));
|
|
97
|
+
}
|
|
98
|
+
return entries.map((entry) => getMetaForEntry(entry));
|
|
103
99
|
}
|
|
104
100
|
/**
|
|
105
101
|
* Generate a time-based one-time password (TOTP) for a specific entry.
|
|
106
102
|
* @param id - The unique identifier of the entry.
|
|
107
103
|
* @param timestamp - Optional timestamp to use for token generation (default is current time).
|
|
108
|
-
* @returns
|
|
104
|
+
* @returns A promise resolving to an object containing the token and between which timestamps it is valid
|
|
109
105
|
* @throws {EntryNotFoundError} If no entry exists with the given ID.
|
|
110
106
|
* @throws {TokenGenerationError} If token generation fails due to invalid entry data or technical issues.
|
|
111
107
|
*/
|
|
@@ -36,9 +36,9 @@ export declare const getFavaLibVaultCreationUtils: (platformProviders: PlatformP
|
|
|
36
36
|
getPasswordStrength: (password: Password) => Promise<ZxcvbnResult>;
|
|
37
37
|
createNewFavaLibVault: (password: Password) => Promise<{
|
|
38
38
|
favaLib: FavaLib;
|
|
39
|
-
publicKey: import("
|
|
39
|
+
publicKey: import("favatypes").PublicKey;
|
|
40
40
|
encryptedPrivateKey: import("../interfaces/CryptoLib.mjs").EncryptedPrivateKey;
|
|
41
|
-
encryptedSymmetricKey: import("
|
|
41
|
+
encryptedSymmetricKey: import("favatypes").EncryptedSymmetricKey;
|
|
42
42
|
salt: import("../interfaces/CryptoLib.mjs").Salt;
|
|
43
43
|
}>;
|
|
44
44
|
loadFavaLibFromLockedRepesentation: (lockedRepresentationString: LockedRepresentationString, password: Password) => Promise<FavaLib>;
|
|
@@ -95,8 +95,7 @@ const loadFavaLibFromLockedRepesentation = async (libraryLoader, deviceType, pas
|
|
|
95
95
|
const cryptoLib = libraryLoader.getCryptoLib();
|
|
96
96
|
const platformProviders = libraryLoader.getPlatformProviders();
|
|
97
97
|
const lockedRepresentation = JSON.parse(lockedRepresentationString);
|
|
98
|
-
if (!lockedRepresentation ||
|
|
99
|
-
!lockedRepresentation.encryptedPrivateKey ||
|
|
98
|
+
if (!lockedRepresentation?.encryptedPrivateKey ||
|
|
100
99
|
!lockedRepresentation.encryptedSymmetricKey ||
|
|
101
100
|
!lockedRepresentation.salt ||
|
|
102
101
|
!lockedRepresentation.encryptedVaultState) {
|
|
@@ -104,8 +103,7 @@ const loadFavaLibFromLockedRepesentation = async (libraryLoader, deviceType, pas
|
|
|
104
103
|
}
|
|
105
104
|
const { privateKey, symmetricKey, publicKey } = await cryptoLib.decryptKeys(lockedRepresentation.encryptedPrivateKey, lockedRepresentation.encryptedSymmetricKey, lockedRepresentation.salt, password);
|
|
106
105
|
const vaultState = JSON.parse(await cryptoLib.decryptSymmetric(symmetricKey, lockedRepresentation.encryptedVaultState));
|
|
107
|
-
if (!vaultState ||
|
|
108
|
-
!vaultState.deviceId ||
|
|
106
|
+
if (!vaultState?.deviceId ||
|
|
109
107
|
!vaultState.sync?.commandSendQueue ||
|
|
110
108
|
!vaultState.sync?.devices) {
|
|
111
109
|
throw new InitializationError('encryptedVaultState is incomplete or corrupted');
|
|
@@ -50,6 +50,8 @@ export const parseOtpUri = (UrlParser, otpUri) => {
|
|
|
50
50
|
const algorithm = parseOtpAlgorithm(searchParams.get('algorithm'));
|
|
51
51
|
const digits = parseInt(searchParams.get('digits') ?? '6', 10);
|
|
52
52
|
const period = parseInt(searchParams.get('period') ?? '30', 10);
|
|
53
|
+
const match = searchParams.get('match');
|
|
54
|
+
const matchType = searchParams.get('matchType');
|
|
53
55
|
// if searchParams has an issuer, use that
|
|
54
56
|
if (searchParams.get('issuer')) {
|
|
55
57
|
if (!name) {
|
|
@@ -68,6 +70,8 @@ export const parseOtpUri = (UrlParser, otpUri) => {
|
|
|
68
70
|
name: name && name.length > 0 ? name : 'Imported Entry',
|
|
69
71
|
issuer: issuer && issuer.length > 0 ? issuer : 'Unknown Issuer',
|
|
70
72
|
type: 'TOTP',
|
|
73
|
+
match: match ?? null,
|
|
74
|
+
matchType: matchType ?? null,
|
|
71
75
|
payload: {
|
|
72
76
|
secret,
|
|
73
77
|
algorithm,
|
|
@@ -77,9 +81,20 @@ export const parseOtpUri = (UrlParser, otpUri) => {
|
|
|
77
81
|
};
|
|
78
82
|
};
|
|
79
83
|
const generateOtpUrl = (entry) => {
|
|
80
|
-
const { name, issuer, payload } = entry;
|
|
84
|
+
const { name, issuer, payload, match, matchType } = entry;
|
|
81
85
|
const { secret, algorithm, digits, period } = payload;
|
|
82
|
-
|
|
86
|
+
// Note: Using manual encodeURIComponent instead of URLSearchParams because
|
|
87
|
+
// URLSearchParams encodes spaces as '+' while encodeURIComponent uses '%20'.
|
|
88
|
+
// OTP clients expect standard percent encoding (%20) for better compatibility.
|
|
89
|
+
let url = `otpauth://totp/${encodeURIComponent(issuer)}:${encodeURIComponent(name)}?secret=${secret}&issuer=${encodeURIComponent(issuer)}&algorithm=${algorithm}&digits=${digits}&period=${period}`;
|
|
90
|
+
// Add match properties if they exist
|
|
91
|
+
if (match !== null && match !== undefined) {
|
|
92
|
+
url += `&match=${encodeURIComponent(match)}`;
|
|
93
|
+
}
|
|
94
|
+
if (matchType !== null && matchType !== undefined) {
|
|
95
|
+
url += `&matchType=${encodeURIComponent(matchType)}`;
|
|
96
|
+
}
|
|
97
|
+
return url;
|
|
83
98
|
};
|
|
84
99
|
/**
|
|
85
100
|
* Generates an HTML page with QR codes for the provided OTP entries.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "favalib",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -24,37 +24,38 @@
|
|
|
24
24
|
"author": "",
|
|
25
25
|
"license": "ISC",
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@types/node-forge": "^1.3.
|
|
28
|
-
"@types/qrcode": "^1.5.
|
|
29
|
-
"@types/uuid": "^10.0.0",
|
|
27
|
+
"@types/node-forge": "^1.3.14",
|
|
28
|
+
"@types/qrcode": "^1.5.6",
|
|
30
29
|
"@types/whatwg-url": "^13.0.0",
|
|
31
|
-
"@vitest/coverage-v8": "^
|
|
32
|
-
"eslint": "^9.
|
|
33
|
-
"type-fest": "^
|
|
34
|
-
"typedoc": "^0.
|
|
35
|
-
"vitest": "^
|
|
30
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
31
|
+
"eslint": "^9.10.0",
|
|
32
|
+
"type-fest": "^5.6.0",
|
|
33
|
+
"typedoc": "^0.28.19",
|
|
34
|
+
"vitest": "^4.1.7",
|
|
36
35
|
"vitest-websocket-mock": "^0.5.0"
|
|
37
36
|
},
|
|
38
37
|
"dependencies": {
|
|
39
38
|
"@zxcvbn-ts/core": "^3.0.4",
|
|
40
39
|
"@zxcvbn-ts/language-common": "^3.0.4",
|
|
41
40
|
"@zxcvbn-ts/language-en": "^3.0.2",
|
|
42
|
-
"canvas": "^3.
|
|
43
|
-
"esbuild": "^0.
|
|
41
|
+
"canvas": "^3.2.3",
|
|
42
|
+
"esbuild": "^0.28.0",
|
|
44
43
|
"hash-wasm": "^4.12.0",
|
|
45
44
|
"jpake-ts": "^1.0.1",
|
|
46
45
|
"jsqr": "^1.4.0",
|
|
47
|
-
"node-forge": "^1.
|
|
48
|
-
"openpgp": "^6.
|
|
46
|
+
"node-forge": "^1.4.0",
|
|
47
|
+
"openpgp": "^6.3.0",
|
|
49
48
|
"qrcode": "^1.5.4",
|
|
50
|
-
"totp-generator": "
|
|
51
|
-
"typescript-event-target": "^1.1.
|
|
52
|
-
"uint8array-extras": "^1.
|
|
53
|
-
"uuid": "^
|
|
54
|
-
"whatwg-url": "^
|
|
55
|
-
"ws": "^8.
|
|
49
|
+
"totp-generator": "2.0.1",
|
|
50
|
+
"typescript-event-target": "^1.1.2",
|
|
51
|
+
"uint8array-extras": "^1.5.0",
|
|
52
|
+
"uuid": "^14.0.0",
|
|
53
|
+
"whatwg-url": "^16.0.1",
|
|
54
|
+
"ws": "^8.20.1",
|
|
55
|
+
"favaserver": "0.0.1",
|
|
56
|
+
"favatypes": "0.0.1"
|
|
56
57
|
},
|
|
57
58
|
"engines": {
|
|
58
59
|
"node": ">=20"
|
|
59
60
|
}
|
|
60
|
-
}
|
|
61
|
+
}
|