mirror of
https://github.com/bitwarden/browser
synced 2026-01-02 00:23:35 +00:00
send password history to server
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { CipherType } from '../enums/cipherType';
|
||||
import { FieldType } from '../enums/fieldType';
|
||||
import { UriMatchType } from '../enums/uriMatchType';
|
||||
|
||||
import { CipherData } from '../models/data/cipherData';
|
||||
@@ -12,6 +13,7 @@ import { Field } from '../models/domain/field';
|
||||
import { Identity } from '../models/domain/identity';
|
||||
import { Login } from '../models/domain/login';
|
||||
import { LoginUri } from '../models/domain/loginUri';
|
||||
import { Password } from '../models/domain/password';
|
||||
import { SecureNote } from '../models/domain/secureNote';
|
||||
import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
|
||||
|
||||
@@ -28,6 +30,7 @@ import { ErrorResponse } from '../models/response/errorResponse';
|
||||
import { AttachmentView } from '../models/view/attachmentView';
|
||||
import { CipherView } from '../models/view/cipherView';
|
||||
import { FieldView } from '../models/view/fieldView';
|
||||
import { PasswordHistoryView } from '../models/view/passwordHistoryView';
|
||||
import { View } from '../models/view/view';
|
||||
|
||||
import { ApiService } from '../abstractions/api.service';
|
||||
@@ -61,6 +64,41 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
}
|
||||
|
||||
async encrypt(model: CipherView, key?: SymmetricCryptoKey): Promise<Cipher> {
|
||||
// Adjust password history
|
||||
if (model.id != null) {
|
||||
const existingCipher = await (await this.get(model.id)).decrypt();
|
||||
if (existingCipher != null) {
|
||||
model.passwordHistory = existingCipher.passwordHistory || [];
|
||||
if (model.type === CipherType.Login && existingCipher.type === CipherType.Login &&
|
||||
existingCipher.login.password !== model.login.password) {
|
||||
const ph = new PasswordHistoryView(null);
|
||||
ph.password = existingCipher.login.password;
|
||||
ph.lastUsedDate = new Date();
|
||||
model.passwordHistory.splice(0, 0, ph);
|
||||
}
|
||||
if (existingCipher.hasFields) {
|
||||
const existingHiddenFields = existingCipher.fields.filter((f) => f.type === FieldType.Hidden);
|
||||
const hiddenFields = model.fields == null ? [] :
|
||||
model.fields.filter((f) => f.type === FieldType.Hidden);
|
||||
existingHiddenFields.forEach((ef) => {
|
||||
const matchedField = hiddenFields.filter((f) => f.name === ef.name);
|
||||
if (matchedField.length === 0 || matchedField[0].value !== ef.value) {
|
||||
const ph = new PasswordHistoryView(null);
|
||||
ph.password = ef.name + ': ' + ef.value;
|
||||
ph.lastUsedDate = new Date();
|
||||
model.passwordHistory.splice(0, 0, ph);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (model.passwordHistory != null && model.passwordHistory.length === 0) {
|
||||
model.passwordHistory = null;
|
||||
} else if (model.passwordHistory != null && model.passwordHistory.length > 5) {
|
||||
// only save last 5 history
|
||||
model.passwordHistory = model.passwordHistory.slice(0, 4);
|
||||
}
|
||||
}
|
||||
|
||||
const cipher = new Cipher();
|
||||
cipher.id = model.id;
|
||||
cipher.folderId = model.folderId;
|
||||
@@ -81,6 +119,9 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
this.encryptFields(model.fields, key).then((fields) => {
|
||||
cipher.fields = fields;
|
||||
}),
|
||||
this.encryptPasswordHistories(model.passwordHistory, key).then((ph) => {
|
||||
cipher.passwordHistory = ph;
|
||||
}),
|
||||
this.encryptAttachments(model.attachments, key).then((attachments) => {
|
||||
cipher.attachments = attachments;
|
||||
}),
|
||||
@@ -144,6 +185,35 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
return field;
|
||||
}
|
||||
|
||||
async encryptPasswordHistories(phModels: PasswordHistoryView[], key: SymmetricCryptoKey): Promise<Password[]> {
|
||||
if (!phModels || !phModels.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const encPhs: Password[] = [];
|
||||
await phModels.reduce((promise, ph) => {
|
||||
return promise.then(() => {
|
||||
return self.encryptPasswordHistory(ph, key);
|
||||
}).then((encPh: Password) => {
|
||||
encPhs.push(encPh);
|
||||
});
|
||||
}, Promise.resolve());
|
||||
|
||||
return encPhs;
|
||||
}
|
||||
|
||||
async encryptPasswordHistory(phModel: PasswordHistoryView, key: SymmetricCryptoKey): Promise<Password> {
|
||||
const ph = new Password();
|
||||
ph.lastUsedDate = phModel.lastUsedDate;
|
||||
|
||||
await this.encryptObjProperty(phModel, ph, {
|
||||
password: null,
|
||||
}, key);
|
||||
|
||||
return ph;
|
||||
}
|
||||
|
||||
async get(id: string): Promise<Cipher> {
|
||||
const userId = await this.userService.getUserId();
|
||||
const localData = await this.storageService.get<any>(Keys.localData);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CipherString } from '../models/domain/cipherString';
|
||||
import { PasswordHistory } from '../models/domain/passwordHistory';
|
||||
import { GeneratedPasswordHistory } from '../models/domain/generatedPasswordHistory';
|
||||
|
||||
import { CryptoService } from '../abstractions/crypto.service';
|
||||
import {
|
||||
@@ -29,7 +29,7 @@ const MaxPasswordsInHistory = 100;
|
||||
|
||||
export class PasswordGenerationService implements PasswordGenerationServiceAbstraction {
|
||||
private optionsCache: any;
|
||||
private history: PasswordHistory[];
|
||||
private history: GeneratedPasswordHistory[];
|
||||
|
||||
constructor(private cryptoService: CryptoService, private storageService: StorageService) { }
|
||||
|
||||
@@ -168,18 +168,18 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
|
||||
this.optionsCache = options;
|
||||
}
|
||||
|
||||
async getHistory(): Promise<PasswordHistory[]> {
|
||||
async getHistory(): Promise<GeneratedPasswordHistory[]> {
|
||||
const hasKey = await this.cryptoService.hasKey();
|
||||
if (!hasKey) {
|
||||
return new Array<PasswordHistory>();
|
||||
return new Array<GeneratedPasswordHistory>();
|
||||
}
|
||||
|
||||
if (!this.history) {
|
||||
const encrypted = await this.storageService.get<PasswordHistory[]>(Keys.history);
|
||||
const encrypted = await this.storageService.get<GeneratedPasswordHistory[]>(Keys.history);
|
||||
this.history = await this.decryptHistory(encrypted);
|
||||
}
|
||||
|
||||
return this.history || new Array<PasswordHistory>();
|
||||
return this.history || new Array<GeneratedPasswordHistory>();
|
||||
}
|
||||
|
||||
async addHistory(password: string): Promise<any> {
|
||||
@@ -196,7 +196,7 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
|
||||
return;
|
||||
}
|
||||
|
||||
currentHistory.unshift(new PasswordHistory(password, Date.now()));
|
||||
currentHistory.unshift(new GeneratedPasswordHistory(password, Date.now()));
|
||||
|
||||
// Remove old items.
|
||||
if (currentHistory.length > MaxPasswordsInHistory) {
|
||||
@@ -212,33 +212,33 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
|
||||
return await this.storageService.remove(Keys.history);
|
||||
}
|
||||
|
||||
private async encryptHistory(history: PasswordHistory[]): Promise<PasswordHistory[]> {
|
||||
private async encryptHistory(history: GeneratedPasswordHistory[]): Promise<GeneratedPasswordHistory[]> {
|
||||
if (history == null || history.length === 0) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
const promises = history.map(async (item) => {
|
||||
const encrypted = await this.cryptoService.encrypt(item.password);
|
||||
return new PasswordHistory(encrypted.encryptedString, item.date);
|
||||
return new GeneratedPasswordHistory(encrypted.encryptedString, item.date);
|
||||
});
|
||||
|
||||
return await Promise.all(promises);
|
||||
}
|
||||
|
||||
private async decryptHistory(history: PasswordHistory[]): Promise<PasswordHistory[]> {
|
||||
private async decryptHistory(history: GeneratedPasswordHistory[]): Promise<GeneratedPasswordHistory[]> {
|
||||
if (history == null || history.length === 0) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
const promises = history.map(async (item) => {
|
||||
const decrypted = await this.cryptoService.decryptToUtf8(new CipherString(item.password));
|
||||
return new PasswordHistory(decrypted, item.date);
|
||||
return new GeneratedPasswordHistory(decrypted, item.date);
|
||||
});
|
||||
|
||||
return await Promise.all(promises);
|
||||
}
|
||||
|
||||
private matchesPrevious(password: string, history: PasswordHistory[]): boolean {
|
||||
private matchesPrevious(password: string, history: GeneratedPasswordHistory[]): boolean {
|
||||
if (history == null || history.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user