@binsky/passman-client-ts 0.1.10 → 0.2.0

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.
Files changed (63) hide show
  1. package/README.md +12 -0
  2. package/lib/Interfaces/Credential/{CredentialInterface.d.ts → DecryptedCredentialInterface.d.ts} +5 -1
  3. package/lib/Interfaces/Credential/EncryptedCredentialInterface.d.ts +12 -11
  4. package/lib/Interfaces/Credential/EncryptedOwnedCredentialFromServerInterface.d.ts +35 -0
  5. package/lib/Interfaces/Credential/EncryptedOwnedCredentialFromServerInterface.js +2 -0
  6. package/lib/Interfaces/Credential/EncryptedOwnedCredentialToUpdateForServerInterface.d.ts +8 -0
  7. package/lib/Interfaces/Credential/EncryptedOwnedCredentialToUpdateForServerInterface.js +2 -0
  8. package/lib/Interfaces/Credential/SerializableTransferCredentialInterface.d.ts +7 -0
  9. package/lib/Interfaces/Credential/SerializableTransferCredentialInterface.js +2 -0
  10. package/lib/Interfaces/DecryptedDataCachingHandlerInterface.d.ts +20 -0
  11. package/lib/Interfaces/DecryptedDataCachingHandlerInterface.js +2 -0
  12. package/lib/Interfaces/NextcloudServer/NextcloudServerInterface.d.ts +8 -3
  13. package/lib/Interfaces/PassmanCrypto/EncryptedStringType.d.ts +4 -0
  14. package/lib/Interfaces/PassmanCrypto/EncryptedStringType.js +2 -0
  15. package/lib/Interfaces/PersistenceInterface.d.ts +10 -0
  16. package/lib/Interfaces/PersistenceInterface.js +2 -0
  17. package/lib/Interfaces/RequestCachingHandlerInterface.d.ts +5 -1
  18. package/lib/Interfaces/Revision/RevisionInterface.d.ts +2 -2
  19. package/lib/Interfaces/ShareService/CredentialShareRequestInterface.d.ts +2 -2
  20. package/lib/Interfaces/ShareService/SerializableACLInterface.d.ts +14 -0
  21. package/lib/Interfaces/ShareService/SerializableACLInterface.js +2 -0
  22. package/lib/Interfaces/Vault/GenericVaultInformationFromServerInterface.d.ts +17 -0
  23. package/lib/Interfaces/Vault/GenericVaultInformationFromServerInterface.js +2 -0
  24. package/lib/Interfaces/Vault/SerializableSpecificVaultInformationFromServerInterface.d.ts +12 -0
  25. package/lib/Interfaces/Vault/SerializableSpecificVaultInformationFromServerInterface.js +2 -0
  26. package/lib/Interfaces/Vault/SerializableTransferFullVaultInterface.d.ts +6 -0
  27. package/lib/Interfaces/Vault/SerializableTransferFullVaultInterface.js +2 -0
  28. package/lib/Interfaces/Vault/SpecificVaultInformationFromServerInterface.d.ts +14 -0
  29. package/lib/Interfaces/Vault/SpecificVaultInformationFromServerInterface.js +2 -0
  30. package/lib/Interfaces/Vault/VaultCreateServerResponseInterface.d.ts +8 -0
  31. package/lib/Interfaces/Vault/VaultCreateServerResponseInterface.js +2 -0
  32. package/lib/Model/Credential.d.ts +70 -19
  33. package/lib/Model/Credential.js +138 -25
  34. package/lib/Model/File.d.ts +7 -7
  35. package/lib/Model/NextcloudServer.d.ts +9 -8
  36. package/lib/Model/NextcloudServer.js +14 -14
  37. package/lib/Model/PreloadedVault.d.ts +20 -0
  38. package/lib/Model/PreloadedVault.js +54 -0
  39. package/lib/Model/Revision.d.ts +3 -3
  40. package/lib/Model/Revision.js +3 -3
  41. package/lib/Model/SharingACL.d.ts +3 -2
  42. package/lib/Model/SharingACL.js +9 -6
  43. package/lib/Model/Vault.d.ts +48 -5
  44. package/lib/Model/Vault.js +141 -61
  45. package/lib/PassmanClient.d.ts +51 -10
  46. package/lib/PassmanClient.js +101 -35
  47. package/lib/Service/CredentialFilterService.d.ts +2 -1
  48. package/lib/Service/CredentialFilterService.js +24 -9
  49. package/lib/Service/DefaultLoggingService.d.ts +3 -0
  50. package/lib/Service/DefaultLoggingService.js +3 -0
  51. package/lib/Service/DefaultPersistenceService.d.ts +12 -0
  52. package/lib/Service/DefaultPersistenceService.js +20 -0
  53. package/lib/Service/OTPService.d.ts +6 -6
  54. package/lib/Service/OTPService.js +17 -7
  55. package/lib/Service/PassmanCrypto.d.ts +9 -4
  56. package/lib/Service/PassmanCrypto.js +6 -6
  57. package/lib/Service/ReEncryptionService.js +2 -2
  58. package/lib/Service/RequestCachingService.d.ts +5 -2
  59. package/lib/Service/RequestCachingService.js +3 -0
  60. package/lib/Service/ShareService.js +2 -4
  61. package/lib/tsconfig.tsbuildinfo +1 -1
  62. package/package.json +3 -1
  63. /package/lib/Interfaces/Credential/{CredentialInterface.js → DecryptedCredentialInterface.js} +0 -0
