mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-09 09:51:02 +01:00
Add new method for cycling through every login (#142)
* Add new method for cycling through every login To be used from browser extension when autofilling. Related PR: https://github.com/bitwarden/browser/pull/956 * Cache sorted ciphers by URL and invalidate them after a period of 5 seconds * Move file to models
This commit is contained in:
parent
e516692559
commit
5c62938dbb
@ -24,6 +24,7 @@ export abstract class CipherService {
|
|||||||
getAllDecryptedForUrl: (url: string, includeOtherTypes?: CipherType[]) => Promise<CipherView[]>;
|
getAllDecryptedForUrl: (url: string, includeOtherTypes?: CipherType[]) => Promise<CipherView[]>;
|
||||||
getAllFromApiForOrganization: (organizationId: string) => Promise<CipherView[]>;
|
getAllFromApiForOrganization: (organizationId: string) => Promise<CipherView[]>;
|
||||||
getLastUsedForUrl: (url: string) => Promise<CipherView>;
|
getLastUsedForUrl: (url: string) => Promise<CipherView>;
|
||||||
|
getNextCipherForUrl: (url: string) => Promise<CipherView>;
|
||||||
updateLastUsedDate: (id: string) => Promise<void>;
|
updateLastUsedDate: (id: string) => Promise<void>;
|
||||||
saveNeverDomain: (domain: string) => Promise<void>;
|
saveNeverDomain: (domain: string) => Promise<void>;
|
||||||
saveWithServer: (cipher: Cipher) => Promise<any>;
|
saveWithServer: (cipher: Cipher) => Promise<any>;
|
||||||
|
60
src/models/domain/sortedCiphersCache.ts
Normal file
60
src/models/domain/sortedCiphersCache.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { CipherView } from '../view';
|
||||||
|
|
||||||
|
const CacheTTL = 5000;
|
||||||
|
|
||||||
|
export class SortedCiphersCache {
|
||||||
|
private readonly sortedCiphersByUrl: Map<string, Ciphers> = new Map<string, Ciphers>();
|
||||||
|
private readonly timeouts: Map<string, any> = new Map<string, any>();
|
||||||
|
|
||||||
|
constructor(private readonly comparator: (a: CipherView, b: CipherView) => number) { }
|
||||||
|
|
||||||
|
isCached(url: string) {
|
||||||
|
return this.sortedCiphersByUrl.has(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
addCiphers(url: string, ciphers: CipherView[]) {
|
||||||
|
ciphers.sort(this.comparator);
|
||||||
|
this.sortedCiphersByUrl.set(url, new Ciphers(ciphers));
|
||||||
|
this.resetTimer(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLastUsed(url: string) {
|
||||||
|
this.resetTimer(url);
|
||||||
|
return this.isCached(url) ? this.sortedCiphersByUrl.get(url).getLastUsed() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNext(url: string) {
|
||||||
|
this.resetTimer(url);
|
||||||
|
return this.isCached(url) ? this.sortedCiphersByUrl.get(url).getNext() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.sortedCiphersByUrl.clear();
|
||||||
|
this.timeouts.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private resetTimer(url: string) {
|
||||||
|
clearTimeout(this.timeouts.get(url));
|
||||||
|
this.timeouts.set(url, setTimeout(() => {
|
||||||
|
this.sortedCiphersByUrl.delete(url);
|
||||||
|
this.timeouts.delete(url);
|
||||||
|
}, CacheTTL));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Ciphers {
|
||||||
|
lastUsedIndex = -1;
|
||||||
|
|
||||||
|
constructor(private readonly ciphers: CipherView[]) { }
|
||||||
|
|
||||||
|
getLastUsed() {
|
||||||
|
this.lastUsedIndex = Math.max(this.lastUsedIndex, 0);
|
||||||
|
return this.ciphers[this.lastUsedIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
getNext() {
|
||||||
|
const nextIndex = (this.lastUsedIndex + 1) % this.ciphers.length;
|
||||||
|
this.lastUsedIndex = nextIndex;
|
||||||
|
return this.ciphers[nextIndex];
|
||||||
|
}
|
||||||
|
}
|
@ -35,6 +35,8 @@ import { FieldView } from '../models/view/fieldView';
|
|||||||
import { PasswordHistoryView } from '../models/view/passwordHistoryView';
|
import { PasswordHistoryView } from '../models/view/passwordHistoryView';
|
||||||
import { View } from '../models/view/view';
|
import { View } from '../models/view/view';
|
||||||
|
|
||||||
|
import { SortedCiphersCache } from '../models/domain/sortedCiphersCache';
|
||||||
|
|
||||||
import { ApiService } from '../abstractions/api.service';
|
import { ApiService } from '../abstractions/api.service';
|
||||||
import { CipherService as CipherServiceAbstraction } from '../abstractions/cipher.service';
|
import { CipherService as CipherServiceAbstraction } from '../abstractions/cipher.service';
|
||||||
import { CryptoService } from '../abstractions/crypto.service';
|
import { CryptoService } from '../abstractions/crypto.service';
|
||||||
@ -63,6 +65,8 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
_decryptedCipherCache: CipherView[];
|
_decryptedCipherCache: CipherView[];
|
||||||
|
|
||||||
|
private sortedCiphersCache: SortedCiphersCache = new SortedCiphersCache(this.sortCiphersByLastUsed);
|
||||||
|
|
||||||
constructor(private cryptoService: CryptoService, private userService: UserService,
|
constructor(private cryptoService: CryptoService, private userService: UserService,
|
||||||
private settingsService: SettingsService, private apiService: ApiService,
|
private settingsService: SettingsService, private apiService: ApiService,
|
||||||
private storageService: StorageService, private i18nService: I18nService,
|
private storageService: StorageService, private i18nService: I18nService,
|
||||||
@ -85,6 +89,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
|
|
||||||
clearCache(): void {
|
clearCache(): void {
|
||||||
this.decryptedCipherCache = null;
|
this.decryptedCipherCache = null;
|
||||||
|
this.sortedCiphersCache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
async encrypt(model: CipherView, key?: SymmetricCryptoKey, originalCipher: Cipher = null): Promise<Cipher> {
|
async encrypt(model: CipherView, key?: SymmetricCryptoKey, originalCipher: Cipher = null): Promise<Cipher> {
|
||||||
@ -437,13 +442,11 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getLastUsedForUrl(url: string): Promise<CipherView> {
|
async getLastUsedForUrl(url: string): Promise<CipherView> {
|
||||||
const ciphers = await this.getAllDecryptedForUrl(url);
|
return this.getCipherForUrl(url, true);
|
||||||
if (ciphers.length === 0) {
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sortedCiphers = ciphers.sort(this.sortCiphersByLastUsed);
|
async getNextCipherForUrl(url: string): Promise<CipherView> {
|
||||||
return sortedCiphers[0];
|
return this.getCipherForUrl(url, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateLastUsedDate(id: string): Promise<void> {
|
async updateLastUsedDate(id: string): Promise<void> {
|
||||||
@ -1002,4 +1005,16 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
throw new Error('Unknown cipher type.');
|
throw new Error('Unknown cipher type.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getCipherForUrl(url: string, lastUsed: boolean): Promise<CipherView> {
|
||||||
|
if (!this.sortedCiphersCache.isCached(url)) {
|
||||||
|
const ciphers = await this.getAllDecryptedForUrl(url);
|
||||||
|
if (!ciphers) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
this.sortedCiphersCache.addCiphers(url, ciphers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastUsed ? this.sortedCiphersCache.getLastUsed(url) : this.sortedCiphersCache.getNext(url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user