Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | import { NextcloudServer } from "./NextcloudServer";
import Vault from "./Vault";
import { DefaultLoggingService } from "../Service/DefaultLoggingService";
import PreloadedVault from "./PreloadedVault";
import { DefaultPersistenceService } from "../Service/DefaultPersistenceService";
/**
* Represents a single Passman/Nextcloud server connection and owns all per-server state
* (the underlying server, the preloaded vault list and the in-memory full-featured vault cache).
*
* A {@link PassmanClient} manages one or more of these connections. Each connection is identified
* by a stable {@link connectionId} derived from baseUrl + user + backendAppId.
*/
export class PassmanServerConnection {
server;
/**
* Stable identifier derived from baseUrl + user + backendAppId. Used as the key in the PassmanClient connection map.
*/
connectionId;
logger;
/**
* Non-serializable in-memory object cache, useful for long-living instances.
* todo: may add a constructor option to disable this one to save memory (for short-living instances like webextensions)
*/
_fullFeaturedVaultObjectCache;
/**
* Array of available vaults, with just enough metadata for a vault listing and to run testVaultKey('...').
*/
_preloadedVaults;
/**
* Create a PassmanServerConnection instance.
* @param serverData
* @param nextcloudServer
* @param logger
* @param persistence
* @throws ConfigurationError from nextcloud server configuration data
*/
constructor(serverData, nextcloudServer, logger, persistence) {
this.logger = logger ?? new DefaultLoggingService();
this.server = nextcloudServer ?? new NextcloudServer(serverData, this.logger, persistence ?? new DefaultPersistenceService());
this._fullFeaturedVaultObjectCache = [];
this.connectionId = PassmanServerConnection.buildConnectionId(serverData);
}
/**
* Build a stable connection identifier from the server connection data.
* @param serverData
*/
static buildConnectionId(serverData) {
return `${serverData.baseUrl}|${serverData.user}|${serverData.backendAppId ?? 'passman'}`;
}
/**
* Restore preloaded vaults and full vault objects on client reconstruction.
* Prefers the model store (primary read/reconstruction path); falls back to the deprecated request-cache reconstruction.
*/
async restore() {
const modelStore = this.server.persistence.getModelStore();
if (modelStore) {
await this._restoreFromModelStore(modelStore);
return;
}
const requestCacheHandler = this.server.persistence.getRequestCacheHandler();
if (requestCacheHandler) {
await this.restoreFromCacheHandler(requestCacheHandler);
}
}
/**
* Rebuild preloaded vaults and full vault objects purely from the model store (offline, via fromSerializable()).
* @param modelStore
* @private
*/
async _restoreFromModelStore(modelStore) {
const vaultList = await modelStore.getVaultList(this.connectionId);
if (vaultList) {
const preloaded = PreloadedVault.parseResponse(vaultList, this.server);
if (preloaded) {
this.preloadedVaults = preloaded;
}
}
for (const preloadedVault of this.preloadedVaults) {
const restoredVault = await this._restoreFullVaultFromModelStore(preloadedVault.guid);
if (restoredVault) {
this._updateFullFeaturedVaultInObjectCache(restoredVault);
}
}
}
/**
* Rebuild a single locked full vault from the model store via Vault.fromSerializable(), or void on a store miss.
* If a valid vaultKey is provided, it unlocks the vault and restores its serialized decrypted data cache.
* @param guid
* @param vaultKey
* @private
*/
async _restoreFullVaultFromModelStore(guid, vaultKey) {
const modelStore = this.server.persistence.getModelStore();
if (!modelStore) {
return;
}
const serializableVault = await modelStore.getVault(this.connectionId, guid);
if (!serializableVault) {
return;
}
const vault = Vault.fromSerializable(serializableVault, this.server);
if (vaultKey && vault.testVaultKey(vaultKey)) {
vault.vaultKey = vaultKey;
for (const credential of vault.credentials) {
await credential.restoreSerializedDecryptedDataCache();
}
}
return vault;
}
/**
* @deprecated reconstruction from the raw-GET request cache is superseded by the model store. Use {@link restore} instead.
* @param cache
*/
async restoreFromCacheHandler(cache) {
this.logger.onWarning('restoreFromCacheHandler() is deprecated; use restoreFromModelStore() instead. (from ' + this.connectionId + ')');
await this.preloadVaults(false, true);
// test for which vaults we have full-feature loading requests cached and hint-load them to be recreated from request cache
const cachePrefix = 'cache-getJson-';
for (const preloadedVault of this.preloadedVaults) {
const cachedValue = await cache.get(cachePrefix + '/vaults/' + preloadedVault.guid);
if (cachedValue && cachedValue !== '') {
await this.getFullVaultByGuid(preloadedVault.guid, true);
}
}
}
/**
* Preloads vaults or refreshes the preloaded vaults array, if getCachedIfPossible = false.
* @param throwError
* @param getCachedIfPossible
*/
async preloadVaults(throwError = false, getCachedIfPossible = false) {
const vaultsResponse = await this.server.getJson('/vaults', (error) => {
console.error(error);
if (throwError) {
this.logger.onThrow(error);
}
}, getCachedIfPossible);
let newPreloadedVaults = PreloadedVault.parseResponse(vaultsResponse, this.server);
if (newPreloadedVaults) {
this.preloadedVaults = newPreloadedVaults;
if (vaultsResponse) {
// keep the model store vault listing (primary read/reconstruction path) in sync
await this.server.persistence.getModelStore()?.putVaultList(this.connectionId, vaultsResponse);
}
return true;
}
return false;
}
async createVault(vaultName, vaultPassword) {
let newVault = await Vault.create(vaultName, vaultPassword, this.server);
if (newVault) {
this._updateFullFeaturedVaultInObjectCache(newVault);
return newVault;
}
}
/**
* @deprecated use getFullVaultByGuid instead
* @param guid
* @param getCachedIfPossible
*/
async getVaultByGuid(guid, getCachedIfPossible = false) {
return this.getFullVaultByGuid(guid, getCachedIfPossible);
}
/**
* Returns full vault from cache, or fetches it from the server (and updates the full-featured vault cache afterward).
* If a vault key is provided, it tries not only to unlock, also to restore decrypted data if getCachedIfPossible is set.
* @param guid
* @param getCachedIfPossible
*/
async getFullVaultByGuid(guid, getCachedIfPossible = false, vaultKey) {
if (getCachedIfPossible) {
const cachedVault = this._getFullFeaturedVaultFromObjectCacheByGuid(guid);
if (cachedVault) {
return cachedVault;
}
// model store is the primary cached read path (replaces the legacy request-cache reconstruction)
const restoredVault = await this._restoreFullVaultFromModelStore(guid, vaultKey);
if (restoredVault) {
this._updateFullFeaturedVaultInObjectCache(restoredVault);
return restoredVault;
}
}
const freshVault = await Vault.fetchFullVaultFromServer(this.server, guid, vaultKey, getCachedIfPossible);
if (freshVault) {
this._updateFullFeaturedVaultInObjectCache(freshVault);
return freshVault;
}
this.logger.onError(`vault with guid ${guid} not found`);
}
get preloadedVaults() {
return this._preloadedVaults ?? [];
}
set preloadedVaults(preloadedVaults) {
this._preloadedVaults = preloadedVaults;
}
/**
* Returns the full-featured vault instance from the in-memory object cache, if possible.
* @param guid
* @private
*/
_getFullFeaturedVaultFromObjectCacheByGuid(guid) {
for (const vault of this._fullFeaturedVaultObjectCache) {
if (vault.guid === guid) {
return vault;
}
}
}
/**
* Add or update a full-featured vault instance in the in-memory object cache.
* @param vault
* @private
*/
_updateFullFeaturedVaultInObjectCache(vault) {
for (let i = 0; i < this._fullFeaturedVaultObjectCache.length; i++) {
if (this._fullFeaturedVaultObjectCache[i].guid === vault.guid) {
this._fullFeaturedVaultObjectCache[i] = vault;
return;
}
}
// add if vault was not found in the cache loop above
this._fullFeaturedVaultObjectCache.push(vault);
}
async getTranslation(lang = 'en') {
return await this.server.getJson('/language?lang=' + lang, (response) => {
this.logger.onError(response.message);
});
}
}
|