mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-30 13:03:53 +01:00
Add support for Emergency Access (#204)
* Add support for Emergency Access * Resolve review comments
This commit is contained in:
parent
12321e53b9
commit
573eea66ee
@ -15,6 +15,11 @@ import { CollectionRequest } from '../models/request/collectionRequest';
|
||||
import { DeleteRecoverRequest } from '../models/request/deleteRecoverRequest';
|
||||
import { EmailRequest } from '../models/request/emailRequest';
|
||||
import { EmailTokenRequest } from '../models/request/emailTokenRequest';
|
||||
import { EmergencyAccessAcceptRequest } from '../models/request/emergencyAccessAcceptRequest';
|
||||
import { EmergencyAccessConfirmRequest } from '../models/request/emergencyAccessConfirmRequest';
|
||||
import { EmergencyAccessInviteRequest } from '../models/request/emergencyAccessInviteRequest';
|
||||
import { EmergencyAccessPasswordRequest } from '../models/request/emergencyAccessPasswordRequest';
|
||||
import { EmergencyAccessUpdateRequest } from '../models/request/emergencyAccessUpdateRequest';
|
||||
import { EventRequest } from '../models/request/eventRequest';
|
||||
import { FolderRequest } from '../models/request/folderRequest';
|
||||
import { GroupRequest } from '../models/request/groupRequest';
|
||||
@ -73,6 +78,12 @@ import {
|
||||
CollectionResponse,
|
||||
} from '../models/response/collectionResponse';
|
||||
import { DomainsResponse } from '../models/response/domainsResponse';
|
||||
import {
|
||||
EmergencyAccessGranteeDetailsResponse,
|
||||
EmergencyAccessGrantorDetailsResponse,
|
||||
EmergencyAccessTakeoverResponse,
|
||||
EmergencyAccessViewResponse
|
||||
} from '../models/response/emergencyAccessResponse';
|
||||
import { EventResponse } from '../models/response/eventResponse';
|
||||
import { FolderResponse } from '../models/response/folderResponse';
|
||||
import {
|
||||
@ -278,6 +289,22 @@ export abstract class ApiService {
|
||||
postTwoFactorEmailSetup: (request: TwoFactorEmailRequest) => Promise<any>;
|
||||
postTwoFactorEmail: (request: TwoFactorEmailRequest) => Promise<any>;
|
||||
|
||||
getEmergencyAccessTrusted: () => Promise<ListResponse<EmergencyAccessGranteeDetailsResponse>>;
|
||||
getEmergencyAccessGranted: () => Promise<ListResponse<EmergencyAccessGrantorDetailsResponse>>;
|
||||
getEmergencyAccess: (id: string) => Promise<EmergencyAccessGranteeDetailsResponse>;
|
||||
putEmergencyAccess: (id: string, request: EmergencyAccessUpdateRequest) => Promise<any>;
|
||||
deleteEmergencyAccess: (id: string) => Promise<any>;
|
||||
postEmergencyAccessInvite: (request: EmergencyAccessInviteRequest) => Promise<any>;
|
||||
postEmergencyAccessReinvite: (id: string) => Promise<any>;
|
||||
postEmergencyAccessAccept: (id: string, request: EmergencyAccessAcceptRequest) => Promise<any>;
|
||||
postEmergencyAccessConfirm: (id: string, request: EmergencyAccessConfirmRequest) => Promise<any>;
|
||||
postEmergencyAccessInitiate: (id: string) => Promise<any>;
|
||||
postEmergencyAccessApprove: (id: string) => Promise<any>;
|
||||
postEmergencyAccessReject: (id: string) => Promise<any>;
|
||||
postEmergencyAccessTakeover: (id: string) => Promise<EmergencyAccessTakeoverResponse>;
|
||||
postEmergencyAccessPassword: (id: string, request: EmergencyAccessPasswordRequest) => Promise<any>;
|
||||
postEmergencyAccessView: (id: string) => Promise<EmergencyAccessViewResponse>;
|
||||
|
||||
getOrganization: (id: string) => Promise<OrganizationResponse>;
|
||||
getOrganizationBilling: (id: string) => Promise<BillingResponse>;
|
||||
getOrganizationSubscription: (id: string) => Promise<OrganizationSubscriptionResponse>;
|
||||
|
@ -38,10 +38,11 @@ export abstract class CryptoService {
|
||||
makeSendKey: (keyMaterial: ArrayBuffer) => Promise<SymmetricCryptoKey>;
|
||||
hashPassword: (password: string, key: SymmetricCryptoKey) => Promise<string>;
|
||||
makeEncKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, CipherString]>;
|
||||
remakeEncKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, CipherString]>;
|
||||
remakeEncKey: (key: SymmetricCryptoKey, encKey?: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, CipherString]>;
|
||||
encrypt: (plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey) => Promise<CipherString>;
|
||||
encryptToBytes: (plainValue: ArrayBuffer, key?: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
||||
rsaEncrypt: (data: ArrayBuffer, publicKey?: ArrayBuffer) => Promise<CipherString>;
|
||||
rsaDecrypt: (encValue: string) => Promise<ArrayBuffer>;
|
||||
decryptToBytes: (cipherString: CipherString, key?: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
||||
decryptToUtf8: (cipherString: CipherString, key?: SymmetricCryptoKey) => Promise<string>;
|
||||
decryptFromBytes: (encBuf: ArrayBuffer, key: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
||||
|
@ -21,11 +21,11 @@ export class ChangePasswordComponent implements OnInit {
|
||||
masterPasswordScore: number;
|
||||
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
|
||||
protected email: string;
|
||||
protected kdf: KdfType;
|
||||
protected kdfIterations: number;
|
||||
|
||||
private masterPasswordStrengthTimeout: any;
|
||||
private email: string;
|
||||
|
||||
constructor(protected i18nService: I18nService, protected cryptoService: CryptoService,
|
||||
protected messagingService: MessagingService, protected userService: UserService,
|
||||
@ -58,43 +58,9 @@ export class ChangePasswordComponent implements OnInit {
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (this.masterPassword == null || this.masterPassword === '') {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('masterPassRequired'));
|
||||
if (!await this.strongPassword()) {
|
||||
return;
|
||||
}
|
||||
if (this.masterPassword.length < 8) {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('masterPassLength'));
|
||||
return;
|
||||
}
|
||||
if (this.masterPassword !== this.masterPasswordRetype) {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('masterPassDoesntMatch'));
|
||||
return;
|
||||
}
|
||||
|
||||
const strengthResult = this.passwordGenerationService.passwordStrength(this.masterPassword,
|
||||
this.getPasswordStrengthUserInput());
|
||||
|
||||
if (this.enforcedPolicyOptions != null &&
|
||||
!this.policyService.evaluateMasterPassword(
|
||||
strengthResult.score,
|
||||
this.masterPassword,
|
||||
this.enforcedPolicyOptions)) {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('masterPasswordPolicyRequirementsNotMet'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (strengthResult != null && strengthResult.score < 3) {
|
||||
const result = await this.platformUtilsService.showDialog(this.i18nService.t('weakMasterPasswordDesc'),
|
||||
this.i18nService.t('weakMasterPassword'), this.i18nService.t('yes'), this.i18nService.t('no'),
|
||||
'warning');
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!await this.setupSubmitActions()) {
|
||||
return;
|
||||
@ -133,6 +99,48 @@ export class ChangePasswordComponent implements OnInit {
|
||||
// Override in sub-class
|
||||
}
|
||||
|
||||
async strongPassword(): Promise<boolean> {
|
||||
if (this.masterPassword == null || this.masterPassword === '') {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('masterPassRequired'));
|
||||
return false;
|
||||
}
|
||||
if (this.masterPassword.length < 8) {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('masterPassLength'));
|
||||
return false;
|
||||
}
|
||||
if (this.masterPassword !== this.masterPasswordRetype) {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('masterPassDoesntMatch'));
|
||||
return false;
|
||||
}
|
||||
|
||||
const strengthResult = this.passwordGenerationService.passwordStrength(this.masterPassword,
|
||||
this.getPasswordStrengthUserInput());
|
||||
|
||||
if (this.enforcedPolicyOptions != null &&
|
||||
!this.policyService.evaluateMasterPassword(
|
||||
strengthResult.score,
|
||||
this.masterPassword,
|
||||
this.enforcedPolicyOptions)) {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('masterPasswordPolicyRequirementsNotMet'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strengthResult != null && strengthResult.score < 3) {
|
||||
const result = await this.platformUtilsService.showDialog(this.i18nService.t('weakMasterPasswordDesc'),
|
||||
this.i18nService.t('weakMasterPassword'), this.i18nService.t('yes'), this.i18nService.t('no'),
|
||||
'warning');
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
updatePasswordStrength() {
|
||||
if (this.masterPasswordStrengthTimeout != null) {
|
||||
clearTimeout(this.masterPasswordStrengthTimeout);
|
||||
|
7
src/enums/emergencyAccessStatusType.ts
Normal file
7
src/enums/emergencyAccessStatusType.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export enum EmergencyAccessStatusType {
|
||||
Invited = 0,
|
||||
Accepted = 1,
|
||||
Confirmed = 2,
|
||||
RecoveryInitiated = 3,
|
||||
RecoveryApproved = 4,
|
||||
}
|
5
src/enums/emergencyAccessType.ts
Normal file
5
src/enums/emergencyAccessType.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export enum EmergencyAccessType
|
||||
{
|
||||
View = 0,
|
||||
Takeover = 1,
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -19,6 +19,11 @@ import { CollectionRequest } from '../models/request/collectionRequest';
|
||||
import { DeleteRecoverRequest } from '../models/request/deleteRecoverRequest';
|
||||
import { EmailRequest } from '../models/request/emailRequest';
|
||||
import { EmailTokenRequest } from '../models/request/emailTokenRequest';
|
||||
import { EmergencyAccessAcceptRequest } from '../models/request/emergencyAccessAcceptRequest';
|
||||
import { EmergencyAccessConfirmRequest } from '../models/request/emergencyAccessConfirmRequest';
|
||||
import { EmergencyAccessInviteRequest } from '../models/request/emergencyAccessInviteRequest';
|
||||
import { EmergencyAccessPasswordRequest } from '../models/request/emergencyAccessPasswordRequest';
|
||||
import { EmergencyAccessUpdateRequest } from '../models/request/emergencyAccessUpdateRequest';
|
||||
import { EventRequest } from '../models/request/eventRequest';
|
||||
import { FolderRequest } from '../models/request/folderRequest';
|
||||
import { GroupRequest } from '../models/request/groupRequest';
|
||||
@ -77,6 +82,12 @@ import {
|
||||
CollectionResponse,
|
||||
} from '../models/response/collectionResponse';
|
||||
import { DomainsResponse } from '../models/response/domainsResponse';
|
||||
import {
|
||||
EmergencyAccessGranteeDetailsResponse,
|
||||
EmergencyAccessGrantorDetailsResponse,
|
||||
EmergencyAccessTakeoverResponse,
|
||||
EmergencyAccessViewResponse
|
||||
} from '../models/response/emergencyAccessResponse';
|
||||
import { ErrorResponse } from '../models/response/errorResponse';
|
||||
import { EventResponse } from '../models/response/eventResponse';
|
||||
import { FolderResponse } from '../models/response/folderResponse';
|
||||
@ -901,6 +912,73 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
return this.send('POST', '/two-factor/send-email-login', request, false, false);
|
||||
}
|
||||
|
||||
// Emergency Access APIs
|
||||
|
||||
async getEmergencyAccessTrusted(): Promise<ListResponse<EmergencyAccessGranteeDetailsResponse>> {
|
||||
const r = await this.send('GET', '/emergency-access/trusted', null, true, true);
|
||||
return new ListResponse(r, EmergencyAccessGranteeDetailsResponse);
|
||||
}
|
||||
|
||||
async getEmergencyAccessGranted(): Promise<ListResponse<EmergencyAccessGrantorDetailsResponse>> {
|
||||
const r = await this.send('GET', '/emergency-access/granted', null, true, true);
|
||||
return new ListResponse(r, EmergencyAccessGrantorDetailsResponse);
|
||||
}
|
||||
|
||||
async getEmergencyAccess(id: string): Promise<EmergencyAccessGranteeDetailsResponse> {
|
||||
const r = await this.send('GET', '/emergency-access/' + id, null, true, true);
|
||||
return new EmergencyAccessGranteeDetailsResponse(r);
|
||||
}
|
||||
|
||||
putEmergencyAccess(id: string, request: EmergencyAccessUpdateRequest): Promise<any> {
|
||||
return this.send('PUT', '/emergency-access/' + id, request, true, false);
|
||||
}
|
||||
|
||||
deleteEmergencyAccess(id: string): Promise<any> {
|
||||
return this.send('DELETE', '/emergency-access/' + id, null, true, false);
|
||||
}
|
||||
|
||||
postEmergencyAccessInvite(request: EmergencyAccessInviteRequest): Promise<any> {
|
||||
return this.send('POST', '/emergency-access/invite', request, true, false);
|
||||
}
|
||||
|
||||
postEmergencyAccessReinvite(id: string): Promise<any> {
|
||||
return this.send('POST', '/emergency-access/' + id + '/reinvite', null, true, false);
|
||||
}
|
||||
|
||||
postEmergencyAccessAccept(id: string, request: EmergencyAccessAcceptRequest): Promise<any> {
|
||||
return this.send('POST', '/emergency-access/' + id + '/accept', request, true, false);
|
||||
}
|
||||
|
||||
postEmergencyAccessConfirm(id: string, request: EmergencyAccessConfirmRequest): Promise<any> {
|
||||
return this.send('POST', '/emergency-access/' + id + '/confirm', request, true, false);
|
||||
}
|
||||
|
||||
postEmergencyAccessInitiate(id: string): Promise<any> {
|
||||
return this.send('POST', '/emergency-access/' + id + '/initiate', null, true, false);
|
||||
}
|
||||
|
||||
postEmergencyAccessApprove(id: string): Promise<any> {
|
||||
return this.send('POST', '/emergency-access/' + id + '/approve', null, true, false);
|
||||
}
|
||||
|
||||
postEmergencyAccessReject(id: string): Promise<any> {
|
||||
return this.send('POST', '/emergency-access/' + id + '/reject', null, true, false);
|
||||
}
|
||||
|
||||
async postEmergencyAccessTakeover(id: string): Promise<EmergencyAccessTakeoverResponse> {
|
||||
const r = await this.send('POST', '/emergency-access/' + id + '/takeover', null, true, true);
|
||||
return new EmergencyAccessTakeoverResponse(r);
|
||||
}
|
||||
|
||||
async postEmergencyAccessPassword(id: string, request: EmergencyAccessPasswordRequest): Promise<any> {
|
||||
const r = await this.send('POST', '/emergency-access/' + id + '/password', request, true, true);
|
||||
}
|
||||
|
||||
async postEmergencyAccessView(id: string): Promise<EmergencyAccessViewResponse> {
|
||||
const r = await this.send('POST', '/emergency-access/' + id + '/view', null, true, true);
|
||||
return new EmergencyAccessViewResponse(r);
|
||||
}
|
||||
|
||||
// Organization APIs
|
||||
|
||||
async getOrganization(id: string): Promise<OrganizationResponse> {
|
||||
|
@ -380,8 +380,10 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return this.buildEncKey(theKey, encKey);
|
||||
}
|
||||
|
||||
async remakeEncKey(key: SymmetricCryptoKey): Promise<[SymmetricCryptoKey, CipherString]> {
|
||||
const encKey = await this.getEncKey();
|
||||
async remakeEncKey(key: SymmetricCryptoKey, encKey?: SymmetricCryptoKey): Promise<[SymmetricCryptoKey, CipherString]> {
|
||||
if (encKey == null) {
|
||||
encKey = await this.getEncKey();
|
||||
}
|
||||
return this.buildEncKey(key, encKey.key);
|
||||
}
|
||||
|
||||
@ -434,6 +436,58 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return new CipherString(EncryptionType.Rsa2048_OaepSha1_B64, Utils.fromBufferToB64(encBytes));
|
||||
}
|
||||
|
||||
async rsaDecrypt(encValue: string): Promise<ArrayBuffer> {
|
||||
const headerPieces = encValue.split('.');
|
||||
let encType: EncryptionType = null;
|
||||
let encPieces: string[];
|
||||
|
||||
if (headerPieces.length === 1) {
|
||||
encType = EncryptionType.Rsa2048_OaepSha256_B64;
|
||||
encPieces = [headerPieces[0]];
|
||||
} else if (headerPieces.length === 2) {
|
||||
try {
|
||||
encType = parseInt(headerPieces[0], null);
|
||||
encPieces = headerPieces[1].split('|');
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
switch (encType) {
|
||||
case EncryptionType.Rsa2048_OaepSha256_B64:
|
||||
case EncryptionType.Rsa2048_OaepSha1_B64:
|
||||
// HmacSha256 types are deprecated
|
||||
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
|
||||
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
|
||||
break;
|
||||
default:
|
||||
throw new Error('encType unavailable.');
|
||||
}
|
||||
|
||||
if (encPieces == null || encPieces.length <= 0) {
|
||||
throw new Error('encPieces unavailable.');
|
||||
}
|
||||
|
||||
const data = Utils.fromB64ToArray(encPieces[0]).buffer;
|
||||
const privateKey = await this.getPrivateKey();
|
||||
if (privateKey == null) {
|
||||
throw new Error('No private key.');
|
||||
}
|
||||
|
||||
let alg: 'sha1' | 'sha256' = 'sha1';
|
||||
switch (encType) {
|
||||
case EncryptionType.Rsa2048_OaepSha256_B64:
|
||||
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
|
||||
alg = 'sha256';
|
||||
break;
|
||||
case EncryptionType.Rsa2048_OaepSha1_B64:
|
||||
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
|
||||
break;
|
||||
default:
|
||||
throw new Error('encType unavailable.');
|
||||
}
|
||||
|
||||
return this.cryptoFunctionService.rsaDecrypt(data, privateKey, alg);
|
||||
}
|
||||
|
||||
async decryptToBytes(cipherString: CipherString, key?: SymmetricCryptoKey): Promise<ArrayBuffer> {
|
||||
const iv = Utils.fromB64ToArray(cipherString.iv).buffer;
|
||||
const data = Utils.fromB64ToArray(cipherString.data).buffer;
|
||||
@ -604,58 +658,6 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return await this.cryptoFunctionService.aesDecrypt(data, iv, theKey.encKey);
|
||||
}
|
||||
|
||||
private async rsaDecrypt(encValue: string): Promise<ArrayBuffer> {
|
||||
const headerPieces = encValue.split('.');
|
||||
let encType: EncryptionType = null;
|
||||
let encPieces: string[];
|
||||
|
||||
if (headerPieces.length === 1) {
|
||||
encType = EncryptionType.Rsa2048_OaepSha256_B64;
|
||||
encPieces = [headerPieces[0]];
|
||||
} else if (headerPieces.length === 2) {
|
||||
try {
|
||||
encType = parseInt(headerPieces[0], null);
|
||||
encPieces = headerPieces[1].split('|');
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
switch (encType) {
|
||||
case EncryptionType.Rsa2048_OaepSha256_B64:
|
||||
case EncryptionType.Rsa2048_OaepSha1_B64:
|
||||
// HmacSha256 types are deprecated
|
||||
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
|
||||
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
|
||||
break;
|
||||
default:
|
||||
throw new Error('encType unavailable.');
|
||||
}
|
||||
|
||||
if (encPieces == null || encPieces.length <= 0) {
|
||||
throw new Error('encPieces unavailable.');
|
||||
}
|
||||
|
||||
const data = Utils.fromB64ToArray(encPieces[0]).buffer;
|
||||
const privateKey = await this.getPrivateKey();
|
||||
if (privateKey == null) {
|
||||
throw new Error('No private key.');
|
||||
}
|
||||
|
||||
let alg: 'sha1' | 'sha256' = 'sha1';
|
||||
switch (encType) {
|
||||
case EncryptionType.Rsa2048_OaepSha256_B64:
|
||||
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
|
||||
alg = 'sha256';
|
||||
break;
|
||||
case EncryptionType.Rsa2048_OaepSha1_B64:
|
||||
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
|
||||
break;
|
||||
default:
|
||||
throw new Error('encType unavailable.');
|
||||
}
|
||||
|
||||
return this.cryptoFunctionService.rsaDecrypt(data, privateKey, alg);
|
||||
}
|
||||
|
||||
private async getKeyForEncryption(key?: SymmetricCryptoKey): Promise<SymmetricCryptoKey> {
|
||||
if (key != null) {
|
||||
return key;
|
||||
|
Loading…
Reference in New Issue
Block a user