mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 01:33:33 +00:00
Add support for Emergency Access (#204)
* Add support for Emergency Access * Resolve review comments
This commit is contained in:
@@ -34,10 +34,10 @@ export class Attachment extends Domain {
|
||||
}, alreadyEncrypted, ['id', 'url', 'sizeName']);
|
||||
}
|
||||
|
||||
async decrypt(orgId: string): Promise<AttachmentView> {
|
||||
async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<AttachmentView> {
|
||||
const view = await this.decryptObj(new AttachmentView(this), {
|
||||
fileName: null,
|
||||
}, orgId);
|
||||
}, orgId, encKey);
|
||||
|
||||
if (this.key != null) {
|
||||
let cryptoService: CryptoService;
|
||||
@@ -50,7 +50,7 @@ export class Attachment extends Domain {
|
||||
|
||||
try {
|
||||
const orgKey = await cryptoService.getOrgKey(orgId);
|
||||
const decValue = await cryptoService.decryptToBytes(this.key, orgKey);
|
||||
const decValue = await cryptoService.decryptToBytes(this.key, orgKey ?? encKey);
|
||||
view.key = new SymmetricCryptoKey(decValue);
|
||||
} catch (e) {
|
||||
// TODO: error?
|
||||
|
||||
@@ -4,6 +4,7 @@ import { CipherString } from './cipherString';
|
||||
import Domain from './domainBase';
|
||||
|
||||
import { CardView } from '../view/cardView';
|
||||
import { SymmetricCryptoKey } from './symmetricCryptoKey';
|
||||
|
||||
export class Card extends Domain {
|
||||
cardholderName: CipherString;
|
||||
@@ -29,7 +30,7 @@ export class Card extends Domain {
|
||||
}, alreadyEncrypted, []);
|
||||
}
|
||||
|
||||
decrypt(orgId: string): Promise<CardView> {
|
||||
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<CardView> {
|
||||
return this.decryptObj(new CardView(this), {
|
||||
cardholderName: null,
|
||||
brand: null,
|
||||
@@ -37,7 +38,7 @@ export class Card extends Domain {
|
||||
expMonth: null,
|
||||
expYear: null,
|
||||
code: null,
|
||||
}, orgId);
|
||||
}, orgId, encKey);
|
||||
}
|
||||
|
||||
toCardData(): CardData {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Identity } from './identity';
|
||||
import { Login } from './login';
|
||||
import { Password } from './password';
|
||||
import { SecureNote } from './secureNote';
|
||||
import { SymmetricCryptoKey } from './symmetricCryptoKey';
|
||||
|
||||
export class Cipher extends Domain {
|
||||
id: string;
|
||||
@@ -102,26 +103,26 @@ export class Cipher extends Domain {
|
||||
}
|
||||
}
|
||||
|
||||
async decrypt(): Promise<CipherView> {
|
||||
async decrypt(encKey?: SymmetricCryptoKey): Promise<CipherView> {
|
||||
const model = new CipherView(this);
|
||||
|
||||
await this.decryptObj(model, {
|
||||
name: null,
|
||||
notes: null,
|
||||
}, this.organizationId);
|
||||
}, this.organizationId, encKey);
|
||||
|
||||
switch (this.type) {
|
||||
case CipherType.Login:
|
||||
model.login = await this.login.decrypt(this.organizationId);
|
||||
model.login = await this.login.decrypt(this.organizationId, encKey);
|
||||
break;
|
||||
case CipherType.SecureNote:
|
||||
model.secureNote = await this.secureNote.decrypt(this.organizationId);
|
||||
model.secureNote = await this.secureNote.decrypt(this.organizationId, encKey);
|
||||
break;
|
||||
case CipherType.Card:
|
||||
model.card = await this.card.decrypt(this.organizationId);
|
||||
model.card = await this.card.decrypt(this.organizationId, encKey);
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
model.identity = await this.identity.decrypt(this.organizationId);
|
||||
model.identity = await this.identity.decrypt(this.organizationId, encKey);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -133,7 +134,7 @@ export class Cipher extends Domain {
|
||||
const attachments: any[] = [];
|
||||
await this.attachments.reduce((promise, attachment) => {
|
||||
return promise.then(() => {
|
||||
return attachment.decrypt(orgId);
|
||||
return attachment.decrypt(orgId, encKey);
|
||||
}).then((decAttachment) => {
|
||||
attachments.push(decAttachment);
|
||||
});
|
||||
@@ -145,7 +146,7 @@ export class Cipher extends Domain {
|
||||
const fields: any[] = [];
|
||||
await this.fields.reduce((promise, field) => {
|
||||
return promise.then(() => {
|
||||
return field.decrypt(orgId);
|
||||
return field.decrypt(orgId, encKey);
|
||||
}).then((decField) => {
|
||||
fields.push(decField);
|
||||
});
|
||||
@@ -157,7 +158,7 @@ export class Cipher extends Domain {
|
||||
const passwordHistory: any[] = [];
|
||||
await this.passwordHistory.reduce((promise, ph) => {
|
||||
return promise.then(() => {
|
||||
return ph.decrypt(orgId);
|
||||
return ph.decrypt(orgId, encKey);
|
||||
}).then((decPh) => {
|
||||
passwordHistory.push(decPh);
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import { CipherString } from './cipherString';
|
||||
import Domain from './domainBase';
|
||||
|
||||
import { FieldView } from '../view/fieldView';
|
||||
import { SymmetricCryptoKey } from './symmetricCryptoKey';
|
||||
|
||||
export class Field extends Domain {
|
||||
name: CipherString;
|
||||
@@ -25,11 +26,11 @@ export class Field extends Domain {
|
||||
}, alreadyEncrypted, []);
|
||||
}
|
||||
|
||||
decrypt(orgId: string): Promise<FieldView> {
|
||||
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<FieldView> {
|
||||
return this.decryptObj(new FieldView(this), {
|
||||
name: null,
|
||||
value: null,
|
||||
}, orgId);
|
||||
}, orgId, encKey);
|
||||
}
|
||||
|
||||
toFieldData(): FieldData {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { IdentityData } from '../data/identityData';
|
||||
|
||||
import { CipherString } from './cipherString';
|
||||
import Domain from './domainBase';
|
||||
import { SymmetricCryptoKey } from './symmetricCryptoKey';
|
||||
|
||||
import { IdentityView } from '../view/identityView';
|
||||
|
||||
@@ -53,7 +54,7 @@ export class Identity extends Domain {
|
||||
}, alreadyEncrypted, []);
|
||||
}
|
||||
|
||||
decrypt(orgId: string): Promise<IdentityView> {
|
||||
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<IdentityView> {
|
||||
return this.decryptObj(new IdentityView(this), {
|
||||
title: null,
|
||||
firstName: null,
|
||||
@@ -73,7 +74,7 @@ export class Identity extends Domain {
|
||||
username: null,
|
||||
passportNumber: null,
|
||||
licenseNumber: null,
|
||||
}, orgId);
|
||||
}, orgId, encKey);
|
||||
}
|
||||
|
||||
toIdentityData(): IdentityData {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { LoginView } from '../view/loginView';
|
||||
|
||||
import { CipherString } from './cipherString';
|
||||
import Domain from './domainBase';
|
||||
import { SymmetricCryptoKey } from './symmetricCryptoKey';
|
||||
|
||||
export class Login extends Domain {
|
||||
uris: LoginUri[];
|
||||
@@ -35,17 +36,17 @@ export class Login extends Domain {
|
||||
}
|
||||
}
|
||||
|
||||
async decrypt(orgId: string): Promise<LoginView> {
|
||||
async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<LoginView> {
|
||||
const view = await this.decryptObj(new LoginView(this), {
|
||||
username: null,
|
||||
password: null,
|
||||
totp: null,
|
||||
}, orgId);
|
||||
}, orgId, encKey);
|
||||
|
||||
if (this.uris != null) {
|
||||
view.uris = [];
|
||||
for (let i = 0; i < this.uris.length; i++) {
|
||||
const uri = await this.uris[i].decrypt(orgId);
|
||||
const uri = await this.uris[i].decrypt(orgId, encKey);
|
||||
view.uris.push(uri);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { LoginUriView } from '../view/loginUriView';
|
||||
|
||||
import { CipherString } from './cipherString';
|
||||
import Domain from './domainBase';
|
||||
import { SymmetricCryptoKey } from './symmetricCryptoKey';
|
||||
|
||||
export class LoginUri extends Domain {
|
||||
uri: CipherString;
|
||||
@@ -23,10 +24,10 @@ export class LoginUri extends Domain {
|
||||
}, alreadyEncrypted, []);
|
||||
}
|
||||
|
||||
decrypt(orgId: string): Promise<LoginUriView> {
|
||||
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<LoginUriView> {
|
||||
return this.decryptObj(new LoginUriView(this), {
|
||||
uri: null,
|
||||
}, orgId);
|
||||
}, orgId, encKey);
|
||||
}
|
||||
|
||||
toLoginUriData(): LoginUriData {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { CipherString } from './cipherString';
|
||||
import Domain from './domainBase';
|
||||
|
||||
import { PasswordHistoryView } from '../view/passwordHistoryView';
|
||||
import { SymmetricCryptoKey } from './symmetricCryptoKey';
|
||||
|
||||
export class Password extends Domain {
|
||||
password: CipherString;
|
||||
@@ -21,10 +22,10 @@ export class Password extends Domain {
|
||||
this.lastUsedDate = new Date(obj.lastUsedDate);
|
||||
}
|
||||
|
||||
decrypt(orgId: string): Promise<PasswordHistoryView> {
|
||||
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<PasswordHistoryView> {
|
||||
return this.decryptObj(new PasswordHistoryView(this), {
|
||||
password: null,
|
||||
}, orgId);
|
||||
}, orgId, encKey);
|
||||
}
|
||||
|
||||
toPasswordHistoryData(): PasswordHistoryData {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { SecureNoteData } from '../data/secureNoteData';
|
||||
import Domain from './domainBase';
|
||||
|
||||
import { SecureNoteView } from '../view/secureNoteView';
|
||||
import { SymmetricCryptoKey } from './symmetricCryptoKey';
|
||||
|
||||
export class SecureNote extends Domain {
|
||||
type: SecureNoteType;
|
||||
@@ -18,7 +19,7 @@ export class SecureNote extends Domain {
|
||||
this.type = obj.type;
|
||||
}
|
||||
|
||||
decrypt(orgId: string): Promise<SecureNoteView> {
|
||||
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<SecureNoteView> {
|
||||
return Promise.resolve(new SecureNoteView(this));
|
||||
}
|
||||
|
||||
|
||||
3
src/models/request/emergencyAccessAcceptRequest.ts
Normal file
3
src/models/request/emergencyAccessAcceptRequest.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export class EmergencyAccessAcceptRequest {
|
||||
token: string;
|
||||
}
|
||||
3
src/models/request/emergencyAccessConfirmRequest.ts
Normal file
3
src/models/request/emergencyAccessConfirmRequest.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export class EmergencyAccessConfirmRequest {
|
||||
key: string;
|
||||
}
|
||||
7
src/models/request/emergencyAccessInviteRequest.ts
Normal file
7
src/models/request/emergencyAccessInviteRequest.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { EmergencyAccessType } from '../../enums/emergencyAccessType';
|
||||
|
||||
export class EmergencyAccessInviteRequest {
|
||||
email: string;
|
||||
type: EmergencyAccessType;
|
||||
waitTimeDays: number;
|
||||
}
|
||||
4
src/models/request/emergencyAccessPasswordRequest.ts
Normal file
4
src/models/request/emergencyAccessPasswordRequest.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export class EmergencyAccessPasswordRequest {
|
||||
newMasterPasswordHash: string;
|
||||
key: string;
|
||||
}
|
||||
7
src/models/request/emergencyAccessUpdateRequest.ts
Normal file
7
src/models/request/emergencyAccessUpdateRequest.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { EmergencyAccessType } from '../../enums/emergencyAccessType';
|
||||
|
||||
export class EmergencyAccessUpdateRequest {
|
||||
type: EmergencyAccessType;
|
||||
waitTimeDays: number;
|
||||
keyEncrypted?: string;
|
||||
}
|
||||
81
src/models/response/emergencyAccessResponse.ts
Normal file
81
src/models/response/emergencyAccessResponse.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { EmergencyAccessStatusType } from '../../enums/emergencyAccessStatusType';
|
||||
import { EmergencyAccessType } from '../../enums/emergencyAccessType';
|
||||
import { KdfType } from '../../enums/kdfType';
|
||||
import { BaseResponse } from './baseResponse';
|
||||
import { CipherResponse } from './cipherResponse';
|
||||
|
||||
export class EmergencyAccessGranteeDetailsResponse extends BaseResponse {
|
||||
id: string;
|
||||
granteeId: string;
|
||||
name: string;
|
||||
email: string;
|
||||
type: EmergencyAccessType;
|
||||
status: EmergencyAccessStatusType;
|
||||
waitTimeDays: number;
|
||||
creationDate: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.id = this.getResponseProperty('Id');
|
||||
this.granteeId = this.getResponseProperty('GranteeId');
|
||||
this.name = this.getResponseProperty('Name');
|
||||
this.email = this.getResponseProperty('Email');
|
||||
this.type = this.getResponseProperty('Type');
|
||||
this.status = this.getResponseProperty('Status');
|
||||
this.waitTimeDays = this.getResponseProperty('WaitTimeDays');
|
||||
this.creationDate = this.getResponseProperty('CreationDate');
|
||||
}
|
||||
}
|
||||
|
||||
export class EmergencyAccessGrantorDetailsResponse extends BaseResponse {
|
||||
id: string;
|
||||
grantorId: string;
|
||||
name: string;
|
||||
email: string;
|
||||
type: EmergencyAccessType;
|
||||
status: EmergencyAccessStatusType;
|
||||
waitTimeDays: number;
|
||||
creationDate: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.id = this.getResponseProperty('Id');
|
||||
this.grantorId = this.getResponseProperty('GrantorId');
|
||||
this.name = this.getResponseProperty('Name');
|
||||
this.email = this.getResponseProperty('Email');
|
||||
this.type = this.getResponseProperty('Type');
|
||||
this.status = this.getResponseProperty('Status');
|
||||
this.waitTimeDays = this.getResponseProperty('WaitTimeDays');
|
||||
this.creationDate = this.getResponseProperty('CreationDate');
|
||||
}
|
||||
}
|
||||
|
||||
export class EmergencyAccessTakeoverResponse extends BaseResponse {
|
||||
keyEncrypted: string;
|
||||
kdf: KdfType;
|
||||
kdfIterations: number;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
|
||||
this.keyEncrypted = this.getResponseProperty('KeyEncrypted');
|
||||
this.kdf = this.getResponseProperty('Kdf');
|
||||
this.kdfIterations = this.getResponseProperty('KdfIterations');
|
||||
}
|
||||
}
|
||||
|
||||
export class EmergencyAccessViewResponse extends BaseResponse {
|
||||
keyEncrypted: string;
|
||||
ciphers: CipherResponse[] = [];
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
|
||||
this.keyEncrypted = this.getResponseProperty('KeyEncrypted');
|
||||
|
||||
const ciphers = this.getResponseProperty('Ciphers');
|
||||
if (ciphers != null) {
|
||||
this.ciphers = ciphers.map((c: any) => new CipherResponse(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user