@@ -1,21 +1,64 @@
1
1
  import { NextcloudServerInterface } from "../Interfaces/NextcloudServer/NextcloudServerInterface";
2
2
  import Credential from "./Credential";
3
- import { VaultInterface } from "../Interfaces/Vault/VaultInterface";
3
+ import { SpecificVaultInformationFromServerInterface } from "../Interfaces/Vault/SpecificVaultInformationFromServerInterface";
4
+ import { SerializableTransferFullVaultInterface } from "../Interfaces/Vault/SerializableTransferFullVaultInterface";
4
5
  export default class Vault {
5
- private data;
6
- private server;
6
+ private _specificVaultInformation;
7
+ private readonly server;
7
8
  private _vaultKey;
8
- constructor(data: VaultInterface, server: NextcloudServerInterface);
9
- static create(vaultName: string, vaultPassword: string, server: NextcloudServerInterface): Promise<Vault>;
9
+ collectedTags: Set<string>;
10
+ private _credentials;
11
+ private constructor();
12
+ /**
13
+ * Filler for the now private Vault constructor. Use this carefully, or even better: do not use it!
14
+ * The constructor was previously used for custom re-creation from cache, this is now handled using a custom persistence service (when constructing PassmanClient).
15
+ * @param _specificVaultInformation
16
+ * @param server
17
+ * @deprecated use Vault.create() or Vault.fetchFullVaultFromServer() (with the option of setting getCachedIfPossible: boolean) instead.
18
+ */
19
+ static createManually(_specificVaultInformation: SpecificVaultInformationFromServerInterface, server: NextcloudServerInterface): Vault;
20
+ /**
21
+ * Create a new vault on the server and return an unlocked full-featured vault instance with fresh data.
22
+ * @param vaultName
23
+ * @param vaultPassword
24
+ * @param server
25
+ * @return new fresh vault instance or void if something went wrong
26
+ */
27
+ static create(vaultName: string, vaultPassword: string, server: NextcloudServerInterface): Promise<Vault | void>;
28
+ /**
29
+ * Creates a locked full-featured vault instance with fresh data (metadata, credentials, shared credentials) from the passman server.
30
+ * Optional: directly unlock by providing the correct vault key.
31
+ */
32
+ static fetchFullVaultFromServer(server: NextcloudServerInterface, guid: string, vaultKey?: string, getCachedIfPossible?: boolean): Promise<Vault | void>;
33
+ /**
34
+ * Clear all request caches for this vault. (not for the specific credentials that's managed by the Credential model)
35
+ */
36
+ clearRequestCache(): Promise<void>;
37
+ /**
38
+ * Reload current vault's metadata and credentials (as well as shared credentials) from the server.
39
+ * @param getCachedIfPossible
40
+ */
10
41
  refresh(getCachedIfPossible?: boolean): Promise<boolean>;
11
42
  update(): Promise<boolean>;
12
43
  delete(currentPassword: string): Promise<boolean>;
13
44
  lock(): void;
45
+ /**
46
+ * Set the given sharing keys for the current vault on the server. Does not (!) store the given keys in the current vault object.
47
+ * @param public_sharing_key
48
+ * @param private_sharing_key
49
+ */
14
50
  updateSharingKeys(public_sharing_key: string, private_sharing_key: string): Promise<void>;
15
51
  testVaultKey(vaultKey: string): boolean;
16
52
  private getChallengingFieldValue;
17
53
  private getFirstOwnedCredential;
18
54
  getCredentialByGuid(guid: string): Credential | undefined;
55
+ getAsSerializable(): SerializableTransferFullVaultInterface;
56
+ /**
57
+ * Returns a locked vault, made out of SerializableTransferFullVaultInterface data.
58
+ * @param serializable
59
+ * @param server
60
+ */
61
+ static fromSerializable(serializable: SerializableTransferFullVaultInterface, server: NextcloudServerInterface): Vault;
19
62
  getServer(): NextcloudServerInterface;
20
63
  get vaultId(): number | null;
21
64
  get vaultKey(): string;
@@ -7,27 +7,48 @@ const Credential_1 = __importDefault(require("./Credential"));
7
7
  const PassmanCrypto_1 = require("../Service/PassmanCrypto");
8
8
  const ShareService_1 = require("../Service/ShareService");
9
9
  const File_1 = require("./File");
10
+ const NextcloudServer_1 = require("./NextcloudServer");
10
11
  class Vault {
11
- data;
12
+ _specificVaultInformation;
12
13
  server;
13
14
  _vaultKey;
14
- constructor(data, server) {
15
- this.data = data;
15
+ collectedTags = new Set();
16
+ _credentials;
17
+ constructor(_specificVaultInformation, server) {
18
+ this._specificVaultInformation = _specificVaultInformation;
16
19
  this.server = server;
20
+ this._credentials = [];
17
21
  }
22
+ /**
23
+ * Filler for the now private Vault constructor. Use this carefully, or even better: do not use it!
24
+ * The constructor was previously used for custom re-creation from cache, this is now handled using a custom persistence service (when constructing PassmanClient).
25
+ * @param _specificVaultInformation
26
+ * @param server
27
+ * @deprecated use Vault.create() or Vault.fetchFullVaultFromServer() (with the option of setting getCachedIfPossible: boolean) instead.
28
+ */
29
+ static createManually(_specificVaultInformation, server) {
30
+ return new Vault(_specificVaultInformation, server);
31
+ }
32
+ /**
33
+ * Create a new vault on the server and return an unlocked full-featured vault instance with fresh data.
34
+ * @param vaultName
35
+ * @param vaultPassword
36
+ * @param server
37
+ * @return new fresh vault instance or void if something went wrong
38
+ */
18
39
  static async create(vaultName, vaultPassword, server) {
19
40
  let vaultResponse = await server.postJson('/vaults', { vault_name: vaultName }, (response) => {
20
41
  server.logger.onError(response.message);
21
42
  });
22
43
  if (vaultResponse) {
23
- const vault = new Vault(vaultResponse, server);
24
- vault._vaultKey = vaultPassword;
44
+ const creator_only_vault = new Vault(vaultResponse, server);
45
+ creator_only_vault._vaultKey = vaultPassword;
25
46
  await PassmanCrypto_1.PassmanCrypto.generateRSAKeypair(2048).then(async (value) => {
26
47
  if (value.keypair) {
27
48
  const pemKeyPair = PassmanCrypto_1.PassmanCrypto.rsaKeyPairToPEM(value.keypair);
28
- await vault.updateSharingKeys(pemKeyPair.publicKey, pemKeyPair.privateKey);
49
+ await creator_only_vault.updateSharingKeys(pemKeyPair.publicKey, pemKeyPair.privateKey);
29
50
  // todo: create hidden test credential
30
- let testCredential = new Credential_1.default(vault, server);
51
+ let testCredential = new Credential_1.default(creator_only_vault, server);
31
52
  testCredential.label = 'Test key for vault ' + vaultName;
32
53
  testCredential.hidden = true;
33
54
  testCredential.password = 'lorum ipsum';
@@ -43,58 +64,88 @@ class Vault {
43
64
  server.logger.onError('Failed to create a new vault rsa key-pair');
44
65
  }
45
66
  });
46
- return vault;
67
+ return await Vault.fetchFullVaultFromServer(server, creator_only_vault.guid, vaultPassword, false);
47
68
  }
48
69
  }
49
- async refresh(getCachedIfPossible = false) {
50
- let vaultResponse = await this.server.getJson('/vaults/' + this.guid, (response) => {
51
- this.server.logger.onError(response.message);
70
+ /**
71
+ * Creates a locked full-featured vault instance with fresh data (metadata, credentials, shared credentials) from the passman server.
72
+ * Optional: directly unlock by providing the correct vault key.
73
+ */
74
+ static async fetchFullVaultFromServer(server, guid, vaultKey, getCachedIfPossible = false) {
75
+ let specificVaultResponse = await server.getJson('/vaults/' + guid, (response) => {
76
+ server.logger.onError(response.message);
52
77
  }, getCachedIfPossible);
53
- if (vaultResponse) {
78
+ if (specificVaultResponse) {
79
+ const vault = new Vault(specificVaultResponse, server);
80
+ // test vault key with the challenge_password value, to have a definite unlock state for restoring decrypted credential data from cache
81
+ let vaultIsUnlocked = false;
82
+ if (vaultKey && vault.testVaultKey(vaultKey)) {
83
+ vault.vaultKey = vaultKey;
84
+ vaultIsUnlocked = true;
85
+ }
54
86
  const credentials = [];
55
- for (const credentialData of vaultResponse.credentials) {
87
+ let collectedTags = [];
88
+ for (const encryptedCredentialDataFromServer of specificVaultResponse.credentials) {
56
89
  try {
57
- credentials.push(await Credential_1.default.fromData(credentialData, this, this.server));
90
+ const credential = Credential_1.default.fromData(encryptedCredentialDataFromServer, vault, server);
91
+ // restore and counting tags only if the correct vaultKey is given (current vault can then be assumed as unlocked)
92
+ if (vaultIsUnlocked === true) {
93
+ if (getCachedIfPossible) {
94
+ await credential.restoreSerializedDecryptedDataCache();
95
+ }
96
+ if (credential.tags && credential.tags.length > 0) {
97
+ collectedTags = collectedTags.concat(credential.tags.map(value => value.text).filter(Boolean));
98
+ }
99
+ }
100
+ credentials.push(credential);
58
101
  }
59
102
  catch (e) {
60
- this.server.logger.anyError(e);
61
- this.server.logger.onError('Failed to decrypt credential: ' + credentialData.label);
103
+ server.logger.anyError(e);
104
+ server.logger.onError('Failed to decrypt credential: ' + encryptedCredentialDataFromServer.label);
62
105
  }
63
106
  }
64
- const credentialsSharedWithUs = await ShareService_1.ShareService.getCredentialsSharedWithUs(this, this.server, getCachedIfPossible);
107
+ vault.collectedTags = new Set(collectedTags);
108
+ const credentialsSharedWithUs = await ShareService_1.ShareService.getCredentialsSharedWithUs(vault, server, getCachedIfPossible);
65
109
  for (const credential of credentialsSharedWithUs) {
66
110
  credentials.push(credential);
67
111
  }
112
+ vault._credentials = credentials;
113
+ // we could also test vault key here when we have all credentials, but it would make it harder to restore decrypted credential cache
114
+ return vault;
115
+ }
116
+ }
117
+ /**
118
+ * Clear all request caches for this vault. (not for the specific credentials that's managed by the Credential model)
119
+ */
120
+ clearRequestCache() {
121
+ return this.server.persistence.getRequestCacheHandler().set(NextcloudServer_1.NextcloudServer.getRequestCachePrefix + '/vaults/' + this.guid, undefined);
122
+ }
123
+ /**
124
+ * Reload current vault's metadata and credentials (as well as shared credentials) from the server.
125
+ * @param getCachedIfPossible
126
+ */
127
+ async refresh(getCachedIfPossible = false) {
128
+ const vaultFromServer = await Vault.fetchFullVaultFromServer(this.server, this.guid, this.vaultKey, getCachedIfPossible);
129
+ if (vaultFromServer) {
130
+ this.collectedTags = vaultFromServer.collectedTags;
131
+ this._credentials = vaultFromServer.credentials;
68
132
  // todo: check if a creation of vault.private_sharing_key for old vaults is required
69
- this.data = {
70
- challenge_password: vaultResponse.challenge_password,
71
- created: vaultResponse.created,
72
- credentials: credentials,
73
- delete_request_pending: vaultResponse.delete_request_pending,
74
- guid: vaultResponse.guid,
75
- last_access: vaultResponse.last_access,
76
- name: vaultResponse.name,
77
- private_sharing_key: vaultResponse.private_sharing_key,
78
- public_sharing_key: vaultResponse.public_sharing_key,
79
- sharing_keys_generated: vaultResponse.sharing_keys_generated,
80
- vault_id: vaultResponse.vault_id,
81
- vault_settings: vaultResponse.vault_settings,
82
- };
133
+ this._specificVaultInformation = vaultFromServer._specificVaultInformation;
83
134
  return true;
84
135
  }
85
136
  return false;
86
137
  }
87
138
  async update() {
88
- const result = await this.server.postJson('/vaults/' + this.data.guid, {
89
- guid: this.data.guid,
90
- vault_id: this.data.vault_id,
91
- name: this.data.name,
92
- created: this.data.created,
93
- public_sharing_key: this.data.public_sharing_key,
94
- last_access: this.data.last_access,
95
- delete_request_pending: this.data.delete_request_pending,
96
- sharing_keys_generated: this.data.sharing_keys_generated,
97
- vault_settings: this.data.vault_settings
139
+ const result = await this.server.postJson('/vaults/' + this._specificVaultInformation.guid, {
140
+ guid: this._specificVaultInformation.guid,
141
+ vault_id: this._specificVaultInformation.vault_id,
142
+ name: this._specificVaultInformation.name,
143
+ created: this._specificVaultInformation.created,
144
+ public_sharing_key: this._specificVaultInformation.public_sharing_key,
145
+ last_access: this._specificVaultInformation.last_access,
146
+ delete_request_pending: this._specificVaultInformation.delete_request_pending,
147
+ sharing_keys_generated: this._specificVaultInformation.sharing_keys_generated,
148
+ vault_settings: this._specificVaultInformation.vault_settings
98
149
  }, (response) => {
99
150
  this.server.logger.onError(response.message);
100
151
  }, 'PATCH');
@@ -135,14 +186,21 @@ class Vault {
135
186
  }
136
187
  lock() {
137
188
  this.vaultKey = null;
189
+ this.collectedTags.clear();
138
190
  // clear decrypted credential cache from memory
139
- for (const credential of this.data.credentials) {
191
+ // no need to touch _specificVaultInformation.credentials since these are fully encrypted
192
+ for (const credential of this._credentials) {
140
193
  credential.clearDecryptedDataCache();
141
194
  }
142
195
  }
196
+ /**
197
+ * Set the given sharing keys for the current vault on the server. Does not (!) store the given keys in the current vault object.
198
+ * @param public_sharing_key
199
+ * @param private_sharing_key
200
+ */
143
201
  async updateSharingKeys(public_sharing_key, private_sharing_key) {
144
- return await this.server.postJson('/vaults/' + this.data.guid + '/sharing-keys', {
145
- guid: this.data.guid,
202
+ return await this.server.postJson('/vaults/' + this._specificVaultInformation.guid + '/sharing-keys', {
203
+ guid: this._specificVaultInformation.guid,
146
204
  public_sharing_key: public_sharing_key,
147
205
  private_sharing_key: PassmanCrypto_1.PassmanCrypto.encryptString(private_sharing_key, this.vaultKey),
148
206
  }, (response) => {
@@ -166,6 +224,7 @@ class Vault {
166
224
  }
167
225
  getChallengingFieldValue() {
168
226
  const testCredential = this.getFirstOwnedCredential();
227
+ // use getEncrypted to prevent trying to decrypt an already decrypted or cached value
169
228
  if (testCredential.getEncrypted().username != null) {
170
229
  return testCredential.getEncrypted().username;
171
230
  }
@@ -179,7 +238,7 @@ class Vault {
179
238
  }
180
239
  getFirstOwnedCredential() {
181
240
  for (let credential of this.credentials) {
182
- if (!credential.hasValidSharedKey() && credential.sharedCredentialEncryptionKey === undefined) {
241
+ if (!credential.hasValidSharedKey() && credential.encryptedSharedCredentialEncryptionKey === undefined) {
183
242
  return credential;
184
243
  }
185
244
  }
@@ -191,11 +250,32 @@ class Vault {
191
250
  }
192
251
  }
193
252
  }
253
+ getAsSerializable() {
254
+ const { credentials, ...serializableSpecificVaultInformation } = this._specificVaultInformation;
255
+ return {
256
+ serializableSpecificVaultInformation,
257
+ encryptedSerializableCredentials: this.credentials.map(credential => credential.getAsSerializable()),
258
+ };
259
+ }
260
+ /**
261
+ * Returns a locked vault, made out of SerializableTransferFullVaultInterface data.
262
+ * @param serializable
263
+ * @param server
264
+ */
265
+ static fromSerializable(serializable, server) {
266
+ const specificVaultInformation = {
267
+ credentials: [],
268
+ ...serializable.serializableSpecificVaultInformation
269
+ };
270
+ const vault = new Vault(specificVaultInformation, server);
271
+ vault._credentials = serializable.encryptedSerializableCredentials.map(serializableCredential => Credential_1.default.fromSerializable(serializableCredential, vault, server));
272
+ return vault;
273
+ }
194
274
  getServer() {
195
275
  return this.server;
196
276
  }
197
277
  get vaultId() {
198
- return this.data.vault_id;
278
+ return this._specificVaultInformation.vault_id;
199
279
  }
200
280
  get vaultKey() {
201
281
  return this._vaultKey;
@@ -204,49 +284,49 @@ class Vault {
204
284
  this._vaultKey = value;
205
285
  }
206
286
  get guid() {
207
- return this.data.guid;
287
+ return this._specificVaultInformation.guid;
208
288
  }
209
289
  get name() {
210
- return this.data.name;
290
+ return this._specificVaultInformation.name;
211
291
  }
212
292
  set name(value) {
213
- this.data.name = value;
293
+ this._specificVaultInformation.name = value;
214
294
  }
215
295
  get created() {
216
- return this.data.created;
296
+ return this._specificVaultInformation.created;
217
297
  }
218
298
  get public_sharing_key() {
219
- return this.data.public_sharing_key;
299
+ return this._specificVaultInformation.public_sharing_key;
220
300
  }
221
301
  get private_sharing_key() {
222
- return PassmanCrypto_1.PassmanCrypto.decryptString(this.data.private_sharing_key, this.vaultKey);
302
+ return PassmanCrypto_1.PassmanCrypto.decryptString(this._specificVaultInformation.private_sharing_key, this.vaultKey);
223
303
  }
224
304
  get sharing_keys_generated() {
225
- return this.data.sharing_keys_generated;
305
+ return this._specificVaultInformation.sharing_keys_generated;
226
306
  }
227
307
  set sharing_keys_generated(value) {
228
- this.data.sharing_keys_generated = value;
308
+ this._specificVaultInformation.sharing_keys_generated = value;
229
309
  }
230
310
  get last_access() {
231
- return this.data.last_access;
311
+ return this._specificVaultInformation.last_access;
232
312
  }
233
313
  get challenge_password() {
234
- return this.data.challenge_password;
314
+ return this._specificVaultInformation.challenge_password;
235
315
  }
236
316
  get delete_request_pending() {
237
- return this.data.delete_request_pending;
317
+ return this._specificVaultInformation.delete_request_pending;
238
318
  }
239
319
  set delete_request_pending(value) {
240
- this.data.delete_request_pending = value;
320
+ this._specificVaultInformation.delete_request_pending = value;
241
321
  }
242
322
  get vault_settings() {
243
- return this.data.vault_settings;
323
+ return this._specificVaultInformation.vault_settings;
244
324
  }
245
325
  set vault_settings(value) {
246
- this.data.vault_settings = value;
326
+ this._specificVaultInformation.vault_settings = value;
247
327
  }
248
328
  get credentials() {
249
- return this.data.credentials ?? [];
329
+ return this._credentials;
250
330
  }
251
331
  }
252
332
  exports.default = Vault;
@@ -2,33 +2,74 @@ import Vault from "./Model/Vault";
2
2
  import type { LoggingHandlerInterface } from "./Interfaces/LoggingHandlerInterface";
3
3
  import { NextcloudServerInfoInterface } from "./Interfaces/NextcloudServer/NextcloudServerInfoInterface";
4
4
  import type { NextcloudServerInterface } from "./Interfaces/NextcloudServer/NextcloudServerInterface";
5
+ import PreloadedVault from "./Model/PreloadedVault";
6
+ import { PersistenceInterface } from "./Interfaces/PersistenceInterface";
7
+ import { RequestCachingHandlerInterface } from "./Interfaces/RequestCachingHandlerInterface";
5
8
  export declare class PassmanClient {
6
9
  readonly server: NextcloudServerInterface;
7
- private logger;
8
- vaults: Vault[];
10
+ private readonly logger;
9
11
  /**
10
- * Create PassmanClient instance.
12
+ * Non-serializable in-memory object cache, useful for long-living instances.
13
+ * todo: may add a constructor option to disable this one to save memory (for short-living instances like webextensions)
14
+ */
15
+ protected readonly _fullFeaturedVaultObjectCache: Vault[];
16
+ /**
17
+ * Array of available vaults, with just enough metadata for a vault listing and to run testVaultKey('...').
18
+ */
19
+ protected _preloadedVaults: PreloadedVault[];
20
+ /**
21
+ * Create PassmanClient instance. Deprecated!
11
22
  * @param serverData
12
23
  * @param nextcloudServer
13
24
  * @param logger
25
+ * @param persistence
14
26
  * @throws ConfigurationError from nextcloud server configuration data
27
+ * @deprecated use PassmanClient.createInstance() instead in external/public libs
28
+ */
29
+ constructor(serverData: NextcloudServerInfoInterface, nextcloudServer?: NextcloudServerInterface, logger?: LoggingHandlerInterface, persistence?: PersistenceInterface);
30
+ /**
31
+ * Create PassmanClient instance.
32
+ * To use "auto restore on reconstruction" provide a custom persistence instance. This will never auto-unlock vaults.
33
+ * @param serverData
34
+ * @param nextcloudServer
35
+ * @param logger
36
+ * @param persistence
15
37
  */
16
- constructor(serverData: NextcloudServerInfoInterface, nextcloudServer?: NextcloudServerInterface, logger?: LoggingHandlerInterface);
38
+ static createInstance(serverData: NextcloudServerInfoInterface, nextcloudServer?: NextcloudServerInterface, logger?: LoggingHandlerInterface, persistence?: PersistenceInterface): Promise<PassmanClient>;
39
+ protected restoreFromCacheHandler(cache: RequestCachingHandlerInterface): Promise<void>;
17
40
  /**
18
- * Refresh local (internal) vaults cache.
41
+ * Preloads vaults or refreshes the preloaded vaults array, if getCachedIfPossible = false.
19
42
  * @param throwError
20
- * @param preserveInMemoryVaultKeys
21
43
  * @param getCachedIfPossible
22
- * @throws Error if throwError = true and the api request fails with an error
23
44
  */
24
- refreshVaults(throwError?: boolean, preserveInMemoryVaultKeys?: boolean, getCachedIfPossible?: boolean): Promise<boolean>;
45
+ preloadVaults(throwError?: boolean, getCachedIfPossible?: boolean): Promise<boolean>;
25
46
  createVault(vaultName: string, vaultPassword: string): Promise<void | Vault>;
47
+ /**
48
+ * @deprecated use getFullVaultByGuid instead
49
+ * @param guid
50
+ * @param getCachedIfPossible
51
+ */
26
52
  getVaultByGuid(guid: string, getCachedIfPossible?: boolean): Promise<Vault>;
27
53
  /**
28
- * Make sure that this.vaults is an array and not undefined, before using this method.
54
+ * Returns full vault from cache, or fetches it from the server (and updates the full-featured vault cache afterward).
55
+ * If a vault key is provided, it tries not only to unlock, also to restore decrypted data if getCachedIfPossible is set.
29
56
  * @param guid
57
+ * @param getCachedIfPossible
58
+ */
59
+ getFullVaultByGuid(guid: string, getCachedIfPossible?: boolean, vaultKey?: string): Promise<Vault>;
60
+ get preloadedVaults(): PreloadedVault[];
61
+ set preloadedVaults(preloadedVaults: PreloadedVault[]);
62
+ /**
63
+ * Returns the full-featured vault instance from the in-memory object cache, if possible.
64
+ * @param guid
65
+ * @private
66
+ */
67
+ protected _getFullFeaturedVaultFromObjectCacheByGuid(guid: string): Vault | void;
68
+ /**
69
+ * Add or update a full-featured vault instance in the in-memory object cache.
70
+ * @param vault
30
71
  * @private
31
72
  */
32
- private _getVaultByGuid;
73
+ protected _updateFullFeaturedVaultInObjectCache(vault: Vault): void;
33
74
  getTranslation(lang?: string): Promise<void | object>;
34
75
  }
@@ -7,48 +7,78 @@ exports.PassmanClient = void 0;
7
7
  const NextcloudServer_1 = require("./Model/NextcloudServer");
8
8
  const Vault_1 = __importDefault(require("./Model/Vault"));
9
9
  const DefaultLoggingService_1 = require("./Service/DefaultLoggingService");
10
+ const PreloadedVault_1 = __importDefault(require("./Model/PreloadedVault"));
11
+ const DefaultPersistenceService_1 = require("./Service/DefaultPersistenceService");
10
12
  class PassmanClient {
11
13
  server;
12
14
  logger;
13
- vaults;
14
15
  /**
15
- * Create PassmanClient instance.
16
+ * Non-serializable in-memory object cache, useful for long-living instances.
17
+ * todo: may add a constructor option to disable this one to save memory (for short-living instances like webextensions)
18
+ */
19
+ _fullFeaturedVaultObjectCache;
20
+ /**
21
+ * Array of available vaults, with just enough metadata for a vault listing and to run testVaultKey('...').
22
+ */
23
+ _preloadedVaults;
24
+ /**
25
+ * Create PassmanClient instance. Deprecated!
16
26
  * @param serverData
17
27
  * @param nextcloudServer
18
28
  * @param logger
29
+ * @param persistence
19
30
  * @throws ConfigurationError from nextcloud server configuration data
31
+ * @deprecated use PassmanClient.createInstance() instead in external/public libs
20
32
  */
21
- constructor(serverData, nextcloudServer, logger) {
33
+ constructor(serverData, nextcloudServer, logger, persistence) {
22
34
  this.logger = logger ?? new DefaultLoggingService_1.DefaultLoggingService();
23
- this.server = nextcloudServer ?? new NextcloudServer_1.NextcloudServer(serverData, this.logger);
35
+ this.server = nextcloudServer ?? new NextcloudServer_1.NextcloudServer(serverData, this.logger, persistence ?? new DefaultPersistenceService_1.DefaultPersistenceService());
36
+ this._fullFeaturedVaultObjectCache = [];
37
+ }
38
+ /**
39
+ * Create PassmanClient instance.
40
+ * To use "auto restore on reconstruction" provide a custom persistence instance. This will never auto-unlock vaults.
41
+ * @param serverData
42
+ * @param nextcloudServer
43
+ * @param logger
44
+ * @param persistence
45
+ */
46
+ static async createInstance(serverData, nextcloudServer, logger, persistence) {
47
+ if (persistence?.autoRestoreOnReconstruction()) {
48
+ let passmanClient = new this(serverData, nextcloudServer ?? new NextcloudServer_1.NextcloudServer(serverData, logger, persistence), logger, persistence);
49
+ await passmanClient.restoreFromCacheHandler(persistence.getRequestCacheHandler());
50
+ return passmanClient;
51
+ }
52
+ else {
53
+ return new this(serverData, nextcloudServer, logger, persistence);
54
+ }
55
+ }
56
+ async restoreFromCacheHandler(cache) {
57
+ await this.preloadVaults(false, true);
58
+ // test for which vaults we have full-feature loading requests cached and hint-load them to be recreated from request cache
59
+ const cachePrefix = 'cache-getJson-';
60
+ for (const preloadedVault of this.preloadedVaults) {
61
+ const cachedValue = await cache.get(cachePrefix + '/vaults/' + preloadedVault.guid);
62
+ if (cachedValue && cachedValue !== '') {
63
+ await this.getFullVaultByGuid(preloadedVault.guid, true);
64
+ }
65
+ }
24
66
  }
25
67
  /**
26
- * Refresh local (internal) vaults cache.
68
+ * Preloads vaults or refreshes the preloaded vaults array, if getCachedIfPossible = false.
27
69
  * @param throwError
28
- * @param preserveInMemoryVaultKeys
29
70
  * @param getCachedIfPossible
30
- * @throws Error if throwError = true and the api request fails with an error
31
71
  */
32
- async refreshVaults(throwError = false, preserveInMemoryVaultKeys = true, getCachedIfPossible = false) {
33
- let newVaults = [];
34
- const vaults = await this.server.getJson('/vaults', (error) => {
72
+ async preloadVaults(throwError = false, getCachedIfPossible = false) {
73
+ const vaultsResponse = await this.server.getJson('/vaults', (error) => {
35
74
  console.error(error);
36
75
  if (throwError) {
37
76
  this.logger.onThrow(error);
38
77
  }
39
78
  }, getCachedIfPossible);
40
- if (vaults) {
41
- vaults.forEach((vaultData) => {
42
- const vault = new Vault_1.default(vaultData, this.server);
43
- if (this.vaults !== undefined && preserveInMemoryVaultKeys) {
44
- const oldVaultInstance = this._getVaultByGuid(vault.guid);
45
- if (oldVaultInstance && oldVaultInstance.vaultKey && vault.testVaultKey(oldVaultInstance.vaultKey)) {
46
- vault.vaultKey = oldVaultInstance.vaultKey;
47
- }
48
- }
49
- newVaults.push(vault);
50
- });
51
- this.vaults = newVaults;
79
+ let newPreloadedVaults = PreloadedVault_1.default.parseResponse(vaultsResponse, this.server);
80
+ if (newPreloadedVaults) {
81
+ this.preloadedVaults = newPreloadedVaults;
52
82
  return true;
53
83
  }
54
84
  return false;
@@ -56,34 +86,70 @@ class PassmanClient {
56
86
  async createVault(vaultName, vaultPassword) {
57
87
  let newVault = await Vault_1.default.create(vaultName, vaultPassword, this.server);
58
88
  if (newVault) {
59
- this.vaults.push(newVault);
89
+ this._updateFullFeaturedVaultInObjectCache(newVault);
60
90
  return newVault;
61
91
  }
62
92
  }
93
+ /**
94
+ * @deprecated use getFullVaultByGuid instead
95
+ * @param guid
96
+ * @param getCachedIfPossible
97
+ */
63
98
  async getVaultByGuid(guid, getCachedIfPossible = false) {
64
- if (this.vaults === undefined) {
65
- if (!await this.refreshVaults(false, true, getCachedIfPossible)) {
66
- this.logger.onError("failed to refresh vaults");
67
- return;
99
+ return this.getFullVaultByGuid(guid, getCachedIfPossible);
100
+ }
101
+ /**
102
+ * Returns full vault from cache, or fetches it from the server (and updates the full-featured vault cache afterward).
103
+ * If a vault key is provided, it tries not only to unlock, also to restore decrypted data if getCachedIfPossible is set.
104
+ * @param guid
105
+ * @param getCachedIfPossible
106
+ */
107
+ async getFullVaultByGuid(guid, getCachedIfPossible = false, vaultKey) {
108
+ if (getCachedIfPossible) {
109
+ const cachedVault = this._getFullFeaturedVaultFromObjectCacheByGuid(guid);
110
+ if (cachedVault) {
111
+ return cachedVault;
68
112
  }
69
113
  }
70
- const foundVault = this._getVaultByGuid(guid);
71
- if (foundVault) {
72
- return foundVault;
114
+ const freshVault = await Vault_1.default.fetchFullVaultFromServer(this.server, guid, vaultKey, getCachedIfPossible);
115
+ if (freshVault) {
116
+ this._updateFullFeaturedVaultInObjectCache(freshVault);
117
+ return freshVault;
73
118
  }
74
119
  this.logger.onError(`vault with guid ${guid} not found`);
75
120
  }
121
+ get preloadedVaults() {
122
+ return this._preloadedVaults ?? [];
123
+ }
124
+ set preloadedVaults(preloadedVaults) {
125
+ this._preloadedVaults = preloadedVaults;
126
+ }
76
127
  /**
77
- * Make sure that this.vaults is an array and not undefined, before using this method.
128
+ * Returns the full-featured vault instance from the in-memory object cache, if possible.
78
129
  * @param guid
79
130
  * @private
80
131
  */
81
- _getVaultByGuid(guid) {
82
- for (const element of this.vaults) {
83
- if (element.guid === guid) {
84
- return element;
132
+ _getFullFeaturedVaultFromObjectCacheByGuid(guid) {
133
+ for (const vault of this._fullFeaturedVaultObjectCache) {
134
+ if (vault.guid === guid) {
135
+ return vault;
136
+ }
137
+ }
138
+ }
139
+ /**
140
+ * Add or update a full-featured vault instance in the in-memory object cache.
141
+ * @param vault
142
+ * @private
143
+ */
144
+ _updateFullFeaturedVaultInObjectCache(vault) {
145
+ for (let i = 0; i < this._fullFeaturedVaultObjectCache.length; i++) {
146
+ if (this._fullFeaturedVaultObjectCache[i].guid === vault.guid) {
147
+ this._fullFeaturedVaultObjectCache[i] = vault;
148
+ return;
85
149
  }
86
150
  }
151
+ // add if vault was not found in the cache loop above
152
+ this._fullFeaturedVaultObjectCache.push(vault);
87
153
  }
88
154
  async getTranslation(lang = 'en') {
89
155
  return await this.server.getJson('/language?lang=' + lang, (response) => {