mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 16:23:44 +00:00
send password history to server
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
import { PasswordHistory } from '../models/domain/passwordHistory';
|
import { GeneratedPasswordHistory } from '../models/domain/generatedPasswordHistory';
|
||||||
|
|
||||||
export abstract class PasswordGenerationService {
|
export abstract class PasswordGenerationService {
|
||||||
generatePassword: (options: any) => Promise<string>;
|
generatePassword: (options: any) => Promise<string>;
|
||||||
getOptions: () => any;
|
getOptions: () => any;
|
||||||
saveOptions: (options: any) => Promise<any>;
|
saveOptions: (options: any) => Promise<any>;
|
||||||
getHistory: () => Promise<PasswordHistory[]>;
|
getHistory: () => Promise<GeneratedPasswordHistory[]>;
|
||||||
addHistory: (password: string) => Promise<any>;
|
addHistory: (password: string) => Promise<any>;
|
||||||
clear: () => Promise<any>;
|
clear: () => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import { I18nService } from '../../abstractions/i18n.service';
|
|||||||
import { PasswordGenerationService } from '../../abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from '../../abstractions/passwordGeneration.service';
|
||||||
import { PlatformUtilsService } from '../../abstractions/platformUtils.service';
|
import { PlatformUtilsService } from '../../abstractions/platformUtils.service';
|
||||||
|
|
||||||
import { PasswordHistory } from '../../models/domain/passwordHistory';
|
import { GeneratedPasswordHistory } from '../../models/domain/generatedPasswordHistory';
|
||||||
|
|
||||||
export class PasswordGeneratorHistoryComponent implements OnInit {
|
export class PasswordGeneratorHistoryComponent implements OnInit {
|
||||||
history: PasswordHistory[] = [];
|
history: GeneratedPasswordHistory[] = [];
|
||||||
|
|
||||||
constructor(protected passwordGenerationService: PasswordGenerationService, protected analytics: Angulartics2,
|
constructor(protected passwordGenerationService: PasswordGenerationService, protected analytics: Angulartics2,
|
||||||
protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService,
|
protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { CardData } from './cardData';
|
|||||||
import { FieldData } from './fieldData';
|
import { FieldData } from './fieldData';
|
||||||
import { IdentityData } from './identityData';
|
import { IdentityData } from './identityData';
|
||||||
import { LoginData } from './loginData';
|
import { LoginData } from './loginData';
|
||||||
|
import { PasswordHistoryData } from './passwordHistoryData';
|
||||||
import { SecureNoteData } from './secureNoteData';
|
import { SecureNoteData } from './secureNoteData';
|
||||||
|
|
||||||
import { CipherResponse } from '../response/cipherResponse';
|
import { CipherResponse } from '../response/cipherResponse';
|
||||||
@@ -28,6 +29,7 @@ export class CipherData {
|
|||||||
identity?: IdentityData;
|
identity?: IdentityData;
|
||||||
fields?: FieldData[];
|
fields?: FieldData[];
|
||||||
attachments?: AttachmentData[];
|
attachments?: AttachmentData[];
|
||||||
|
passwordHistory?: PasswordHistoryData[];
|
||||||
collectionIds?: string[];
|
collectionIds?: string[];
|
||||||
|
|
||||||
constructor(response?: CipherResponse, userId?: string, collectionIds?: string[]) {
|
constructor(response?: CipherResponse, userId?: string, collectionIds?: string[]) {
|
||||||
@@ -83,5 +85,12 @@ export class CipherData {
|
|||||||
this.attachments.push(new AttachmentData(attachment));
|
this.attachments.push(new AttachmentData(attachment));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response.passwordHistory != null) {
|
||||||
|
this.passwordHistory = [];
|
||||||
|
response.passwordHistory.forEach((ph) => {
|
||||||
|
this.passwordHistory.push(new PasswordHistoryData(ph));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/models/data/passwordHistoryData.ts
Normal file
15
src/models/data/passwordHistoryData.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { PasswordHistoryResponse } from '../response/passwordHistoryResponse';
|
||||||
|
|
||||||
|
export class PasswordHistoryData {
|
||||||
|
password: string;
|
||||||
|
lastUsedDate: Date;
|
||||||
|
|
||||||
|
constructor(response?: PasswordHistoryResponse) {
|
||||||
|
if (response == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.password = response.password;
|
||||||
|
this.lastUsedDate = response.lastUsedDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import Domain from './domain';
|
|||||||
import { Field } from './field';
|
import { Field } from './field';
|
||||||
import { Identity } from './identity';
|
import { Identity } from './identity';
|
||||||
import { Login } from './login';
|
import { Login } from './login';
|
||||||
|
import { Password } from './password';
|
||||||
import { SecureNote } from './secureNote';
|
import { SecureNote } from './secureNote';
|
||||||
|
|
||||||
export class Cipher extends Domain {
|
export class Cipher extends Domain {
|
||||||
@@ -31,6 +32,7 @@ export class Cipher extends Domain {
|
|||||||
secureNote: SecureNote;
|
secureNote: SecureNote;
|
||||||
attachments: Attachment[];
|
attachments: Attachment[];
|
||||||
fields: Field[];
|
fields: Field[];
|
||||||
|
passwordHistory: Password[];
|
||||||
collectionIds: string[];
|
collectionIds: string[];
|
||||||
|
|
||||||
constructor(obj?: CipherData, alreadyEncrypted: boolean = false, localData: any = null) {
|
constructor(obj?: CipherData, alreadyEncrypted: boolean = false, localData: any = null) {
|
||||||
@@ -90,6 +92,15 @@ export class Cipher extends Domain {
|
|||||||
} else {
|
} else {
|
||||||
this.fields = null;
|
this.fields = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (obj.passwordHistory != null) {
|
||||||
|
this.passwordHistory = [];
|
||||||
|
obj.passwordHistory.forEach((ph) => {
|
||||||
|
this.passwordHistory.push(new Password(ph, alreadyEncrypted));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.passwordHistory = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async decrypt(): Promise<CipherView> {
|
async decrypt(): Promise<CipherView> {
|
||||||
@@ -143,6 +154,18 @@ export class Cipher extends Domain {
|
|||||||
model.fields = fields;
|
model.fields = fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.passwordHistory != null && this.passwordHistory.length > 0) {
|
||||||
|
const passwordHistory: any[] = [];
|
||||||
|
await this.passwordHistory.reduce((promise, ph) => {
|
||||||
|
return promise.then(() => {
|
||||||
|
return ph.decrypt(orgId);
|
||||||
|
}).then((decPh) => {
|
||||||
|
passwordHistory.push(decPh);
|
||||||
|
});
|
||||||
|
}, Promise.resolve());
|
||||||
|
model.passwordHistory = passwordHistory;
|
||||||
|
}
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,6 +217,13 @@ export class Cipher extends Domain {
|
|||||||
c.attachments.push(attachment.toAttachmentData());
|
c.attachments.push(attachment.toAttachmentData());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.passwordHistory != null) {
|
||||||
|
c.passwordHistory = [];
|
||||||
|
this.passwordHistory.forEach((ph) => {
|
||||||
|
c.passwordHistory.push(ph.toPasswordHistoryData());
|
||||||
|
});
|
||||||
|
}
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export class PasswordHistory {
|
export class GeneratedPasswordHistory {
|
||||||
password: string;
|
password: string;
|
||||||
date: number;
|
date: number;
|
||||||
|
|
||||||
@@ -11,6 +11,6 @@ export { Folder } from './folder';
|
|||||||
export { Identity } from './identity';
|
export { Identity } from './identity';
|
||||||
export { Login } from './login';
|
export { Login } from './login';
|
||||||
export { LoginUri } from './loginUri';
|
export { LoginUri } from './loginUri';
|
||||||
export { PasswordHistory } from './passwordHistory';
|
export { GeneratedPasswordHistory } from './generatedPasswordHistory';
|
||||||
export { SecureNote } from './secureNote';
|
export { SecureNote } from './secureNote';
|
||||||
export { SymmetricCryptoKey } from './symmetricCryptoKey';
|
export { SymmetricCryptoKey } from './symmetricCryptoKey';
|
||||||
|
|||||||
39
src/models/domain/password.ts
Normal file
39
src/models/domain/password.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { PasswordHistoryData } from '../data/passwordHistoryData';
|
||||||
|
|
||||||
|
import { CipherString } from './cipherString';
|
||||||
|
import Domain from './domain';
|
||||||
|
|
||||||
|
import { PasswordHistoryView } from '../view/passwordHistoryView';
|
||||||
|
|
||||||
|
export class Password extends Domain {
|
||||||
|
password: CipherString;
|
||||||
|
lastUsedDate: Date;
|
||||||
|
|
||||||
|
constructor(obj?: PasswordHistoryData, alreadyEncrypted: boolean = false) {
|
||||||
|
super();
|
||||||
|
if (obj == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.buildDomainModel(this, obj, {
|
||||||
|
password: null,
|
||||||
|
lastUsedDate: null,
|
||||||
|
}, alreadyEncrypted, ['lastUsedDate']);
|
||||||
|
}
|
||||||
|
|
||||||
|
async decrypt(orgId: string): Promise<PasswordHistoryView> {
|
||||||
|
const view = await this.decryptObj(new PasswordHistoryView(this), {
|
||||||
|
password: null,
|
||||||
|
}, orgId);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
toPasswordHistoryData(): PasswordHistoryData {
|
||||||
|
const ph = new PasswordHistoryData();
|
||||||
|
ph.lastUsedDate = this.lastUsedDate;
|
||||||
|
this.buildDataModel(this, ph, {
|
||||||
|
password: null,
|
||||||
|
});
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ import { IdentityApi } from '../api/identityApi';
|
|||||||
import { LoginApi } from '../api/loginApi';
|
import { LoginApi } from '../api/loginApi';
|
||||||
import { SecureNoteApi } from '../api/secureNoteApi';
|
import { SecureNoteApi } from '../api/secureNoteApi';
|
||||||
|
|
||||||
|
import { PasswordHistoryRequest } from './passwordHistoryRequest';
|
||||||
|
|
||||||
export class CipherRequest {
|
export class CipherRequest {
|
||||||
type: CipherType;
|
type: CipherType;
|
||||||
folderId: string;
|
folderId: string;
|
||||||
@@ -20,6 +22,7 @@ export class CipherRequest {
|
|||||||
card: CardApi;
|
card: CardApi;
|
||||||
identity: IdentityApi;
|
identity: IdentityApi;
|
||||||
fields: FieldApi[];
|
fields: FieldApi[];
|
||||||
|
passwordHistory: PasswordHistoryRequest[];
|
||||||
attachments: { [id: string]: string; };
|
attachments: { [id: string]: string; };
|
||||||
|
|
||||||
constructor(cipher: Cipher) {
|
constructor(cipher: Cipher) {
|
||||||
@@ -102,6 +105,16 @@ export class CipherRequest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cipher.passwordHistory) {
|
||||||
|
this.passwordHistory = [];
|
||||||
|
cipher.passwordHistory.forEach((ph) => {
|
||||||
|
this.passwordHistory.push({
|
||||||
|
lastUsedDate: ph.lastUsedDate,
|
||||||
|
password: ph.password ? ph.password.encryptedString : null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (cipher.attachments) {
|
if (cipher.attachments) {
|
||||||
this.attachments = {};
|
this.attachments = {};
|
||||||
cipher.attachments.forEach((attachment) => {
|
cipher.attachments.forEach((attachment) => {
|
||||||
|
|||||||
9
src/models/request/passwordHistoryRequest.ts
Normal file
9
src/models/request/passwordHistoryRequest.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export class PasswordHistoryRequest {
|
||||||
|
password: string;
|
||||||
|
lastUsedDate: Date;
|
||||||
|
|
||||||
|
constructor(response: any) {
|
||||||
|
this.password = response.Password;
|
||||||
|
this.lastUsedDate = response.LastUsedDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { AttachmentResponse } from './attachmentResponse';
|
import { AttachmentResponse } from './attachmentResponse';
|
||||||
|
import { PasswordHistoryResponse } from './passwordHistoryResponse';
|
||||||
|
|
||||||
import { CardApi } from '../api/cardApi';
|
import { CardApi } from '../api/cardApi';
|
||||||
import { FieldApi } from '../api/fieldApi';
|
import { FieldApi } from '../api/fieldApi';
|
||||||
@@ -23,6 +24,7 @@ export class CipherResponse {
|
|||||||
organizationUseTotp: boolean;
|
organizationUseTotp: boolean;
|
||||||
revisionDate: Date;
|
revisionDate: Date;
|
||||||
attachments: AttachmentResponse[];
|
attachments: AttachmentResponse[];
|
||||||
|
passwordHistory: PasswordHistoryResponse[];
|
||||||
collectionIds: string[];
|
collectionIds: string[];
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
@@ -67,6 +69,13 @@ export class CipherResponse {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response.PasswordHistory != null) {
|
||||||
|
this.passwordHistory = [];
|
||||||
|
response.PasswordHistory.forEach((ph: any) => {
|
||||||
|
this.passwordHistory.push(new PasswordHistoryResponse(ph));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (response.CollectionIds) {
|
if (response.CollectionIds) {
|
||||||
this.collectionIds = [];
|
this.collectionIds = [];
|
||||||
response.CollectionIds.forEach((id: string) => {
|
response.CollectionIds.forEach((id: string) => {
|
||||||
|
|||||||
9
src/models/response/passwordHistoryResponse.ts
Normal file
9
src/models/response/passwordHistoryResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export class PasswordHistoryResponse {
|
||||||
|
password: string;
|
||||||
|
lastUsedDate: Date;
|
||||||
|
|
||||||
|
constructor(response: any) {
|
||||||
|
this.password = response.Password;
|
||||||
|
this.lastUsedDate = response.LastUsedDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import { CardView } from './cardView';
|
|||||||
import { FieldView } from './fieldView';
|
import { FieldView } from './fieldView';
|
||||||
import { IdentityView } from './identityView';
|
import { IdentityView } from './identityView';
|
||||||
import { LoginView } from './loginView';
|
import { LoginView } from './loginView';
|
||||||
|
import { PasswordHistoryView } from './passwordHistoryView';
|
||||||
import { SecureNoteView } from './secureNoteView';
|
import { SecureNoteView } from './secureNoteView';
|
||||||
import { View } from './view';
|
import { View } from './view';
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ export class CipherView implements View {
|
|||||||
secureNote: SecureNoteView;
|
secureNote: SecureNoteView;
|
||||||
attachments: AttachmentView[];
|
attachments: AttachmentView[];
|
||||||
fields: FieldView[];
|
fields: FieldView[];
|
||||||
|
passwordHistory: PasswordHistoryView[];
|
||||||
collectionIds: string[];
|
collectionIds: string[];
|
||||||
|
|
||||||
constructor(c?: Cipher) {
|
constructor(c?: Cipher) {
|
||||||
@@ -62,6 +64,10 @@ export class CipherView implements View {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hasPasswordHistory(): boolean {
|
||||||
|
return this.passwordHistory && this.passwordHistory.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
get hasAttachments(): boolean {
|
get hasAttachments(): boolean {
|
||||||
return this.attachments && this.attachments.length > 0;
|
return this.attachments && this.attachments.length > 0;
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/models/view/passwordHistoryView.ts
Normal file
16
src/models/view/passwordHistoryView.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { View } from './view';
|
||||||
|
|
||||||
|
import { Password } from '../domain/password';
|
||||||
|
|
||||||
|
export class PasswordHistoryView implements View {
|
||||||
|
password: string;
|
||||||
|
lastUsedDate: Date;
|
||||||
|
|
||||||
|
constructor(ph?: Password) {
|
||||||
|
if (!ph) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastUsedDate = ph.lastUsedDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { CipherType } from '../enums/cipherType';
|
import { CipherType } from '../enums/cipherType';
|
||||||
|
import { FieldType } from '../enums/fieldType';
|
||||||
import { UriMatchType } from '../enums/uriMatchType';
|
import { UriMatchType } from '../enums/uriMatchType';
|
||||||
|
|
||||||
import { CipherData } from '../models/data/cipherData';
|
import { CipherData } from '../models/data/cipherData';
|
||||||
@@ -12,6 +13,7 @@ import { Field } from '../models/domain/field';
|
|||||||
import { Identity } from '../models/domain/identity';
|
import { Identity } from '../models/domain/identity';
|
||||||
import { Login } from '../models/domain/login';
|
import { Login } from '../models/domain/login';
|
||||||
import { LoginUri } from '../models/domain/loginUri';
|
import { LoginUri } from '../models/domain/loginUri';
|
||||||
|
import { Password } from '../models/domain/password';
|
||||||
import { SecureNote } from '../models/domain/secureNote';
|
import { SecureNote } from '../models/domain/secureNote';
|
||||||
import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
|
import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
|
||||||
|
|
||||||
@@ -28,6 +30,7 @@ import { ErrorResponse } from '../models/response/errorResponse';
|
|||||||
import { AttachmentView } from '../models/view/attachmentView';
|
import { AttachmentView } from '../models/view/attachmentView';
|
||||||
import { CipherView } from '../models/view/cipherView';
|
import { CipherView } from '../models/view/cipherView';
|
||||||
import { FieldView } from '../models/view/fieldView';
|
import { FieldView } from '../models/view/fieldView';
|
||||||
|
import { PasswordHistoryView } from '../models/view/passwordHistoryView';
|
||||||
import { View } from '../models/view/view';
|
import { View } from '../models/view/view';
|
||||||
|
|
||||||
import { ApiService } from '../abstractions/api.service';
|
import { ApiService } from '../abstractions/api.service';
|
||||||
@@ -61,6 +64,41 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async encrypt(model: CipherView, key?: SymmetricCryptoKey): Promise<Cipher> {
|
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();
|
const cipher = new Cipher();
|
||||||
cipher.id = model.id;
|
cipher.id = model.id;
|
||||||
cipher.folderId = model.folderId;
|
cipher.folderId = model.folderId;
|
||||||
@@ -81,6 +119,9 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
this.encryptFields(model.fields, key).then((fields) => {
|
this.encryptFields(model.fields, key).then((fields) => {
|
||||||
cipher.fields = fields;
|
cipher.fields = fields;
|
||||||
}),
|
}),
|
||||||
|
this.encryptPasswordHistories(model.passwordHistory, key).then((ph) => {
|
||||||
|
cipher.passwordHistory = ph;
|
||||||
|
}),
|
||||||
this.encryptAttachments(model.attachments, key).then((attachments) => {
|
this.encryptAttachments(model.attachments, key).then((attachments) => {
|
||||||
cipher.attachments = attachments;
|
cipher.attachments = attachments;
|
||||||
}),
|
}),
|
||||||
@@ -144,6 +185,35 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
return field;
|
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> {
|
async get(id: string): Promise<Cipher> {
|
||||||
const userId = await this.userService.getUserId();
|
const userId = await this.userService.getUserId();
|
||||||
const localData = await this.storageService.get<any>(Keys.localData);
|
const localData = await this.storageService.get<any>(Keys.localData);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { CipherString } from '../models/domain/cipherString';
|
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 { CryptoService } from '../abstractions/crypto.service';
|
||||||
import {
|
import {
|
||||||
@@ -29,7 +29,7 @@ const MaxPasswordsInHistory = 100;
|
|||||||
|
|
||||||
export class PasswordGenerationService implements PasswordGenerationServiceAbstraction {
|
export class PasswordGenerationService implements PasswordGenerationServiceAbstraction {
|
||||||
private optionsCache: any;
|
private optionsCache: any;
|
||||||
private history: PasswordHistory[];
|
private history: GeneratedPasswordHistory[];
|
||||||
|
|
||||||
constructor(private cryptoService: CryptoService, private storageService: StorageService) { }
|
constructor(private cryptoService: CryptoService, private storageService: StorageService) { }
|
||||||
|
|
||||||
@@ -168,18 +168,18 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
|
|||||||
this.optionsCache = options;
|
this.optionsCache = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getHistory(): Promise<PasswordHistory[]> {
|
async getHistory(): Promise<GeneratedPasswordHistory[]> {
|
||||||
const hasKey = await this.cryptoService.hasKey();
|
const hasKey = await this.cryptoService.hasKey();
|
||||||
if (!hasKey) {
|
if (!hasKey) {
|
||||||
return new Array<PasswordHistory>();
|
return new Array<GeneratedPasswordHistory>();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.history) {
|
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);
|
this.history = await this.decryptHistory(encrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.history || new Array<PasswordHistory>();
|
return this.history || new Array<GeneratedPasswordHistory>();
|
||||||
}
|
}
|
||||||
|
|
||||||
async addHistory(password: string): Promise<any> {
|
async addHistory(password: string): Promise<any> {
|
||||||
@@ -196,7 +196,7 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentHistory.unshift(new PasswordHistory(password, Date.now()));
|
currentHistory.unshift(new GeneratedPasswordHistory(password, Date.now()));
|
||||||
|
|
||||||
// Remove old items.
|
// Remove old items.
|
||||||
if (currentHistory.length > MaxPasswordsInHistory) {
|
if (currentHistory.length > MaxPasswordsInHistory) {
|
||||||
@@ -212,33 +212,33 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
|
|||||||
return await this.storageService.remove(Keys.history);
|
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) {
|
if (history == null || history.length === 0) {
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const promises = history.map(async (item) => {
|
const promises = history.map(async (item) => {
|
||||||
const encrypted = await this.cryptoService.encrypt(item.password);
|
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);
|
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) {
|
if (history == null || history.length === 0) {
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const promises = history.map(async (item) => {
|
const promises = history.map(async (item) => {
|
||||||
const decrypted = await this.cryptoService.decryptToUtf8(new CipherString(item.password));
|
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);
|
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) {
|
if (history == null || history.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user