mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 09:13:33 +00:00
fixed merge conflict
This commit is contained in:
@@ -243,6 +243,9 @@ export abstract class ApiService {
|
||||
putRestoreManyCiphers: (
|
||||
request: CipherBulkRestoreRequest
|
||||
) => Promise<ListResponse<CipherResponse>>;
|
||||
putRestoreManyCiphersAdmin: (
|
||||
request: CipherBulkRestoreRequest
|
||||
) => Promise<ListResponse<CipherResponse>>;
|
||||
|
||||
/**
|
||||
* @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads.
|
||||
|
||||
@@ -201,6 +201,17 @@ export abstract class OrganizationUserService {
|
||||
request: OrganizationUserResetPasswordRequest
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Enable Secrets Manager for many users
|
||||
* @param organizationId - Identifier for the organization the user belongs to
|
||||
* @param ids - List of organization user identifiers to enable
|
||||
* @return List of user ids, including both those that were successfully enabled and those that had an error
|
||||
*/
|
||||
abstract putOrganizationUserBulkEnableSecretsManager(
|
||||
organizationId: string,
|
||||
ids: string[]
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Delete an organization user
|
||||
* @param organizationId - Identifier for the organization the user belongs to
|
||||
|
||||
@@ -14,6 +14,7 @@ export class OrganizationUserResponse extends BaseResponse {
|
||||
accessSecretsManager: boolean;
|
||||
permissions: PermissionsApi;
|
||||
resetPasswordEnrolled: boolean;
|
||||
hasMasterPassword: boolean;
|
||||
collections: SelectionReadOnlyResponse[] = [];
|
||||
groups: string[] = [];
|
||||
|
||||
@@ -28,6 +29,7 @@ export class OrganizationUserResponse extends BaseResponse {
|
||||
this.accessAll = this.getResponseProperty("AccessAll");
|
||||
this.accessSecretsManager = this.getResponseProperty("AccessSecretsManager");
|
||||
this.resetPasswordEnrolled = this.getResponseProperty("ResetPasswordEnrolled");
|
||||
this.hasMasterPassword = this.getResponseProperty("HasMasterPassword");
|
||||
|
||||
const collections = this.getResponseProperty("Collections");
|
||||
if (collections != null) {
|
||||
|
||||
@@ -3,9 +3,11 @@ import { OrganizationSsoRequest } from "../../../auth/models/request/organizatio
|
||||
import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request";
|
||||
import { ApiKeyResponse } from "../../../auth/models/response/api-key.response";
|
||||
import { OrganizationSsoResponse } from "../../../auth/models/response/organization-sso.response";
|
||||
import { OrganizationSmSubscriptionUpdateRequest } from "../../../billing/models/request/organization-sm-subscription-update.request";
|
||||
import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request";
|
||||
import { OrganizationTaxInfoUpdateRequest } from "../../../billing/models/request/organization-tax-info-update.request";
|
||||
import { PaymentRequest } from "../../../billing/models/request/payment.request";
|
||||
import { SecretsManagerSubscribeRequest } from "../../../billing/models/request/sm-subscribe.request";
|
||||
import { BillingResponse } from "../../../billing/models/response/billing.response";
|
||||
import { OrganizationSubscriptionResponse } from "../../../billing/models/response/organization-subscription.response";
|
||||
import { PaymentResponse } from "../../../billing/models/response/payment.response";
|
||||
@@ -16,7 +18,6 @@ import { StorageRequest } from "../../../models/request/storage.request";
|
||||
import { VerifyBankRequest } from "../../../models/request/verify-bank.request";
|
||||
import { ListResponse } from "../../../models/response/list.response";
|
||||
import { OrganizationApiKeyType } from "../../enums";
|
||||
import { OrganizationEnrollSecretsManagerRequest } from "../../models/request/organization/organization-enroll-secrets-manager.request";
|
||||
import { OrganizationCreateRequest } from "../../models/request/organization-create.request";
|
||||
import { OrganizationKeysRequest } from "../../models/request/organization-keys.request";
|
||||
import { OrganizationUpdateRequest } from "../../models/request/organization-update.request";
|
||||
@@ -25,6 +26,7 @@ import { OrganizationApiKeyInformationResponse } from "../../models/response/org
|
||||
import { OrganizationAutoEnrollStatusResponse } from "../../models/response/organization-auto-enroll-status.response";
|
||||
import { OrganizationKeysResponse } from "../../models/response/organization-keys.response";
|
||||
import { OrganizationResponse } from "../../models/response/organization.response";
|
||||
import { ProfileOrganizationResponse } from "../../models/response/profile-organization.response";
|
||||
|
||||
export class OrganizationApiServiceAbstraction {
|
||||
get: (id: string) => Promise<OrganizationResponse>;
|
||||
@@ -37,7 +39,14 @@ export class OrganizationApiServiceAbstraction {
|
||||
save: (id: string, request: OrganizationUpdateRequest) => Promise<OrganizationResponse>;
|
||||
updatePayment: (id: string, request: PaymentRequest) => Promise<void>;
|
||||
upgrade: (id: string, request: OrganizationUpgradeRequest) => Promise<PaymentResponse>;
|
||||
updateSubscription: (id: string, request: OrganizationSubscriptionUpdateRequest) => Promise<void>;
|
||||
updatePasswordManagerSeats: (
|
||||
id: string,
|
||||
request: OrganizationSubscriptionUpdateRequest
|
||||
) => Promise<void>;
|
||||
updateSecretsManagerSubscription: (
|
||||
id: string,
|
||||
request: OrganizationSmSubscriptionUpdateRequest
|
||||
) => Promise<void>;
|
||||
updateSeats: (id: string, request: SeatRequest) => Promise<PaymentResponse>;
|
||||
updateStorage: (id: string, request: StorageRequest) => Promise<PaymentResponse>;
|
||||
verifyBank: (id: string, request: VerifyBankRequest) => Promise<void>;
|
||||
@@ -60,8 +69,8 @@ export class OrganizationApiServiceAbstraction {
|
||||
getSso: (id: string) => Promise<OrganizationSsoResponse>;
|
||||
updateSso: (id: string, request: OrganizationSsoRequest) => Promise<OrganizationSsoResponse>;
|
||||
selfHostedSyncLicense: (id: string) => Promise<void>;
|
||||
updateEnrollSecretsManager: (
|
||||
subscribeToSecretsManager: (
|
||||
id: string,
|
||||
request: OrganizationEnrollSecretsManagerRequest
|
||||
) => Promise<void>;
|
||||
request: SecretsManagerSubscribeRequest
|
||||
) => Promise<ProfileOrganizationResponse>;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ export function canAccessSettingsTab(org: Organization): boolean {
|
||||
org.canManagePolicies ||
|
||||
org.canManageSso ||
|
||||
org.canManageScim ||
|
||||
org.canAccessImportExport
|
||||
org.canAccessImportExport ||
|
||||
org.canManageDeviceApprovals
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,6 +57,12 @@ export function canAccessAdmin(i18nService: I18nService) {
|
||||
);
|
||||
}
|
||||
|
||||
export function canAccessImportExport(i18nService: I18nService) {
|
||||
return map<Organization[], Organization[]>((orgs) =>
|
||||
orgs.filter((org) => org.canAccessImportExport).sort(Utils.getSortFunction(i18nService, "name"))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if a user is a member of an organization (rather than only being a ProviderUser)
|
||||
* @deprecated Use organizationService.memberOrganizations$ instead
|
||||
@@ -85,6 +92,7 @@ export abstract class OrganizationService {
|
||||
hasOrganizations: () => boolean;
|
||||
}
|
||||
|
||||
export abstract class InternalOrganizationService extends OrganizationService {
|
||||
export abstract class InternalOrganizationServiceAbstraction extends OrganizationService {
|
||||
replace: (organizations: { [id: string]: OrganizationData }) => Promise<void>;
|
||||
upsert: (OrganizationData: OrganizationData | OrganizationData[]) => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ export class OrganizationData {
|
||||
useCustomPermissions: boolean;
|
||||
useResetPassword: boolean;
|
||||
useSecretsManager: boolean;
|
||||
usePasswordManager: boolean;
|
||||
useActivateAutofillPolicy: boolean;
|
||||
selfHost: boolean;
|
||||
usersGetPremium: boolean;
|
||||
@@ -74,6 +75,7 @@ export class OrganizationData {
|
||||
this.useCustomPermissions = response.useCustomPermissions;
|
||||
this.useResetPassword = response.useResetPassword;
|
||||
this.useSecretsManager = response.useSecretsManager;
|
||||
this.usePasswordManager = response.usePasswordManager;
|
||||
this.useActivateAutofillPolicy = response.useActivateAutofillPolicy;
|
||||
this.selfHost = response.selfHost;
|
||||
this.usersGetPremium = response.usersGetPremium;
|
||||
|
||||
@@ -31,6 +31,7 @@ export class Organization {
|
||||
useCustomPermissions: boolean;
|
||||
useResetPassword: boolean;
|
||||
useSecretsManager: boolean;
|
||||
usePasswordManager: boolean;
|
||||
useActivateAutofillPolicy: boolean;
|
||||
selfHost: boolean;
|
||||
usersGetPremium: boolean;
|
||||
@@ -87,6 +88,7 @@ export class Organization {
|
||||
this.useCustomPermissions = obj.useCustomPermissions;
|
||||
this.useResetPassword = obj.useResetPassword;
|
||||
this.useSecretsManager = obj.useSecretsManager;
|
||||
this.usePasswordManager = obj.usePasswordManager;
|
||||
this.useActivateAutofillPolicy = obj.useActivateAutofillPolicy;
|
||||
this.selfHost = obj.selfHost;
|
||||
this.usersGetPremium = obj.usersGetPremium;
|
||||
@@ -215,6 +217,10 @@ export class Organization {
|
||||
return this.isAdmin || this.permissions.manageResetPassword;
|
||||
}
|
||||
|
||||
get canManageDeviceApprovals() {
|
||||
return (this.isAdmin || this.permissions.manageResetPassword) && this.useSso;
|
||||
}
|
||||
|
||||
get isExemptFromPolicies() {
|
||||
return this.canManagePolicies;
|
||||
}
|
||||
|
||||
@@ -23,4 +23,8 @@ export class OrganizationCreateRequest {
|
||||
billingAddressState: string;
|
||||
billingAddressPostalCode: string;
|
||||
billingAddressCountry: string;
|
||||
|
||||
useSecretsManager: boolean;
|
||||
additionalSmSeats: number;
|
||||
additionalServiceAccounts: number;
|
||||
}
|
||||
|
||||
@@ -11,4 +11,8 @@ export class OrganizationUpgradeRequest {
|
||||
billingAddressCountry: string;
|
||||
billingAddressPostalCode: string;
|
||||
keys: OrganizationKeysRequest;
|
||||
|
||||
useSecretsManager: boolean;
|
||||
additionalSmSeats: number;
|
||||
additionalServiceAccounts: number;
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export class OrganizationEnrollSecretsManagerRequest {
|
||||
enabled: boolean;
|
||||
}
|
||||
@@ -13,6 +13,7 @@ export class OrganizationResponse extends BaseResponse {
|
||||
businessTaxNumber: string;
|
||||
billingEmail: string;
|
||||
plan: PlanResponse;
|
||||
secretsManagerPlan: PlanResponse;
|
||||
planType: PlanType;
|
||||
seats: number;
|
||||
maxAutoscaleSeats: number;
|
||||
@@ -27,6 +28,11 @@ export class OrganizationResponse extends BaseResponse {
|
||||
useResetPassword: boolean;
|
||||
useSecretsManager: boolean;
|
||||
hasPublicAndPrivateKeys: boolean;
|
||||
usePasswordManager: boolean;
|
||||
smSeats?: number;
|
||||
smServiceAccounts?: number;
|
||||
maxAutoscaleSmSeats?: number;
|
||||
maxAutoscaleSmServiceAccounts?: number;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
@@ -39,8 +45,14 @@ export class OrganizationResponse extends BaseResponse {
|
||||
this.businessCountry = this.getResponseProperty("BusinessCountry");
|
||||
this.businessTaxNumber = this.getResponseProperty("BusinessTaxNumber");
|
||||
this.billingEmail = this.getResponseProperty("BillingEmail");
|
||||
|
||||
const plan = this.getResponseProperty("Plan");
|
||||
this.plan = plan == null ? null : new PlanResponse(plan);
|
||||
|
||||
const secretsManagerPlan = this.getResponseProperty("SecretsManagerPlan");
|
||||
this.secretsManagerPlan =
|
||||
secretsManagerPlan == null ? null : new PlanResponse(secretsManagerPlan);
|
||||
|
||||
this.planType = this.getResponseProperty("PlanType");
|
||||
this.seats = this.getResponseProperty("Seats");
|
||||
this.maxAutoscaleSeats = this.getResponseProperty("MaxAutoscaleSeats");
|
||||
@@ -55,5 +67,10 @@ export class OrganizationResponse extends BaseResponse {
|
||||
this.useResetPassword = this.getResponseProperty("UseResetPassword");
|
||||
this.useSecretsManager = this.getResponseProperty("UseSecretsManager");
|
||||
this.hasPublicAndPrivateKeys = this.getResponseProperty("HasPublicAndPrivateKeys");
|
||||
this.usePasswordManager = this.getResponseProperty("UsePasswordManager");
|
||||
this.smSeats = this.getResponseProperty("SmSeats");
|
||||
this.smServiceAccounts = this.getResponseProperty("SmServiceAccounts");
|
||||
this.maxAutoscaleSmSeats = this.getResponseProperty("MaxAutoscaleSmSeats");
|
||||
this.maxAutoscaleSmServiceAccounts = this.getResponseProperty("MaxAutoscaleSmServiceAccounts");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ export class ProfileOrganizationResponse extends BaseResponse {
|
||||
useCustomPermissions: boolean;
|
||||
useResetPassword: boolean;
|
||||
useSecretsManager: boolean;
|
||||
usePasswordManager: boolean;
|
||||
useActivateAutofillPolicy: boolean;
|
||||
selfHost: boolean;
|
||||
usersGetPremium: boolean;
|
||||
@@ -65,6 +66,7 @@ export class ProfileOrganizationResponse extends BaseResponse {
|
||||
this.useCustomPermissions = this.getResponseProperty("UseCustomPermissions") ?? false;
|
||||
this.useResetPassword = this.getResponseProperty("UseResetPassword");
|
||||
this.useSecretsManager = this.getResponseProperty("UseSecretsManager");
|
||||
this.usePasswordManager = this.getResponseProperty("UsePasswordManager");
|
||||
this.useActivateAutofillPolicy = this.getResponseProperty("UseActivateAutofillPolicy");
|
||||
this.selfHost = this.getResponseProperty("SelfHost");
|
||||
this.usersGetPremium = this.getResponseProperty("UsersGetPremium");
|
||||
|
||||
@@ -4,9 +4,11 @@ import { OrganizationSsoRequest } from "../../../auth/models/request/organizatio
|
||||
import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request";
|
||||
import { ApiKeyResponse } from "../../../auth/models/response/api-key.response";
|
||||
import { OrganizationSsoResponse } from "../../../auth/models/response/organization-sso.response";
|
||||
import { OrganizationSmSubscriptionUpdateRequest } from "../../../billing/models/request/organization-sm-subscription-update.request";
|
||||
import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request";
|
||||
import { OrganizationTaxInfoUpdateRequest } from "../../../billing/models/request/organization-tax-info-update.request";
|
||||
import { PaymentRequest } from "../../../billing/models/request/payment.request";
|
||||
import { SecretsManagerSubscribeRequest } from "../../../billing/models/request/sm-subscribe.request";
|
||||
import { BillingResponse } from "../../../billing/models/response/billing.response";
|
||||
import { OrganizationSubscriptionResponse } from "../../../billing/models/response/organization-subscription.response";
|
||||
import { PaymentResponse } from "../../../billing/models/response/payment.response";
|
||||
@@ -19,7 +21,6 @@ import { ListResponse } from "../../../models/response/list.response";
|
||||
import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction";
|
||||
import { OrganizationApiServiceAbstraction } from "../../abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationApiKeyType } from "../../enums";
|
||||
import { OrganizationEnrollSecretsManagerRequest } from "../../models/request/organization/organization-enroll-secrets-manager.request";
|
||||
import { OrganizationCreateRequest } from "../../models/request/organization-create.request";
|
||||
import { OrganizationKeysRequest } from "../../models/request/organization-keys.request";
|
||||
import { OrganizationUpdateRequest } from "../../models/request/organization-update.request";
|
||||
@@ -28,6 +29,7 @@ import { OrganizationApiKeyInformationResponse } from "../../models/response/org
|
||||
import { OrganizationAutoEnrollStatusResponse } from "../../models/response/organization-auto-enroll-status.response";
|
||||
import { OrganizationKeysResponse } from "../../models/response/organization-keys.response";
|
||||
import { OrganizationResponse } from "../../models/response/organization.response";
|
||||
import { ProfileOrganizationResponse } from "../../models/response/profile-organization.response";
|
||||
|
||||
export class OrganizationApiService implements OrganizationApiServiceAbstraction {
|
||||
constructor(private apiService: ApiService, private syncService: SyncService) {}
|
||||
@@ -120,7 +122,7 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction
|
||||
return new PaymentResponse(r);
|
||||
}
|
||||
|
||||
async updateSubscription(
|
||||
async updatePasswordManagerSeats(
|
||||
id: string,
|
||||
request: OrganizationSubscriptionUpdateRequest
|
||||
): Promise<void> {
|
||||
@@ -133,6 +135,19 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction
|
||||
);
|
||||
}
|
||||
|
||||
async updateSecretsManagerSubscription(
|
||||
id: string,
|
||||
request: OrganizationSmSubscriptionUpdateRequest
|
||||
): Promise<void> {
|
||||
return this.apiService.send(
|
||||
"POST",
|
||||
"/organizations/" + id + "/sm-subscription",
|
||||
request,
|
||||
true,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
async updateSeats(id: string, request: SeatRequest): Promise<PaymentResponse> {
|
||||
const r = await this.apiService.send(
|
||||
"POST",
|
||||
@@ -294,13 +309,17 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction
|
||||
);
|
||||
}
|
||||
|
||||
async updateEnrollSecretsManager(id: string, request: OrganizationEnrollSecretsManagerRequest) {
|
||||
await this.apiService.send(
|
||||
async subscribeToSecretsManager(
|
||||
id: string,
|
||||
request: SecretsManagerSubscribeRequest
|
||||
): Promise<ProfileOrganizationResponse> {
|
||||
const r = await this.apiService.send(
|
||||
"POST",
|
||||
"/organizations/" + id + "/enroll-secrets-manager",
|
||||
"/organizations/" + id + "/subscribe-secrets-manager",
|
||||
request,
|
||||
true,
|
||||
true
|
||||
);
|
||||
return new ProfileOrganizationResponse(r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { BehaviorSubject, concatMap, map, Observable } from "rxjs";
|
||||
|
||||
import { StateService } from "../../../platform/abstractions/state.service";
|
||||
import {
|
||||
InternalOrganizationService as InternalOrganizationServiceAbstraction,
|
||||
InternalOrganizationServiceAbstraction,
|
||||
isMember,
|
||||
} from "../../abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationData } from "../../models/data/organization.data";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { VerifyOTPRequest } from "../../auth/models/request/verify-otp.request";
|
||||
import { VerifyOTPRequest } from "../../models/request/verify-otp.request";
|
||||
|
||||
export abstract class UserVerificationApiServiceAbstraction {
|
||||
postAccountVerifyOTP: (request: VerifyOTPRequest) => Promise<void>;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SecretVerificationRequest } from "../../auth/models/request/secret-verification.request";
|
||||
import { Verification } from "../../types/verification";
|
||||
import { Verification } from "../../../types/verification";
|
||||
import { SecretVerificationRequest } from "../../models/request/secret-verification.request";
|
||||
|
||||
export abstract class UserVerificationService {
|
||||
buildRequest: <T extends SecretVerificationRequest>(
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { UserVerificationService } from "../../abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { LogService } from "../../platform/abstractions/log.service";
|
||||
import { Verification } from "../../types/verification";
|
||||
import { AccountApiService } from "../abstractions/account-api.service";
|
||||
import { InternalAccountService } from "../abstractions/account.service";
|
||||
import { UserVerificationService } from "../abstractions/user-verification/user-verification.service.abstraction";
|
||||
|
||||
export class AccountApiServiceImplementation implements AccountApiService {
|
||||
constructor(
|
||||
|
||||
@@ -302,15 +302,18 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
(
|
||||
await this.cryptoService.getKey()
|
||||
).encKey,
|
||||
pubKey.buffer
|
||||
);
|
||||
const encryptedMasterPassword = await this.cryptoService.rsaEncrypt(
|
||||
Utils.fromUtf8ToArray(await this.stateService.getKeyHash()),
|
||||
pubKey.buffer
|
||||
pubKey
|
||||
);
|
||||
let encryptedMasterPassword = null;
|
||||
if ((await this.stateService.getKeyHash()) != null) {
|
||||
encryptedMasterPassword = await this.cryptoService.rsaEncrypt(
|
||||
Utils.fromUtf8ToArray(await this.stateService.getKeyHash()),
|
||||
pubKey
|
||||
);
|
||||
}
|
||||
const request = new PasswordlessAuthRequest(
|
||||
encryptedKey.encryptedString,
|
||||
encryptedMasterPassword.encryptedString,
|
||||
encryptedMasterPassword?.encryptedString,
|
||||
await this.appIdService.getAppId(),
|
||||
requestApproved
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ApiService } from "../../../abstractions/api.service";
|
||||
import { UserVerificationApiServiceAbstraction } from "../../../abstractions/userVerification/userVerification-api.service.abstraction";
|
||||
import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction";
|
||||
import { VerifyOTPRequest } from "../../models/request/verify-otp.request";
|
||||
|
||||
export class UserVerificationApiService implements UserVerificationApiServiceAbstraction {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { UserVerificationApiServiceAbstraction } from "../../../abstractions/userVerification/userVerification-api.service.abstraction";
|
||||
import { UserVerificationService as UserVerificationServiceAbstraction } from "../../../abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { CryptoService } from "../../../platform/abstractions/crypto.service";
|
||||
import { I18nService } from "../../../platform/abstractions/i18n.service";
|
||||
import { Verification } from "../../../types/verification";
|
||||
import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction";
|
||||
import { UserVerificationService as UserVerificationServiceAbstraction } from "../../abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { VerificationType } from "../../enums/verification-type";
|
||||
import { SecretVerificationRequest } from "../../models/request/secret-verification.request";
|
||||
import { VerifyOTPRequest } from "../../models/request/verify-otp.request";
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum BitwardenProductType {
|
||||
PasswordManager = 0,
|
||||
SecretsManager = 1,
|
||||
}
|
||||
@@ -2,3 +2,4 @@ export * from "./payment-method-type.enum";
|
||||
export * from "./plan-sponsorship-type.enum";
|
||||
export * from "./plan-type.enum";
|
||||
export * from "./transaction-type.enum";
|
||||
export * from "./bitwarden-product-type.enum";
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
export class OrganizationSmSubscriptionUpdateRequest {
|
||||
/**
|
||||
* The number of seats to add or remove from the subscription.
|
||||
*/
|
||||
seatAdjustment: number;
|
||||
|
||||
/**
|
||||
* The maximum number of seats that can be auto-scaled for the subscription.
|
||||
*/
|
||||
maxAutoscaleSeats?: number;
|
||||
|
||||
/**
|
||||
* The number of additional service accounts to add or remove from the subscription.
|
||||
*/
|
||||
serviceAccountAdjustment: number;
|
||||
|
||||
/**
|
||||
* The maximum number of additional service accounts that can be auto-scaled for the subscription.
|
||||
*/
|
||||
maxAutoscaleServiceAccounts?: number;
|
||||
}
|
||||
@@ -1,3 +1,23 @@
|
||||
export class OrganizationSubscriptionUpdateRequest {
|
||||
constructor(public seatAdjustment: number, public maxAutoscaleSeats?: number) {}
|
||||
/**
|
||||
* The number of seats to add or remove from the subscription.
|
||||
* Applies to both PM and SM request types.
|
||||
*/
|
||||
seatAdjustment: number;
|
||||
|
||||
/**
|
||||
* The maximum number of seats that can be auto-scaled for the subscription.
|
||||
* Applies to both PM and SM request types.
|
||||
*/
|
||||
maxAutoscaleSeats?: number;
|
||||
|
||||
/**
|
||||
* Build a subscription update request for the Password Manager product type.
|
||||
* @param seatAdjustment - The number of seats to add or remove from the subscription.
|
||||
* @param maxAutoscaleSeats - The maximum number of seats that can be auto-scaled for the subscription.
|
||||
*/
|
||||
constructor(seatAdjustment: number, maxAutoscaleSeats?: number) {
|
||||
this.seatAdjustment = seatAdjustment;
|
||||
this.maxAutoscaleSeats = maxAutoscaleSeats;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export class SecretsManagerSubscribeRequest {
|
||||
additionalSmSeats: number;
|
||||
additionalServiceAccounts: number;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ export class OrganizationSubscriptionResponse extends OrganizationResponse {
|
||||
upcomingInvoice: BillingSubscriptionUpcomingInvoiceResponse;
|
||||
expiration: string;
|
||||
expirationWithoutGracePeriod: string;
|
||||
secretsManagerBeta: boolean;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
@@ -26,5 +27,6 @@ export class OrganizationSubscriptionResponse extends OrganizationResponse {
|
||||
: new BillingSubscriptionUpcomingInvoiceResponse(upcomingInvoice);
|
||||
this.expiration = this.getResponseProperty("Expiration");
|
||||
this.expirationWithoutGracePeriod = this.getResponseProperty("ExpirationWithoutGracePeriod");
|
||||
this.secretsManagerBeta = this.getResponseProperty("SecretsManagerBeta");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { ProductType } from "../../../enums";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { PlanType } from "../../enums";
|
||||
import { BitwardenProductType, PlanType } from "../../enums";
|
||||
|
||||
export class PlanResponse extends BaseResponse {
|
||||
type: PlanType;
|
||||
product: ProductType;
|
||||
bitwardenProduct: BitwardenProductType;
|
||||
name: string;
|
||||
isAnnual: boolean;
|
||||
nameLocalizationKey: string;
|
||||
@@ -48,6 +49,15 @@ export class PlanResponse extends BaseResponse {
|
||||
additionalStoragePricePerGb: number;
|
||||
premiumAccessOptionPrice: number;
|
||||
|
||||
// SM only
|
||||
additionalPricePerServiceAccount: number;
|
||||
baseServiceAccount: number;
|
||||
maxServiceAccount: number;
|
||||
hasAdditionalServiceAccountOption: boolean;
|
||||
maxProjects: number;
|
||||
maxAdditionalServiceAccounts: number;
|
||||
stripeServiceAccountPlanId: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.type = this.getResponseProperty("Type");
|
||||
@@ -90,5 +100,18 @@ export class PlanResponse extends BaseResponse {
|
||||
this.seatPrice = this.getResponseProperty("SeatPrice");
|
||||
this.additionalStoragePricePerGb = this.getResponseProperty("AdditionalStoragePricePerGb");
|
||||
this.premiumAccessOptionPrice = this.getResponseProperty("PremiumAccessOptionPrice");
|
||||
|
||||
this.bitwardenProduct = this.getResponseProperty("BitwardenProduct");
|
||||
this.additionalPricePerServiceAccount = this.getResponseProperty(
|
||||
"AdditionalPricePerServiceAccount"
|
||||
);
|
||||
this.baseServiceAccount = this.getResponseProperty("BaseServiceAccount");
|
||||
this.maxServiceAccount = this.getResponseProperty("MaxServiceAccount");
|
||||
this.hasAdditionalServiceAccountOption = this.getResponseProperty(
|
||||
"HasAdditionalServiceAccountOption"
|
||||
);
|
||||
this.maxProjects = this.getResponseProperty("MaxProjects");
|
||||
this.maxAdditionalServiceAccounts = this.getResponseProperty("MaxAdditionalServiceAccounts");
|
||||
this.stripeServiceAccountPlanId = this.getResponseProperty("StripeServiceAccountPlanId");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { BitwardenProductType } from "../../enums";
|
||||
|
||||
export class SubscriptionResponse extends BaseResponse {
|
||||
storageName: string;
|
||||
@@ -62,6 +63,8 @@ export class BillingSubscriptionItemResponse extends BaseResponse {
|
||||
quantity: number;
|
||||
interval: string;
|
||||
sponsoredSubscriptionItem: boolean;
|
||||
addonSubscriptionItem: boolean;
|
||||
bitwardenProduct: BitwardenProductType;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
@@ -70,6 +73,8 @@ export class BillingSubscriptionItemResponse extends BaseResponse {
|
||||
this.quantity = this.getResponseProperty("Quantity");
|
||||
this.interval = this.getResponseProperty("Interval");
|
||||
this.sponsoredSubscriptionItem = this.getResponseProperty("SponsoredSubscriptionItem");
|
||||
this.addonSubscriptionItem = this.getResponseProperty("AddonSubscriptionItem");
|
||||
this.bitwardenProduct = this.getResponseProperty("BitwardenProduct");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,4 +3,5 @@ export enum FeatureFlag {
|
||||
DisplayLowKdfIterationWarningFlag = "display-kdf-iteration-warning",
|
||||
Fido2VaultCredentials = "fido2-vault-credentials",
|
||||
TrustedDeviceEncryption = "trusted-device-encryption",
|
||||
SecretsManagerBilling = "sm-ga-billing",
|
||||
}
|
||||
|
||||
@@ -10,4 +10,5 @@ export abstract class ConfigServiceAbstraction {
|
||||
getFeatureFlagBool: (key: FeatureFlag, defaultValue?: boolean) => Promise<boolean>;
|
||||
getFeatureFlagString: (key: FeatureFlag, defaultValue?: string) => Promise<string>;
|
||||
getFeatureFlagNumber: (key: FeatureFlag, defaultValue?: number) => Promise<number>;
|
||||
getCloudRegion: (defaultValue?: string) => Promise<string>;
|
||||
}
|
||||
|
||||
@@ -4,67 +4,67 @@ import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
||||
|
||||
export abstract class CryptoFunctionService {
|
||||
pbkdf2: (
|
||||
password: string | ArrayBuffer,
|
||||
salt: string | ArrayBuffer,
|
||||
password: string | Uint8Array,
|
||||
salt: string | Uint8Array,
|
||||
algorithm: "sha256" | "sha512",
|
||||
iterations: number
|
||||
) => Promise<ArrayBuffer>;
|
||||
) => Promise<Uint8Array>;
|
||||
argon2: (
|
||||
password: string | ArrayBuffer,
|
||||
salt: string | ArrayBuffer,
|
||||
password: string | Uint8Array,
|
||||
salt: string | Uint8Array,
|
||||
iterations: number,
|
||||
memory: number,
|
||||
parallelism: number
|
||||
) => Promise<ArrayBuffer>;
|
||||
) => Promise<Uint8Array>;
|
||||
hkdf: (
|
||||
ikm: ArrayBuffer,
|
||||
salt: string | ArrayBuffer,
|
||||
info: string | ArrayBuffer,
|
||||
ikm: Uint8Array,
|
||||
salt: string | Uint8Array,
|
||||
info: string | Uint8Array,
|
||||
outputByteSize: number,
|
||||
algorithm: "sha256" | "sha512"
|
||||
) => Promise<ArrayBuffer>;
|
||||
) => Promise<Uint8Array>;
|
||||
hkdfExpand: (
|
||||
prk: ArrayBuffer,
|
||||
info: string | ArrayBuffer,
|
||||
prk: Uint8Array,
|
||||
info: string | Uint8Array,
|
||||
outputByteSize: number,
|
||||
algorithm: "sha256" | "sha512"
|
||||
) => Promise<ArrayBuffer>;
|
||||
) => Promise<Uint8Array>;
|
||||
hash: (
|
||||
value: string | ArrayBuffer,
|
||||
value: string | Uint8Array,
|
||||
algorithm: "sha1" | "sha256" | "sha512" | "md5"
|
||||
) => Promise<ArrayBuffer>;
|
||||
) => Promise<Uint8Array>;
|
||||
hmac: (
|
||||
value: ArrayBuffer,
|
||||
key: ArrayBuffer,
|
||||
value: Uint8Array,
|
||||
key: Uint8Array,
|
||||
algorithm: "sha1" | "sha256" | "sha512"
|
||||
) => Promise<ArrayBuffer>;
|
||||
compare: (a: ArrayBuffer, b: ArrayBuffer) => Promise<boolean>;
|
||||
) => Promise<Uint8Array>;
|
||||
compare: (a: Uint8Array, b: Uint8Array) => Promise<boolean>;
|
||||
hmacFast: (
|
||||
value: ArrayBuffer | string,
|
||||
key: ArrayBuffer | string,
|
||||
value: Uint8Array | string,
|
||||
key: Uint8Array | string,
|
||||
algorithm: "sha1" | "sha256" | "sha512"
|
||||
) => Promise<ArrayBuffer | string>;
|
||||
compareFast: (a: ArrayBuffer | string, b: ArrayBuffer | string) => Promise<boolean>;
|
||||
aesEncrypt: (data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer) => Promise<ArrayBuffer>;
|
||||
) => Promise<Uint8Array | string>;
|
||||
compareFast: (a: Uint8Array | string, b: Uint8Array | string) => Promise<boolean>;
|
||||
aesEncrypt: (data: Uint8Array, iv: Uint8Array, key: Uint8Array) => Promise<Uint8Array>;
|
||||
aesDecryptFastParameters: (
|
||||
data: string,
|
||||
iv: string,
|
||||
mac: string,
|
||||
key: SymmetricCryptoKey
|
||||
) => DecryptParameters<ArrayBuffer | string>;
|
||||
aesDecryptFast: (parameters: DecryptParameters<ArrayBuffer | string>) => Promise<string>;
|
||||
aesDecrypt: (data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer) => Promise<ArrayBuffer>;
|
||||
) => DecryptParameters<Uint8Array | string>;
|
||||
aesDecryptFast: (parameters: DecryptParameters<Uint8Array | string>) => Promise<string>;
|
||||
aesDecrypt: (data: Uint8Array, iv: Uint8Array, key: Uint8Array) => Promise<Uint8Array>;
|
||||
rsaEncrypt: (
|
||||
data: ArrayBuffer,
|
||||
publicKey: ArrayBuffer,
|
||||
data: Uint8Array,
|
||||
publicKey: Uint8Array,
|
||||
algorithm: "sha1" | "sha256"
|
||||
) => Promise<ArrayBuffer>;
|
||||
) => Promise<Uint8Array>;
|
||||
rsaDecrypt: (
|
||||
data: ArrayBuffer,
|
||||
privateKey: ArrayBuffer,
|
||||
data: Uint8Array,
|
||||
privateKey: Uint8Array,
|
||||
algorithm: "sha1" | "sha256"
|
||||
) => Promise<ArrayBuffer>;
|
||||
rsaExtractPublicKey: (privateKey: ArrayBuffer) => Promise<ArrayBuffer>;
|
||||
rsaGenerateKeyPair: (length: 1024 | 2048 | 4096) => Promise<[ArrayBuffer, ArrayBuffer]>;
|
||||
) => Promise<Uint8Array>;
|
||||
rsaExtractPublicKey: (privateKey: Uint8Array) => Promise<Uint8Array>;
|
||||
rsaGenerateKeyPair: (length: 1024 | 2048 | 4096) => Promise<[Uint8Array, Uint8Array]>;
|
||||
randomBytes: (length: number) => Promise<CsprngArray>;
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ export abstract class CryptoService {
|
||||
getKeyHash: () => Promise<string>;
|
||||
compareAndUpdateKeyHash: (masterPassword: string, key: SymmetricCryptoKey) => Promise<boolean>;
|
||||
getEncKey: (key?: SymmetricCryptoKey) => Promise<SymmetricCryptoKey>;
|
||||
getPublicKey: () => Promise<ArrayBuffer>;
|
||||
getPrivateKey: () => Promise<ArrayBuffer>;
|
||||
getFingerprint: (userId: string, publicKey?: ArrayBuffer) => Promise<string[]>;
|
||||
getPublicKey: () => Promise<Uint8Array>;
|
||||
getPrivateKey: () => Promise<Uint8Array>;
|
||||
getFingerprint: (fingerprintMaterial: string, publicKey?: Uint8Array) => Promise<string[]>;
|
||||
getOrgKeys: () => Promise<Map<string, SymmetricCryptoKey>>;
|
||||
getOrgKey: (orgId: string) => Promise<SymmetricCryptoKey>;
|
||||
getProviderKey: (providerId: string) => Promise<SymmetricCryptoKey>;
|
||||
@@ -63,7 +63,7 @@ export abstract class CryptoService {
|
||||
kdf: KdfType,
|
||||
kdfConfig: KdfConfig
|
||||
) => Promise<SymmetricCryptoKey>;
|
||||
makeSendKey: (keyMaterial: ArrayBuffer) => Promise<SymmetricCryptoKey>;
|
||||
makeSendKey: (keyMaterial: Uint8Array) => Promise<SymmetricCryptoKey>;
|
||||
hashPassword: (
|
||||
password: string,
|
||||
key: SymmetricCryptoKey,
|
||||
@@ -74,13 +74,13 @@ export abstract class CryptoService {
|
||||
key: SymmetricCryptoKey,
|
||||
encKey?: SymmetricCryptoKey
|
||||
) => Promise<[SymmetricCryptoKey, EncString]>;
|
||||
encrypt: (plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey) => Promise<EncString>;
|
||||
encryptToBytes: (plainValue: ArrayBuffer, key?: SymmetricCryptoKey) => Promise<EncArrayBuffer>;
|
||||
rsaEncrypt: (data: ArrayBuffer, publicKey?: ArrayBuffer) => Promise<EncString>;
|
||||
rsaDecrypt: (encValue: string, privateKeyValue?: ArrayBuffer) => Promise<ArrayBuffer>;
|
||||
decryptToBytes: (encString: EncString, key?: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
||||
encrypt: (plainValue: string | Uint8Array, key?: SymmetricCryptoKey) => Promise<EncString>;
|
||||
encryptToBytes: (plainValue: Uint8Array, key?: SymmetricCryptoKey) => Promise<EncArrayBuffer>;
|
||||
rsaEncrypt: (data: Uint8Array, publicKey?: Uint8Array) => Promise<EncString>;
|
||||
rsaDecrypt: (encValue: string, privateKeyValue?: Uint8Array) => Promise<Uint8Array>;
|
||||
decryptToBytes: (encString: EncString, key?: SymmetricCryptoKey) => Promise<Uint8Array>;
|
||||
decryptToUtf8: (encString: EncString, key?: SymmetricCryptoKey) => Promise<string>;
|
||||
decryptFromBytes: (encBuffer: EncArrayBuffer, key: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
||||
decryptFromBytes: (encBuffer: EncArrayBuffer, key: SymmetricCryptoKey) => Promise<Uint8Array>;
|
||||
randomNumber: (min: number, max: number) => Promise<number>;
|
||||
validateKey: (key: SymmetricCryptoKey) => Promise<boolean>;
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ import { EncString } from "../models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
||||
|
||||
export abstract class EncryptService {
|
||||
abstract encrypt(plainValue: string | ArrayBuffer, key: SymmetricCryptoKey): Promise<EncString>;
|
||||
abstract encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise<EncString>;
|
||||
abstract encryptToBytes: (
|
||||
plainValue: ArrayBuffer,
|
||||
plainValue: Uint8Array,
|
||||
key?: SymmetricCryptoKey
|
||||
) => Promise<EncArrayBuffer>;
|
||||
abstract decryptToUtf8: (encString: EncString, key: SymmetricCryptoKey) => Promise<string>;
|
||||
abstract decryptToBytes: (encThing: Encrypted, key: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
||||
abstract decryptToBytes: (encThing: Encrypted, key: SymmetricCryptoKey) => Promise<Uint8Array>;
|
||||
abstract resolveLegacyKey: (key: SymmetricCryptoKey, encThing: Encrypted) => SymmetricCryptoKey;
|
||||
abstract decryptItems: <T extends InitializerMetadata>(
|
||||
items: Decryptable<T>[],
|
||||
|
||||
@@ -17,12 +17,41 @@ export type PayPalConfig = {
|
||||
buttonAction?: string;
|
||||
};
|
||||
|
||||
export enum Region {
|
||||
US = "US",
|
||||
EU = "EU",
|
||||
SelfHosted = "Self-hosted",
|
||||
}
|
||||
|
||||
export enum RegionDomain {
|
||||
US = "bitwarden.com",
|
||||
EU = "bitwarden.eu",
|
||||
USQA = "bitwarden.pw",
|
||||
}
|
||||
|
||||
export abstract class EnvironmentService {
|
||||
urls: Observable<void>;
|
||||
usUrls: Urls;
|
||||
euUrls: Urls;
|
||||
selectedRegion?: Region;
|
||||
initialized = true;
|
||||
|
||||
hasBaseUrl: () => boolean;
|
||||
getNotificationsUrl: () => string;
|
||||
getWebVaultUrl: () => string;
|
||||
/**
|
||||
* Retrieves the URL of the cloud web vault app.
|
||||
*
|
||||
* @returns {string} The URL of the cloud web vault app.
|
||||
* @remarks Use this method only in views exclusive to self-host instances.
|
||||
*/
|
||||
getCloudWebVaultUrl: () => string;
|
||||
/**
|
||||
* Sets the URL of the cloud web vault app based on the region parameter.
|
||||
*
|
||||
* @param {Region} region - The region of the cloud web vault app.
|
||||
*/
|
||||
setCloudWebVaultUrl: (region: Region) => void;
|
||||
getSendUrl: () => string;
|
||||
getIconsUrl: () => string;
|
||||
getApiUrl: () => string;
|
||||
@@ -32,11 +61,8 @@ export abstract class EnvironmentService {
|
||||
getScimUrl: () => string;
|
||||
setUrlsFromStorage: () => Promise<void>;
|
||||
setUrls: (urls: Urls) => Promise<Urls>;
|
||||
setRegion: (region: Region) => Promise<void>;
|
||||
getUrls: () => Urls;
|
||||
isCloud: () => boolean;
|
||||
/**
|
||||
* @remarks For desktop and browser use only.
|
||||
* For web, use PlatformUtilsService.isSelfHost()
|
||||
*/
|
||||
isSelfHosted: () => boolean;
|
||||
isEmpty: () => boolean;
|
||||
}
|
||||
|
||||
@@ -113,8 +113,8 @@ export abstract class StateService<T extends Account = Account> {
|
||||
* @deprecated Do not call this, use PolicyService
|
||||
*/
|
||||
setDecryptedPolicies: (value: Policy[], options?: StorageOptions) => Promise<void>;
|
||||
getDecryptedPrivateKey: (options?: StorageOptions) => Promise<ArrayBuffer>;
|
||||
setDecryptedPrivateKey: (value: ArrayBuffer, options?: StorageOptions) => Promise<void>;
|
||||
getDecryptedPrivateKey: (options?: StorageOptions) => Promise<Uint8Array>;
|
||||
setDecryptedPrivateKey: (value: Uint8Array, options?: StorageOptions) => Promise<void>;
|
||||
getDecryptedProviderKeys: (options?: StorageOptions) => Promise<Map<string, SymmetricCryptoKey>>;
|
||||
setDecryptedProviderKeys: (
|
||||
value: Map<string, SymmetricCryptoKey>,
|
||||
@@ -263,6 +263,8 @@ export abstract class StateService<T extends Account = Account> {
|
||||
setEntityType: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getEnvironmentUrls: (options?: StorageOptions) => Promise<EnvironmentUrls>;
|
||||
setEnvironmentUrls: (value: EnvironmentUrls, options?: StorageOptions) => Promise<void>;
|
||||
getRegion: (options?: StorageOptions) => Promise<string>;
|
||||
setRegion: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getEquivalentDomains: (options?: StorageOptions) => Promise<string[][]>;
|
||||
setEquivalentDomains: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getEventCollection: (options?: StorageOptions) => Promise<EventData[]>;
|
||||
@@ -329,8 +331,8 @@ export abstract class StateService<T extends Account = Account> {
|
||||
setProtectedPin: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getProviders: (options?: StorageOptions) => Promise<{ [id: string]: ProviderData }>;
|
||||
setProviders: (value: { [id: string]: ProviderData }, options?: StorageOptions) => Promise<void>;
|
||||
getPublicKey: (options?: StorageOptions) => Promise<ArrayBuffer>;
|
||||
setPublicKey: (value: ArrayBuffer, options?: StorageOptions) => Promise<void>;
|
||||
getPublicKey: (options?: StorageOptions) => Promise<Uint8Array>;
|
||||
setPublicKey: (value: Uint8Array, options?: StorageOptions) => Promise<void>;
|
||||
getRefreshToken: (options?: StorageOptions) => Promise<string>;
|
||||
setRefreshToken: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getRememberedEmail: (options?: StorageOptions) => Promise<string>;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { EncryptionType } from "../../enums";
|
||||
|
||||
export interface Encrypted {
|
||||
encryptionType?: EncryptionType;
|
||||
dataBytes: ArrayBuffer;
|
||||
macBytes: ArrayBuffer;
|
||||
ivBytes: ArrayBuffer;
|
||||
dataBytes: Uint8Array;
|
||||
macBytes: Uint8Array;
|
||||
ivBytes: Uint8Array;
|
||||
}
|
||||
|
||||
@@ -358,4 +358,32 @@ describe("Utils Service", () => {
|
||||
expect(actual.protocol).toBe("http:");
|
||||
});
|
||||
});
|
||||
|
||||
describe("daysRemaining", () => {
|
||||
beforeAll(() => {
|
||||
const now = new Date(2023, 9, 2, 10);
|
||||
jest.spyOn(Date, "now").mockReturnValue(now.getTime());
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("should return 0 for equal dates", () => {
|
||||
expect(Utils.daysRemaining(new Date(2023, 9, 2))).toBe(0);
|
||||
expect(Utils.daysRemaining(new Date(2023, 9, 2, 12))).toBe(0);
|
||||
});
|
||||
|
||||
it("should return 0 for dates in the past", () => {
|
||||
expect(Utils.daysRemaining(new Date(2020, 5, 11))).toBe(0);
|
||||
expect(Utils.daysRemaining(new Date(2023, 9, 1))).toBe(0);
|
||||
});
|
||||
|
||||
it("should handle future dates", () => {
|
||||
expect(Utils.daysRemaining(new Date(2023, 9, 3, 10))).toBe(1);
|
||||
expect(Utils.daysRemaining(new Date(2023, 10, 12, 10))).toBe(41);
|
||||
// leap year
|
||||
expect(Utils.daysRemaining(new Date(2024, 9, 2, 10))).toBe(366);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -545,6 +545,16 @@ export class Utils {
|
||||
return of(undefined).pipe(switchMap(() => generator()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of days remaining before a target date arrives.
|
||||
* Returns 0 if the day has already passed.
|
||||
*/
|
||||
static daysRemaining(targetDate: Date): number {
|
||||
const diffTime = targetDate.getTime() - Date.now();
|
||||
const msPerDay = 86400000;
|
||||
return Math.max(0, Math.floor(diffTime / msPerDay));
|
||||
}
|
||||
|
||||
private static isAppleMobile(win: Window) {
|
||||
return (
|
||||
win.navigator.userAgent.match(/iPhone/i) != null ||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Region } from "../../abstractions/environment.service";
|
||||
|
||||
import {
|
||||
EnvironmentServerConfigData,
|
||||
ServerConfigData,
|
||||
@@ -15,6 +17,7 @@ describe("ServerConfigData", () => {
|
||||
url: "https://test.com",
|
||||
},
|
||||
environment: {
|
||||
cloudRegion: Region.EU,
|
||||
vault: "https://vault.com",
|
||||
api: "https://api.com",
|
||||
identity: "https://identity.com",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { Region } from "../../abstractions/environment.service";
|
||||
import {
|
||||
ServerConfigResponse,
|
||||
ThirdPartyServerConfigResponse,
|
||||
@@ -50,6 +51,7 @@ export class ThirdPartyServerConfigData {
|
||||
}
|
||||
|
||||
export class EnvironmentServerConfigData {
|
||||
cloudRegion: Region;
|
||||
vault: string;
|
||||
api: string;
|
||||
identity: string;
|
||||
@@ -57,6 +59,7 @@ export class EnvironmentServerConfigData {
|
||||
sso: string;
|
||||
|
||||
constructor(response: Partial<EnvironmentServerConfigResponse>) {
|
||||
this.cloudRegion = response.cloudRegion;
|
||||
this.vault = response.vault;
|
||||
this.api = response.api;
|
||||
this.identity = response.identity;
|
||||
|
||||
@@ -8,7 +8,7 @@ describe("AccountKeys", () => {
|
||||
describe("toJSON", () => {
|
||||
it("should serialize itself", () => {
|
||||
const keys = new AccountKeys();
|
||||
const buffer = makeStaticByteArray(64).buffer;
|
||||
const buffer = makeStaticByteArray(64);
|
||||
keys.publicKey = buffer;
|
||||
|
||||
const bufferSpy = jest.spyOn(Utils, "fromBufferToByteString");
|
||||
@@ -18,7 +18,7 @@ describe("AccountKeys", () => {
|
||||
|
||||
it("should serialize public key as a string", () => {
|
||||
const keys = new AccountKeys();
|
||||
keys.publicKey = Utils.fromByteStringToArray("hello").buffer;
|
||||
keys.publicKey = Utils.fromByteStringToArray("hello");
|
||||
const json = JSON.stringify(keys);
|
||||
expect(json).toContain('"publicKey":"hello"');
|
||||
});
|
||||
@@ -29,7 +29,7 @@ describe("AccountKeys", () => {
|
||||
const keys = AccountKeys.fromJSON({
|
||||
publicKey: "hello",
|
||||
});
|
||||
expect(keys.publicKey).toEqual(Utils.fromByteStringToArray("hello").buffer);
|
||||
expect(keys.publicKey).toEqual(Utils.fromByteStringToArray("hello"));
|
||||
});
|
||||
|
||||
it("should deserialize cryptoMasterKey", () => {
|
||||
|
||||
@@ -119,8 +119,8 @@ export class AccountKeys {
|
||||
any,
|
||||
Record<string, SymmetricCryptoKey>
|
||||
>();
|
||||
privateKey?: EncryptionPair<string, ArrayBuffer> = new EncryptionPair<string, ArrayBuffer>();
|
||||
publicKey?: ArrayBuffer;
|
||||
privateKey?: EncryptionPair<string, Uint8Array> = new EncryptionPair<string, Uint8Array>();
|
||||
publicKey?: Uint8Array;
|
||||
apiKeyClientSecret?: string;
|
||||
|
||||
toJSON() {
|
||||
@@ -142,11 +142,10 @@ export class AccountKeys {
|
||||
),
|
||||
organizationKeys: AccountKeys.initRecordEncryptionPairsFromJSON(obj?.organizationKeys),
|
||||
providerKeys: AccountKeys.initRecordEncryptionPairsFromJSON(obj?.providerKeys),
|
||||
privateKey: EncryptionPair.fromJSON<string, ArrayBuffer>(
|
||||
obj?.privateKey,
|
||||
(decObj: string) => Utils.fromByteStringToArray(decObj).buffer
|
||||
privateKey: EncryptionPair.fromJSON<string, Uint8Array>(obj?.privateKey, (decObj: string) =>
|
||||
Utils.fromByteStringToArray(decObj)
|
||||
),
|
||||
publicKey: Utils.fromByteStringToArray(obj?.publicKey)?.buffer,
|
||||
publicKey: Utils.fromByteStringToArray(obj?.publicKey),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -233,6 +232,7 @@ export class AccountSettings {
|
||||
approveLoginRequests?: boolean;
|
||||
avatarColor?: string;
|
||||
activateAutoFillOnPageLoadFromPolicy?: boolean;
|
||||
region?: string;
|
||||
smOnboardingTasks?: Record<string, Record<string, boolean>>;
|
||||
|
||||
static fromJSON(obj: Jsonify<AccountSettings>): AccountSettings {
|
||||
|
||||
@@ -20,7 +20,7 @@ describe("encArrayBuffer", () => {
|
||||
array.set(mac, 1 + iv.byteLength);
|
||||
array.set(data, 1 + iv.byteLength + mac.byteLength);
|
||||
|
||||
const actual = new EncArrayBuffer(array.buffer);
|
||||
const actual = new EncArrayBuffer(array);
|
||||
|
||||
expect(actual.encryptionType).toEqual(encType);
|
||||
expect(actual.ivBytes).toEqualBuffer(iv);
|
||||
@@ -39,11 +39,11 @@ describe("encArrayBuffer", () => {
|
||||
array.set(iv, 1);
|
||||
array.set(data, 1 + iv.byteLength);
|
||||
|
||||
const actual = new EncArrayBuffer(array.buffer);
|
||||
const actual = new EncArrayBuffer(array);
|
||||
|
||||
expect(actual.encryptionType).toEqual(encType);
|
||||
expect(actual.ivBytes).toEqualBuffer(iv);
|
||||
expect(actual.dataBytes).toEqualBuffer(data);
|
||||
expect(actual.ivBytes).toEqual(iv);
|
||||
expect(actual.dataBytes).toEqual(data);
|
||||
expect(actual.macBytes).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -58,13 +58,11 @@ describe("encArrayBuffer", () => {
|
||||
// Minus 1 to leave room for the encType, minus 1 to make it invalid
|
||||
const invalidBytes = makeStaticByteArray(minLength - 2);
|
||||
|
||||
const invalidArray = new Uint8Array(1 + invalidBytes.buffer.byteLength);
|
||||
const invalidArray = new Uint8Array(1 + invalidBytes.byteLength);
|
||||
invalidArray.set([encType]);
|
||||
invalidArray.set(invalidBytes, 1);
|
||||
|
||||
expect(() => new EncArrayBuffer(invalidArray.buffer)).toThrow(
|
||||
"Error parsing encrypted ArrayBuffer"
|
||||
);
|
||||
expect(() => new EncArrayBuffer(invalidArray)).toThrow("Error parsing encrypted ArrayBuffer");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@ const MIN_DATA_LENGTH = 1;
|
||||
|
||||
export class EncArrayBuffer implements Encrypted {
|
||||
readonly encryptionType: EncryptionType = null;
|
||||
readonly dataBytes: ArrayBuffer = null;
|
||||
readonly ivBytes: ArrayBuffer = null;
|
||||
readonly macBytes: ArrayBuffer = null;
|
||||
readonly dataBytes: Uint8Array = null;
|
||||
readonly ivBytes: Uint8Array = null;
|
||||
readonly macBytes: Uint8Array = null;
|
||||
|
||||
constructor(readonly buffer: ArrayBuffer) {
|
||||
const encBytes = new Uint8Array(buffer);
|
||||
constructor(readonly buffer: Uint8Array) {
|
||||
const encBytes = buffer;
|
||||
const encType = encBytes[0];
|
||||
|
||||
switch (encType) {
|
||||
@@ -25,12 +25,12 @@ export class EncArrayBuffer implements Encrypted {
|
||||
this.throwDecryptionError();
|
||||
}
|
||||
|
||||
this.ivBytes = encBytes.slice(ENC_TYPE_LENGTH, ENC_TYPE_LENGTH + IV_LENGTH).buffer;
|
||||
this.ivBytes = encBytes.slice(ENC_TYPE_LENGTH, ENC_TYPE_LENGTH + IV_LENGTH);
|
||||
this.macBytes = encBytes.slice(
|
||||
ENC_TYPE_LENGTH + IV_LENGTH,
|
||||
ENC_TYPE_LENGTH + IV_LENGTH + MAC_LENGTH
|
||||
).buffer;
|
||||
this.dataBytes = encBytes.slice(ENC_TYPE_LENGTH + IV_LENGTH + MAC_LENGTH).buffer;
|
||||
);
|
||||
this.dataBytes = encBytes.slice(ENC_TYPE_LENGTH + IV_LENGTH + MAC_LENGTH);
|
||||
break;
|
||||
}
|
||||
case EncryptionType.AesCbc256_B64: {
|
||||
@@ -39,8 +39,8 @@ export class EncArrayBuffer implements Encrypted {
|
||||
this.throwDecryptionError();
|
||||
}
|
||||
|
||||
this.ivBytes = encBytes.slice(ENC_TYPE_LENGTH, ENC_TYPE_LENGTH + IV_LENGTH).buffer;
|
||||
this.dataBytes = encBytes.slice(ENC_TYPE_LENGTH + IV_LENGTH).buffer;
|
||||
this.ivBytes = encBytes.slice(ENC_TYPE_LENGTH, ENC_TYPE_LENGTH + IV_LENGTH);
|
||||
this.dataBytes = encBytes.slice(ENC_TYPE_LENGTH + IV_LENGTH);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -63,11 +63,11 @@ export class EncArrayBuffer implements Encrypted {
|
||||
if (buffer == null) {
|
||||
throw new Error("Cannot create EncArrayBuffer from Response - Response is empty");
|
||||
}
|
||||
return new EncArrayBuffer(buffer);
|
||||
return new EncArrayBuffer(new Uint8Array(buffer));
|
||||
}
|
||||
|
||||
static fromB64(b64: string) {
|
||||
const buffer = Utils.fromB64ToArray(b64).buffer;
|
||||
const buffer = Utils.fromB64ToArray(b64);
|
||||
return new EncArrayBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,16 +27,16 @@ export class EncString implements Encrypted {
|
||||
}
|
||||
}
|
||||
|
||||
get ivBytes(): ArrayBuffer {
|
||||
return this.iv == null ? null : Utils.fromB64ToArray(this.iv).buffer;
|
||||
get ivBytes(): Uint8Array {
|
||||
return this.iv == null ? null : Utils.fromB64ToArray(this.iv);
|
||||
}
|
||||
|
||||
get macBytes(): ArrayBuffer {
|
||||
return this.mac == null ? null : Utils.fromB64ToArray(this.mac).buffer;
|
||||
get macBytes(): Uint8Array {
|
||||
return this.mac == null ? null : Utils.fromB64ToArray(this.mac);
|
||||
}
|
||||
|
||||
get dataBytes(): ArrayBuffer {
|
||||
return this.data == null ? null : Utils.fromB64ToArray(this.data).buffer;
|
||||
get dataBytes(): Uint8Array {
|
||||
return this.data == null ? null : Utils.fromB64ToArray(this.data);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
|
||||
export class EncryptedObject {
|
||||
iv: ArrayBuffer;
|
||||
data: ArrayBuffer;
|
||||
mac: ArrayBuffer;
|
||||
iv: Uint8Array;
|
||||
data: Uint8Array;
|
||||
mac: Uint8Array;
|
||||
key: SymmetricCryptoKey;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,13 @@ describe("EncryptionPair", () => {
|
||||
expect(json.decrypted).toEqual("hello");
|
||||
});
|
||||
|
||||
it("should populate decryptedSerialized for TypesArrays", () => {
|
||||
const pair = new EncryptionPair<string, Uint8Array>();
|
||||
pair.decrypted = Utils.fromByteStringToArray("hello");
|
||||
const json = pair.toJSON();
|
||||
expect(json.decrypted).toEqual(new Uint8Array([104, 101, 108, 108, 111]));
|
||||
});
|
||||
|
||||
it("should serialize encrypted and decrypted", () => {
|
||||
const pair = new EncryptionPair<string, string>();
|
||||
pair.encrypted = "hello";
|
||||
|
||||
@@ -36,4 +36,5 @@ export class GlobalState {
|
||||
enableBrowserIntegration?: boolean;
|
||||
enableBrowserIntegrationFingerprint?: boolean;
|
||||
enableDuckDuckGoBrowserIntegration?: boolean;
|
||||
region?: string;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ describe("SymmetricCryptoKey", () => {
|
||||
});
|
||||
|
||||
it("toJSON creates object for serialization", () => {
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64).buffer);
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||
const actual = key.toJSON();
|
||||
|
||||
const expected = { keyB64: key.keyB64 };
|
||||
@@ -77,7 +77,7 @@ describe("SymmetricCryptoKey", () => {
|
||||
});
|
||||
|
||||
it("fromJSON hydrates new object", () => {
|
||||
const expected = new SymmetricCryptoKey(makeStaticByteArray(64).buffer);
|
||||
const expected = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||
const actual = SymmetricCryptoKey.fromJSON({ keyB64: expected.keyB64 });
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
|
||||
@@ -4,9 +4,9 @@ import { EncryptionType } from "../../../enums";
|
||||
import { Utils } from "../../../platform/misc/utils";
|
||||
|
||||
export class SymmetricCryptoKey {
|
||||
key: ArrayBuffer;
|
||||
encKey?: ArrayBuffer;
|
||||
macKey?: ArrayBuffer;
|
||||
key: Uint8Array;
|
||||
encKey?: Uint8Array;
|
||||
macKey?: Uint8Array;
|
||||
encType: EncryptionType;
|
||||
|
||||
keyB64: string;
|
||||
@@ -15,7 +15,7 @@ export class SymmetricCryptoKey {
|
||||
|
||||
meta: any;
|
||||
|
||||
constructor(key: ArrayBuffer, encType?: EncryptionType) {
|
||||
constructor(key: Uint8Array, encType?: EncryptionType) {
|
||||
if (key == null) {
|
||||
throw new Error("Must provide key");
|
||||
}
|
||||
@@ -67,7 +67,7 @@ export class SymmetricCryptoKey {
|
||||
return null;
|
||||
}
|
||||
|
||||
const arrayBuffer = Utils.fromB64ToArray(s).buffer;
|
||||
const arrayBuffer = Utils.fromB64ToArray(s);
|
||||
return new SymmetricCryptoKey(arrayBuffer);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { Region } from "../../abstractions/environment.service";
|
||||
|
||||
export class ServerConfigResponse extends BaseResponse {
|
||||
version: string;
|
||||
@@ -23,6 +24,7 @@ export class ServerConfigResponse extends BaseResponse {
|
||||
}
|
||||
|
||||
export class EnvironmentServerConfigResponse extends BaseResponse {
|
||||
cloudRegion: Region;
|
||||
vault: string;
|
||||
api: string;
|
||||
identity: string;
|
||||
@@ -36,6 +38,7 @@ export class EnvironmentServerConfigResponse extends BaseResponse {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cloudRegion = this.getResponseProperty("CloudRegion");
|
||||
this.vault = this.getResponseProperty("Vault");
|
||||
this.api = this.getResponseProperty("Api");
|
||||
this.identity = this.getResponseProperty("Identity");
|
||||
|
||||
@@ -44,6 +44,7 @@ export class ConfigService implements ConfigServiceAbstraction {
|
||||
return serverConfig;
|
||||
}
|
||||
await this.stateService.setServerConfig(data);
|
||||
this.environmentService.setCloudWebVaultUrl(data.environment?.cloudRegion);
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
@@ -62,6 +63,11 @@ export class ConfigService implements ConfigServiceAbstraction {
|
||||
return await this.getFeatureFlag(key, defaultValue);
|
||||
}
|
||||
|
||||
async getCloudRegion(defaultValue = "US"): Promise<string> {
|
||||
const serverConfig = await this.buildServerConfig();
|
||||
return serverConfig.environment?.cloudRegion ?? defaultValue;
|
||||
}
|
||||
|
||||
private async getFeatureFlag<T>(key: FeatureFlag, defaultValue: T): Promise<T> {
|
||||
const serverConfig = await this.buildServerConfig();
|
||||
if (
|
||||
|
||||
@@ -123,7 +123,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
): Promise<SymmetricCryptoKey> {
|
||||
const key = await this.retrieveKeyFromStorage(keySuffix, userId);
|
||||
if (key != null) {
|
||||
const symmetricKey = new SymmetricCryptoKey(Utils.fromB64ToArray(key).buffer);
|
||||
const symmetricKey = new SymmetricCryptoKey(Utils.fromB64ToArray(key));
|
||||
|
||||
if (!(await this.validateKey(symmetricKey))) {
|
||||
this.logService.warning("Wrong key, throwing away stored key");
|
||||
@@ -172,7 +172,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return this.getEncKeyHelper(key);
|
||||
}
|
||||
|
||||
async getPublicKey(): Promise<ArrayBuffer> {
|
||||
async getPublicKey(): Promise<Uint8Array> {
|
||||
const inMemoryPublicKey = await this.stateService.getPublicKey();
|
||||
if (inMemoryPublicKey != null) {
|
||||
return inMemoryPublicKey;
|
||||
@@ -188,7 +188,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
async getPrivateKey(): Promise<ArrayBuffer> {
|
||||
async getPrivateKey(): Promise<Uint8Array> {
|
||||
const decryptedPrivateKey = await this.stateService.getDecryptedPrivateKey();
|
||||
if (decryptedPrivateKey != null) {
|
||||
return decryptedPrivateKey;
|
||||
@@ -204,7 +204,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
async getFingerprint(userId: string, publicKey?: ArrayBuffer): Promise<string[]> {
|
||||
async getFingerprint(fingerprintMaterial: string, publicKey?: Uint8Array): Promise<string[]> {
|
||||
if (publicKey == null) {
|
||||
publicKey = await this.getPublicKey();
|
||||
}
|
||||
@@ -214,7 +214,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
const keyFingerprint = await this.cryptoFunctionService.hash(publicKey, "sha256");
|
||||
const userFingerprint = await this.cryptoFunctionService.hkdfExpand(
|
||||
keyFingerprint,
|
||||
userId,
|
||||
fingerprintMaterial,
|
||||
32,
|
||||
"sha256"
|
||||
);
|
||||
@@ -416,7 +416,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
kdf: KdfType,
|
||||
kdfConfig: KdfConfig
|
||||
): Promise<SymmetricCryptoKey> {
|
||||
let key: ArrayBuffer = null;
|
||||
let key: Uint8Array = null;
|
||||
if (kdf == null || kdf === KdfType.PBKDF2_SHA256) {
|
||||
if (kdfConfig.iterations == null) {
|
||||
kdfConfig.iterations = 5000;
|
||||
@@ -502,7 +502,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return await this.stretchKey(pinKey);
|
||||
}
|
||||
|
||||
async makeSendKey(keyMaterial: ArrayBuffer): Promise<SymmetricCryptoKey> {
|
||||
async makeSendKey(keyMaterial: Uint8Array): Promise<SymmetricCryptoKey> {
|
||||
const sendKey = await this.cryptoFunctionService.hkdf(
|
||||
keyMaterial,
|
||||
"bitwarden-send",
|
||||
@@ -550,7 +550,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
* @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey)
|
||||
* and then call encryptService.encrypt
|
||||
*/
|
||||
async encrypt(plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey): Promise<EncString> {
|
||||
async encrypt(plainValue: string | Uint8Array, key?: SymmetricCryptoKey): Promise<EncString> {
|
||||
key = await this.getKeyForUserEncryption(key);
|
||||
return await this.encryptService.encrypt(plainValue, key);
|
||||
}
|
||||
@@ -559,12 +559,12 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
* @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey)
|
||||
* and then call encryptService.encryptToBytes
|
||||
*/
|
||||
async encryptToBytes(plainValue: ArrayBuffer, key?: SymmetricCryptoKey): Promise<EncArrayBuffer> {
|
||||
async encryptToBytes(plainValue: Uint8Array, key?: SymmetricCryptoKey): Promise<EncArrayBuffer> {
|
||||
key = await this.getKeyForUserEncryption(key);
|
||||
return this.encryptService.encryptToBytes(plainValue, key);
|
||||
}
|
||||
|
||||
async rsaEncrypt(data: ArrayBuffer, publicKey?: ArrayBuffer): Promise<EncString> {
|
||||
async rsaEncrypt(data: Uint8Array, publicKey?: Uint8Array): Promise<EncString> {
|
||||
if (publicKey == null) {
|
||||
publicKey = await this.getPublicKey();
|
||||
}
|
||||
@@ -576,7 +576,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Utils.fromBufferToB64(encBytes));
|
||||
}
|
||||
|
||||
async rsaDecrypt(encValue: string, privateKeyValue?: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
async rsaDecrypt(encValue: string, privateKeyValue?: Uint8Array): Promise<Uint8Array> {
|
||||
const headerPieces = encValue.split(".");
|
||||
let encType: EncryptionType = null;
|
||||
let encPieces: string[];
|
||||
@@ -607,7 +607,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
throw new Error("encPieces unavailable.");
|
||||
}
|
||||
|
||||
const data = Utils.fromB64ToArray(encPieces[0]).buffer;
|
||||
const data = Utils.fromB64ToArray(encPieces[0]);
|
||||
const privateKey = privateKeyValue ?? (await this.getPrivateKey());
|
||||
if (privateKey == null) {
|
||||
throw new Error("No private key.");
|
||||
@@ -633,7 +633,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
* @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey)
|
||||
* and then call encryptService.decryptToBytes
|
||||
*/
|
||||
async decryptToBytes(encString: EncString, key?: SymmetricCryptoKey): Promise<ArrayBuffer> {
|
||||
async decryptToBytes(encString: EncString, key?: SymmetricCryptoKey): Promise<Uint8Array> {
|
||||
const keyForEnc = await this.getKeyForUserEncryption(key);
|
||||
return this.encryptService.decryptToBytes(encString, keyForEnc);
|
||||
}
|
||||
@@ -651,7 +651,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
* @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey)
|
||||
* and then call encryptService.decryptToBytes
|
||||
*/
|
||||
async decryptFromBytes(encBuffer: EncArrayBuffer, key: SymmetricCryptoKey): Promise<ArrayBuffer> {
|
||||
async decryptFromBytes(encBuffer: EncArrayBuffer, key: SymmetricCryptoKey): Promise<Uint8Array> {
|
||||
if (encBuffer == null) {
|
||||
throw new Error("No buffer provided for decryption.");
|
||||
}
|
||||
@@ -768,10 +768,10 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
const macKey = await this.cryptoFunctionService.hkdfExpand(key.key, "mac", 32, "sha256");
|
||||
newKey.set(new Uint8Array(encKey));
|
||||
newKey.set(new Uint8Array(macKey), 32);
|
||||
return new SymmetricCryptoKey(newKey.buffer);
|
||||
return new SymmetricCryptoKey(newKey);
|
||||
}
|
||||
|
||||
private async hashPhrase(hash: ArrayBuffer, minimumEntropy = 64) {
|
||||
private async hashPhrase(hash: Uint8Array, minimumEntropy = 64) {
|
||||
const entropyPerWord = Math.log(EFFLongWordList.length) / Math.log(2);
|
||||
let numWords = Math.ceil(minimumEntropy / entropyPerWord);
|
||||
|
||||
@@ -793,7 +793,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
|
||||
private async buildEncKey(
|
||||
key: SymmetricCryptoKey,
|
||||
encKey: ArrayBuffer
|
||||
encKey: Uint8Array
|
||||
): Promise<[SymmetricCryptoKey, EncString]> {
|
||||
let encKeyEnc: EncString = null;
|
||||
if (key.key.byteLength === 32) {
|
||||
@@ -830,7 +830,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return null;
|
||||
}
|
||||
|
||||
let decEncKey: ArrayBuffer;
|
||||
let decEncKey: Uint8Array;
|
||||
const encKeyCipher = new EncString(encKey);
|
||||
if (encKeyCipher.encryptionType === EncryptionType.AesCbc256_B64) {
|
||||
decEncKey = await this.decryptToBytes(encKeyCipher, key);
|
||||
|
||||
@@ -18,7 +18,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
protected logMacFailures: boolean
|
||||
) {}
|
||||
|
||||
async encrypt(plainValue: string | ArrayBuffer, key: SymmetricCryptoKey): Promise<EncString> {
|
||||
async encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise<EncString> {
|
||||
if (key == null) {
|
||||
throw new Error("No encryption key provided.");
|
||||
}
|
||||
@@ -27,9 +27,9 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
let plainBuf: ArrayBuffer;
|
||||
let plainBuf: Uint8Array;
|
||||
if (typeof plainValue === "string") {
|
||||
plainBuf = Utils.fromUtf8ToArray(plainValue).buffer;
|
||||
plainBuf = Utils.fromUtf8ToArray(plainValue);
|
||||
} else {
|
||||
plainBuf = plainValue;
|
||||
}
|
||||
@@ -41,7 +41,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
return new EncString(encObj.key.encType, data, iv, mac);
|
||||
}
|
||||
|
||||
async encryptToBytes(plainValue: ArrayBuffer, key: SymmetricCryptoKey): Promise<EncArrayBuffer> {
|
||||
async encryptToBytes(plainValue: Uint8Array, key: SymmetricCryptoKey): Promise<EncArrayBuffer> {
|
||||
if (key == null) {
|
||||
throw new Error("No encryption key provided.");
|
||||
}
|
||||
@@ -60,7 +60,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
}
|
||||
|
||||
encBytes.set(new Uint8Array(encValue.data), 1 + encValue.iv.byteLength + macLen);
|
||||
return new EncArrayBuffer(encBytes.buffer);
|
||||
return new EncArrayBuffer(encBytes);
|
||||
}
|
||||
|
||||
async decryptToUtf8(encString: EncString, key: SymmetricCryptoKey): Promise<string> {
|
||||
@@ -102,7 +102,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
return await this.cryptoFunctionService.aesDecryptFast(fastParams);
|
||||
}
|
||||
|
||||
async decryptToBytes(encThing: Encrypted, key: SymmetricCryptoKey): Promise<ArrayBuffer> {
|
||||
async decryptToBytes(encThing: Encrypted, key: SymmetricCryptoKey): Promise<Uint8Array> {
|
||||
if (key == null) {
|
||||
throw new Error("No encryption key provided.");
|
||||
}
|
||||
@@ -125,11 +125,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
const macData = new Uint8Array(encThing.ivBytes.byteLength + encThing.dataBytes.byteLength);
|
||||
macData.set(new Uint8Array(encThing.ivBytes), 0);
|
||||
macData.set(new Uint8Array(encThing.dataBytes), encThing.ivBytes.byteLength);
|
||||
const computedMac = await this.cryptoFunctionService.hmac(
|
||||
macData.buffer,
|
||||
key.macKey,
|
||||
"sha256"
|
||||
);
|
||||
const computedMac = await this.cryptoFunctionService.hmac(macData, key.macKey, "sha256");
|
||||
if (computedMac === null) {
|
||||
return null;
|
||||
}
|
||||
@@ -161,7 +157,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
return await Promise.all(items.map((item) => item.decrypt(key)));
|
||||
}
|
||||
|
||||
private async aesEncrypt(data: ArrayBuffer, key: SymmetricCryptoKey): Promise<EncryptedObject> {
|
||||
private async aesEncrypt(data: Uint8Array, key: SymmetricCryptoKey): Promise<EncryptedObject> {
|
||||
const obj = new EncryptedObject();
|
||||
obj.key = key;
|
||||
obj.iv = await this.cryptoFunctionService.randomBytes(16);
|
||||
@@ -171,7 +167,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
const macData = new Uint8Array(obj.iv.byteLength + obj.data.byteLength);
|
||||
macData.set(new Uint8Array(obj.iv), 0);
|
||||
macData.set(new Uint8Array(obj.data), obj.iv.byteLength);
|
||||
obj.mac = await this.cryptoFunctionService.hmac(macData.buffer, obj.key.macKey, "sha256");
|
||||
obj.mac = await this.cryptoFunctionService.hmac(macData, obj.key.macKey, "sha256");
|
||||
}
|
||||
|
||||
return obj;
|
||||
|
||||
@@ -37,10 +37,8 @@ describe("EncryptService", () => {
|
||||
|
||||
describe("encrypts data", () => {
|
||||
beforeEach(() => {
|
||||
cryptoFunctionService.randomBytes
|
||||
.calledWith(16)
|
||||
.mockResolvedValueOnce(iv.buffer as CsprngArray);
|
||||
cryptoFunctionService.aesEncrypt.mockResolvedValue(encryptedData.buffer);
|
||||
cryptoFunctionService.randomBytes.calledWith(16).mockResolvedValueOnce(iv as CsprngArray);
|
||||
cryptoFunctionService.aesEncrypt.mockResolvedValue(encryptedData);
|
||||
});
|
||||
|
||||
it("using a key which supports mac", async () => {
|
||||
@@ -50,7 +48,7 @@ describe("EncryptService", () => {
|
||||
|
||||
key.macKey = makeStaticByteArray(16, 20);
|
||||
|
||||
cryptoFunctionService.hmac.mockResolvedValue(mac.buffer);
|
||||
cryptoFunctionService.hmac.mockResolvedValue(mac);
|
||||
|
||||
const actual = await encryptService.encryptToBytes(plainValue, key);
|
||||
|
||||
@@ -86,7 +84,7 @@ describe("EncryptService", () => {
|
||||
describe("decryptToBytes", () => {
|
||||
const encType = EncryptionType.AesCbc256_HmacSha256_B64;
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64, 100), encType);
|
||||
const computedMac = new Uint8Array(1).buffer;
|
||||
const computedMac = new Uint8Array(1);
|
||||
const encBuffer = new EncArrayBuffer(makeStaticByteArray(60, encType));
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -106,9 +104,9 @@ describe("EncryptService", () => {
|
||||
});
|
||||
|
||||
it("decrypts data with provided key", async () => {
|
||||
const decryptedBytes = makeStaticByteArray(10, 200).buffer;
|
||||
const decryptedBytes = makeStaticByteArray(10, 200);
|
||||
|
||||
cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(1).buffer);
|
||||
cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(1));
|
||||
cryptoFunctionService.compare.mockResolvedValue(true);
|
||||
cryptoFunctionService.aesDecrypt.mockResolvedValueOnce(decryptedBytes);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { concatMap, Observable, Subject } from "rxjs";
|
||||
import { EnvironmentUrls } from "../../auth/models/domain/environment-urls";
|
||||
import {
|
||||
EnvironmentService as EnvironmentServiceAbstraction,
|
||||
Region,
|
||||
Urls,
|
||||
} from "../abstractions/environment.service";
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
@@ -10,6 +11,8 @@ import { StateService } from "../abstractions/state.service";
|
||||
export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
private readonly urlsSubject = new Subject<void>();
|
||||
urls: Observable<void> = this.urlsSubject.asObservable();
|
||||
selectedRegion?: Region;
|
||||
initialized = false;
|
||||
|
||||
protected baseUrl: string;
|
||||
protected webVaultUrl: string;
|
||||
@@ -20,11 +23,37 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
protected eventsUrl: string;
|
||||
private keyConnectorUrl: string;
|
||||
private scimUrl: string = null;
|
||||
private cloudWebVaultUrl: string;
|
||||
|
||||
readonly usUrls: Urls = {
|
||||
base: null,
|
||||
api: "https://api.bitwarden.com",
|
||||
identity: "https://identity.bitwarden.com",
|
||||
icons: "https://icons.bitwarden.net",
|
||||
webVault: "https://vault.bitwarden.com",
|
||||
notifications: "https://notifications.bitwarden.com",
|
||||
events: "https://events.bitwarden.com",
|
||||
scim: "https://scim.bitwarden.com",
|
||||
};
|
||||
|
||||
readonly euUrls: Urls = {
|
||||
base: null,
|
||||
api: "https://api.bitwarden.eu",
|
||||
identity: "https://identity.bitwarden.eu",
|
||||
icons: "https://icons.bitwarden.eu",
|
||||
webVault: "https://vault.bitwarden.eu",
|
||||
notifications: "https://notifications.bitwarden.eu",
|
||||
events: "https://events.bitwarden.eu",
|
||||
scim: "https://scim.bitwarden.eu",
|
||||
};
|
||||
|
||||
constructor(private stateService: StateService) {
|
||||
this.stateService.activeAccount$
|
||||
.pipe(
|
||||
concatMap(async () => {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
await this.setUrlsFromStorage();
|
||||
})
|
||||
)
|
||||
@@ -58,6 +87,26 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
return "https://vault.bitwarden.com";
|
||||
}
|
||||
|
||||
getCloudWebVaultUrl() {
|
||||
if (this.cloudWebVaultUrl != null) {
|
||||
return this.cloudWebVaultUrl;
|
||||
}
|
||||
|
||||
return this.usUrls.webVault;
|
||||
}
|
||||
|
||||
setCloudWebVaultUrl(region: Region) {
|
||||
switch (region) {
|
||||
case Region.EU:
|
||||
this.cloudWebVaultUrl = this.euUrls.webVault;
|
||||
break;
|
||||
case Region.US:
|
||||
default:
|
||||
this.cloudWebVaultUrl = this.usUrls.webVault;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
getSendUrl() {
|
||||
return this.getWebVaultUrl() === "https://vault.bitwarden.com"
|
||||
? "https://send.bitwarden.com/#"
|
||||
@@ -127,20 +176,42 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
}
|
||||
|
||||
async setUrlsFromStorage(): Promise<void> {
|
||||
const urls: any = await this.stateService.getEnvironmentUrls();
|
||||
const region = await this.stateService.getRegion();
|
||||
const savedUrls = await this.stateService.getEnvironmentUrls();
|
||||
const envUrls = new EnvironmentUrls();
|
||||
|
||||
this.baseUrl = envUrls.base = urls.base;
|
||||
this.webVaultUrl = urls.webVault;
|
||||
this.apiUrl = envUrls.api = urls.api;
|
||||
this.identityUrl = envUrls.identity = urls.identity;
|
||||
this.iconsUrl = urls.icons;
|
||||
this.notificationsUrl = urls.notifications;
|
||||
this.eventsUrl = envUrls.events = urls.events;
|
||||
this.keyConnectorUrl = urls.keyConnector;
|
||||
// scimUrl is not saved to storage
|
||||
// In release `2023.5.0`, we set the `base` property of the environment URLs to the US web vault URL when a user clicked the "US" region.
|
||||
// This check will detect these cases and convert them to the proper region instead.
|
||||
// We are detecting this by checking for the presence of the web vault URL in the `base` and the absence of the `notifications` property.
|
||||
// This is because the `notifications` will not be `null` in the web vault, and we don't want to migrate the URLs in that case.
|
||||
if (savedUrls.base === "https://vault.bitwarden.com" && savedUrls.notifications == null) {
|
||||
await this.setRegion(Region.US);
|
||||
return;
|
||||
}
|
||||
|
||||
this.urlsSubject.next();
|
||||
switch (region) {
|
||||
case Region.EU:
|
||||
await this.setRegion(Region.EU);
|
||||
return;
|
||||
case Region.US:
|
||||
await this.setRegion(Region.US);
|
||||
return;
|
||||
case Region.SelfHosted:
|
||||
case null:
|
||||
default:
|
||||
this.baseUrl = envUrls.base = savedUrls.base;
|
||||
this.webVaultUrl = savedUrls.webVault;
|
||||
this.apiUrl = envUrls.api = savedUrls.api;
|
||||
this.identityUrl = envUrls.identity = savedUrls.identity;
|
||||
this.iconsUrl = savedUrls.icons;
|
||||
this.notificationsUrl = savedUrls.notifications;
|
||||
this.eventsUrl = envUrls.events = savedUrls.events;
|
||||
this.keyConnectorUrl = savedUrls.keyConnector;
|
||||
await this.setRegion(Region.SelfHosted);
|
||||
// scimUrl is not saved to storage
|
||||
this.urlsSubject.next();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async setUrls(urls: Urls): Promise<Urls> {
|
||||
@@ -178,6 +249,8 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
this.keyConnectorUrl = urls.keyConnector;
|
||||
this.scimUrl = urls.scim;
|
||||
|
||||
await this.setRegion(Region.SelfHosted);
|
||||
|
||||
this.urlsSubject.next();
|
||||
|
||||
return urls;
|
||||
@@ -187,6 +260,7 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
return {
|
||||
base: this.baseUrl,
|
||||
webVault: this.webVaultUrl,
|
||||
cloudWebVault: this.cloudWebVaultUrl,
|
||||
api: this.apiUrl,
|
||||
identity: this.identityUrl,
|
||||
icons: this.iconsUrl,
|
||||
@@ -197,6 +271,53 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
};
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
return (
|
||||
this.baseUrl == null &&
|
||||
this.webVaultUrl == null &&
|
||||
this.apiUrl == null &&
|
||||
this.identityUrl == null &&
|
||||
this.iconsUrl == null &&
|
||||
this.notificationsUrl == null &&
|
||||
this.eventsUrl == null
|
||||
);
|
||||
}
|
||||
|
||||
async setRegion(region: Region) {
|
||||
this.selectedRegion = region;
|
||||
await this.stateService.setRegion(region);
|
||||
|
||||
if (region === Region.SelfHosted) {
|
||||
// If user saves a self-hosted region with empty fields, default to US
|
||||
if (this.isEmpty()) {
|
||||
await this.setRegion(Region.US);
|
||||
}
|
||||
} else {
|
||||
// If we are setting the region to EU or US, clear the self-hosted URLs
|
||||
await this.stateService.setEnvironmentUrls(new EnvironmentUrls());
|
||||
if (region === Region.EU) {
|
||||
this.setUrlsInternal(this.euUrls);
|
||||
} else if (region === Region.US) {
|
||||
this.setUrlsInternal(this.usUrls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setUrlsInternal(urls: Urls) {
|
||||
this.baseUrl = this.formatUrl(urls.base);
|
||||
this.webVaultUrl = this.formatUrl(urls.webVault);
|
||||
this.apiUrl = this.formatUrl(urls.api);
|
||||
this.identityUrl = this.formatUrl(urls.identity);
|
||||
this.iconsUrl = this.formatUrl(urls.icons);
|
||||
this.notificationsUrl = this.formatUrl(urls.notifications);
|
||||
this.eventsUrl = this.formatUrl(urls.events);
|
||||
this.keyConnectorUrl = this.formatUrl(urls.keyConnector);
|
||||
|
||||
// scimUrl cannot be cleared
|
||||
this.scimUrl = this.formatUrl(urls.scim) ?? this.scimUrl;
|
||||
this.urlsSubject.next();
|
||||
}
|
||||
|
||||
private formatUrl(url: string): string {
|
||||
if (url == null || url === "") {
|
||||
return null;
|
||||
@@ -211,19 +332,11 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
|
||||
}
|
||||
|
||||
isCloud(): boolean {
|
||||
return ["https://api.bitwarden.com", "https://vault.bitwarden.com/api"].includes(
|
||||
this.getApiUrl()
|
||||
);
|
||||
}
|
||||
|
||||
isSelfHosted(): boolean {
|
||||
return ![
|
||||
"http://vault.bitwarden.com",
|
||||
"https://vault.bitwarden.com",
|
||||
"http://vault.bitwarden.eu",
|
||||
"https://vault.bitwarden.eu",
|
||||
"http://vault.qa.bitwarden.pw",
|
||||
"https://vault.qa.bitwarden.pw",
|
||||
].includes(this.getWebVaultUrl());
|
||||
return [
|
||||
"https://api.bitwarden.com",
|
||||
"https://vault.bitwarden.com/api",
|
||||
"https://api.bitwarden.eu",
|
||||
"https://vault.bitwarden.eu/api",
|
||||
].includes(this.getApiUrl());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ export class StateService<
|
||||
}
|
||||
|
||||
async addAccount(account: TAccount) {
|
||||
account = await this.setAccountEnvironmentUrls(account);
|
||||
account = await this.setAccountEnvironment(account);
|
||||
await this.updateState(async (state) => {
|
||||
state.authenticatedAccounts.push(account.profile.userId);
|
||||
await this.storageService.save(keys.authenticatedAccounts, state.authenticatedAccounts);
|
||||
@@ -763,13 +763,13 @@ export class StateService<
|
||||
);
|
||||
}
|
||||
|
||||
async getDecryptedPrivateKey(options?: StorageOptions): Promise<ArrayBuffer> {
|
||||
async getDecryptedPrivateKey(options?: StorageOptions): Promise<Uint8Array> {
|
||||
return (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
||||
)?.keys?.privateKey.decrypted;
|
||||
}
|
||||
|
||||
async setDecryptedPrivateKey(value: ArrayBuffer, options?: StorageOptions): Promise<void> {
|
||||
async setDecryptedPrivateKey(value: Uint8Array, options?: StorageOptions): Promise<void> {
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||
);
|
||||
@@ -1598,6 +1598,28 @@ export class StateService<
|
||||
);
|
||||
}
|
||||
|
||||
async getRegion(options?: StorageOptions): Promise<string> {
|
||||
if ((await this.state())?.activeUserId == null) {
|
||||
options = this.reconcileOptions(options, await this.defaultOnDiskOptions());
|
||||
return (await this.getGlobals(options)).region ?? null;
|
||||
}
|
||||
options = this.reconcileOptions(options, await this.defaultOnDiskOptions());
|
||||
return (await this.getAccount(options))?.settings?.region ?? null;
|
||||
}
|
||||
|
||||
async setRegion(value: string, options?: StorageOptions): Promise<void> {
|
||||
// Global values are set on each change and the current global settings are passed to any newly authed accounts.
|
||||
// This is to allow setting region values before an account is active, while still allowing individual accounts to have their own region.
|
||||
const globals = await this.getGlobals(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||
);
|
||||
globals.region = value;
|
||||
await this.saveGlobals(
|
||||
globals,
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||
);
|
||||
}
|
||||
|
||||
async getEquivalentDomains(options?: StorageOptions): Promise<string[][]> {
|
||||
return (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||
@@ -2075,14 +2097,14 @@ export class StateService<
|
||||
);
|
||||
}
|
||||
|
||||
async getPublicKey(options?: StorageOptions): Promise<ArrayBuffer> {
|
||||
async getPublicKey(options?: StorageOptions): Promise<Uint8Array> {
|
||||
const keys = (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
||||
)?.keys;
|
||||
return keys?.publicKey;
|
||||
}
|
||||
|
||||
async setPublicKey(value: ArrayBuffer, options?: StorageOptions): Promise<void> {
|
||||
async setPublicKey(value: Uint8Array, options?: StorageOptions): Promise<void> {
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||
);
|
||||
@@ -2584,8 +2606,9 @@ export class StateService<
|
||||
await this.defaultOnDiskLocalOptions()
|
||||
)
|
||||
);
|
||||
// EnvironmentUrls are set before authenticating and should override whatever is stored from any previous session
|
||||
// EnvironmentUrls and region are set before authenticating and should override whatever is stored from any previous session
|
||||
const environmentUrls = account.settings.environmentUrls;
|
||||
const region = account.settings.region;
|
||||
if (storedAccount?.settings != null) {
|
||||
account.settings = storedAccount.settings;
|
||||
} else if (await this.storageService.has(keys.tempAccountSettings)) {
|
||||
@@ -2593,6 +2616,8 @@ export class StateService<
|
||||
await this.storageService.remove(keys.tempAccountSettings);
|
||||
}
|
||||
account.settings.environmentUrls = environmentUrls;
|
||||
account.settings.region = region;
|
||||
|
||||
if (
|
||||
account.settings.vaultTimeoutAction === VaultTimeoutAction.LogOut &&
|
||||
account.settings.vaultTimeout != null
|
||||
@@ -2620,6 +2645,7 @@ export class StateService<
|
||||
);
|
||||
if (storedAccount?.settings != null) {
|
||||
storedAccount.settings.environmentUrls = account.settings.environmentUrls;
|
||||
storedAccount.settings.region = account.settings.region;
|
||||
account.settings = storedAccount.settings;
|
||||
}
|
||||
await this.storageService.save(
|
||||
@@ -2642,6 +2668,7 @@ export class StateService<
|
||||
);
|
||||
if (storedAccount?.settings != null) {
|
||||
storedAccount.settings.environmentUrls = account.settings.environmentUrls;
|
||||
storedAccount.settings.region = account.settings.region;
|
||||
account.settings = storedAccount.settings;
|
||||
}
|
||||
await this.storageService.save(
|
||||
@@ -2790,7 +2817,9 @@ export class StateService<
|
||||
return Object.assign(this.createAccount(), persistentAccountInformation);
|
||||
}
|
||||
|
||||
protected async setAccountEnvironmentUrls(account: TAccount): Promise<TAccount> {
|
||||
// The environment urls and region are selected before login and are transferred here to an authenticated account
|
||||
protected async setAccountEnvironment(account: TAccount): Promise<TAccount> {
|
||||
account.settings.region = await this.getGlobalRegion();
|
||||
account.settings.environmentUrls = await this.getGlobalEnvironmentUrls();
|
||||
return account;
|
||||
}
|
||||
@@ -2800,6 +2829,11 @@ export class StateService<
|
||||
return (await this.getGlobals(options)).environmentUrls ?? new EnvironmentUrls();
|
||||
}
|
||||
|
||||
protected async getGlobalRegion(options?: StorageOptions): Promise<string> {
|
||||
options = this.reconcileOptions(options, await this.defaultOnDiskOptions());
|
||||
return (await this.getGlobals(options)).region ?? null;
|
||||
}
|
||||
|
||||
protected async clearDecryptedDataForActiveUser(): Promise<void> {
|
||||
await this.updateState(async (state) => {
|
||||
const userId = state?.activeUserId;
|
||||
|
||||
@@ -160,7 +160,7 @@ describe("WebCrypto Function Service", () => {
|
||||
const a = new Uint8Array(2);
|
||||
a[0] = 1;
|
||||
a[1] = 2;
|
||||
const equal = await cryptoFunctionService.compare(a.buffer, a.buffer);
|
||||
const equal = await cryptoFunctionService.compare(a, a);
|
||||
expect(equal).toBe(true);
|
||||
});
|
||||
|
||||
@@ -172,7 +172,7 @@ describe("WebCrypto Function Service", () => {
|
||||
const b = new Uint8Array(2);
|
||||
b[0] = 3;
|
||||
b[1] = 4;
|
||||
const equal = await cryptoFunctionService.compare(a.buffer, b.buffer);
|
||||
const equal = await cryptoFunctionService.compare(a, b);
|
||||
expect(equal).toBe(false);
|
||||
});
|
||||
|
||||
@@ -183,7 +183,7 @@ describe("WebCrypto Function Service", () => {
|
||||
a[1] = 2;
|
||||
const b = new Uint8Array(2);
|
||||
b[0] = 3;
|
||||
const equal = await cryptoFunctionService.compare(a.buffer, b.buffer);
|
||||
const equal = await cryptoFunctionService.compare(a, b);
|
||||
expect(equal).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -200,7 +200,7 @@ describe("WebCrypto Function Service", () => {
|
||||
const a = new Uint8Array(2);
|
||||
a[0] = 1;
|
||||
a[1] = 2;
|
||||
const aByteString = Utils.fromBufferToByteString(a.buffer);
|
||||
const aByteString = Utils.fromBufferToByteString(a);
|
||||
const equal = await cryptoFunctionService.compareFast(aByteString, aByteString);
|
||||
expect(equal).toBe(true);
|
||||
});
|
||||
@@ -210,11 +210,11 @@ describe("WebCrypto Function Service", () => {
|
||||
const a = new Uint8Array(2);
|
||||
a[0] = 1;
|
||||
a[1] = 2;
|
||||
const aByteString = Utils.fromBufferToByteString(a.buffer);
|
||||
const aByteString = Utils.fromBufferToByteString(a);
|
||||
const b = new Uint8Array(2);
|
||||
b[0] = 3;
|
||||
b[1] = 4;
|
||||
const bByteString = Utils.fromBufferToByteString(b.buffer);
|
||||
const bByteString = Utils.fromBufferToByteString(b);
|
||||
const equal = await cryptoFunctionService.compareFast(aByteString, bByteString);
|
||||
expect(equal).toBe(false);
|
||||
});
|
||||
@@ -224,10 +224,10 @@ describe("WebCrypto Function Service", () => {
|
||||
const a = new Uint8Array(2);
|
||||
a[0] = 1;
|
||||
a[1] = 2;
|
||||
const aByteString = Utils.fromBufferToByteString(a.buffer);
|
||||
const aByteString = Utils.fromBufferToByteString(a);
|
||||
const b = new Uint8Array(2);
|
||||
b[0] = 3;
|
||||
const bByteString = Utils.fromBufferToByteString(b.buffer);
|
||||
const bByteString = Utils.fromBufferToByteString(b);
|
||||
const equal = await cryptoFunctionService.compareFast(aByteString, bByteString);
|
||||
expect(equal).toBe(false);
|
||||
});
|
||||
@@ -239,7 +239,7 @@ describe("WebCrypto Function Service", () => {
|
||||
const iv = makeStaticByteArray(16);
|
||||
const key = makeStaticByteArray(32);
|
||||
const data = Utils.fromUtf8ToArray("EncryptMe!");
|
||||
const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
|
||||
const encValue = await cryptoFunctionService.aesEncrypt(data, iv, key);
|
||||
expect(Utils.fromBufferToB64(encValue)).toBe("ByUF8vhyX4ddU9gcooznwA==");
|
||||
});
|
||||
|
||||
@@ -249,10 +249,10 @@ describe("WebCrypto Function Service", () => {
|
||||
const key = makeStaticByteArray(32);
|
||||
const value = "EncryptMe!";
|
||||
const data = Utils.fromUtf8ToArray(value);
|
||||
const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
|
||||
const encValue = await cryptoFunctionService.aesEncrypt(data, iv, key);
|
||||
const encData = Utils.fromBufferToB64(encValue);
|
||||
const b64Iv = Utils.fromBufferToB64(iv.buffer);
|
||||
const symKey = new SymmetricCryptoKey(key.buffer);
|
||||
const b64Iv = Utils.fromBufferToB64(iv);
|
||||
const symKey = new SymmetricCryptoKey(key);
|
||||
const params = cryptoFunctionService.aesDecryptFastParameters(encData, b64Iv, null, symKey);
|
||||
const decValue = await cryptoFunctionService.aesDecryptFast(params);
|
||||
expect(decValue).toBe(value);
|
||||
@@ -264,8 +264,8 @@ describe("WebCrypto Function Service", () => {
|
||||
const key = makeStaticByteArray(32);
|
||||
const value = "EncryptMe!";
|
||||
const data = Utils.fromUtf8ToArray(value);
|
||||
const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
|
||||
const decValue = await cryptoFunctionService.aesDecrypt(encValue, iv.buffer, key.buffer);
|
||||
const encValue = new Uint8Array(await cryptoFunctionService.aesEncrypt(data, iv, key));
|
||||
const decValue = await cryptoFunctionService.aesDecrypt(encValue, iv, key);
|
||||
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
|
||||
});
|
||||
});
|
||||
@@ -273,8 +273,8 @@ describe("WebCrypto Function Service", () => {
|
||||
describe("aesDecryptFast", () => {
|
||||
it("should successfully decrypt data", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const iv = Utils.fromBufferToB64(makeStaticByteArray(16).buffer);
|
||||
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32).buffer);
|
||||
const iv = Utils.fromBufferToB64(makeStaticByteArray(16));
|
||||
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32));
|
||||
const data = "ByUF8vhyX4ddU9gcooznwA==";
|
||||
const params = cryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey);
|
||||
const decValue = await cryptoFunctionService.aesDecryptFast(params);
|
||||
@@ -288,7 +288,7 @@ describe("WebCrypto Function Service", () => {
|
||||
const iv = makeStaticByteArray(16);
|
||||
const key = makeStaticByteArray(32);
|
||||
const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA==");
|
||||
const decValue = await cryptoFunctionService.aesDecrypt(data.buffer, iv.buffer, key.buffer);
|
||||
const decValue = await cryptoFunctionService.aesDecrypt(data, iv, key);
|
||||
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
|
||||
});
|
||||
});
|
||||
@@ -300,8 +300,8 @@ describe("WebCrypto Function Service", () => {
|
||||
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
|
||||
const value = "EncryptMe!";
|
||||
const data = Utils.fromUtf8ToArray(value);
|
||||
const encValue = await cryptoFunctionService.rsaEncrypt(data.buffer, pubKey.buffer, "sha1");
|
||||
const decValue = await cryptoFunctionService.rsaDecrypt(encValue, privKey.buffer, "sha1");
|
||||
const encValue = new Uint8Array(await cryptoFunctionService.rsaEncrypt(data, pubKey, "sha1"));
|
||||
const decValue = await cryptoFunctionService.rsaDecrypt(encValue, privKey, "sha1");
|
||||
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
|
||||
});
|
||||
});
|
||||
@@ -316,7 +316,7 @@ describe("WebCrypto Function Service", () => {
|
||||
"zFOIEPF2S1zgperEP23M01mr4dWVdYN18B32YF67xdJHMbFhp5dkQwv9CmscoWq7OE5HIfOb+JAh7BEZb+CmKhM3yWJvoR/D" +
|
||||
"/5jcercUtK2o+XrzNrL4UQ7yLZcFz6Bfwb/j6ICYvqd/YJwXNE6dwlL57OfwJyCdw2rRYf0/qI00t9u8Iitw=="
|
||||
);
|
||||
const decValue = await cryptoFunctionService.rsaDecrypt(data.buffer, privKey.buffer, "sha1");
|
||||
const decValue = await cryptoFunctionService.rsaDecrypt(data, privKey, "sha1");
|
||||
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
|
||||
});
|
||||
});
|
||||
@@ -325,7 +325,7 @@ describe("WebCrypto Function Service", () => {
|
||||
it("should successfully extract key", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
|
||||
const publicKey = await cryptoFunctionService.rsaExtractPublicKey(privKey.buffer);
|
||||
const publicKey = await cryptoFunctionService.rsaExtractPublicKey(privKey);
|
||||
expect(Utils.fromBufferToB64(publicKey)).toBe(RsaPublicKey);
|
||||
});
|
||||
});
|
||||
@@ -390,8 +390,8 @@ function testPbkdf2(
|
||||
it("should create valid " + algorithm + " key from array buffer input", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const key = await cryptoFunctionService.pbkdf2(
|
||||
Utils.fromUtf8ToArray(regularPassword).buffer,
|
||||
Utils.fromUtf8ToArray(regularEmail).buffer,
|
||||
Utils.fromUtf8ToArray(regularPassword),
|
||||
Utils.fromUtf8ToArray(regularEmail),
|
||||
algorithm,
|
||||
5000
|
||||
);
|
||||
@@ -437,8 +437,8 @@ function testHkdf(
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const key = await cryptoFunctionService.hkdf(
|
||||
ikm,
|
||||
Utils.fromUtf8ToArray(regularSalt).buffer,
|
||||
Utils.fromUtf8ToArray(regularInfo).buffer,
|
||||
Utils.fromUtf8ToArray(regularSalt),
|
||||
Utils.fromUtf8ToArray(regularInfo),
|
||||
32,
|
||||
algorithm
|
||||
);
|
||||
@@ -496,10 +496,7 @@ function testHash(
|
||||
|
||||
it("should create valid " + algorithm + " hash from array buffer input", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const hash = await cryptoFunctionService.hash(
|
||||
Utils.fromUtf8ToArray(regularValue).buffer,
|
||||
algorithm
|
||||
);
|
||||
const hash = await cryptoFunctionService.hash(Utils.fromUtf8ToArray(regularValue), algorithm);
|
||||
expect(Utils.fromBufferToHex(hash)).toBe(regularHash);
|
||||
});
|
||||
}
|
||||
@@ -508,8 +505,8 @@ function testHmac(algorithm: "sha1" | "sha256" | "sha512", mac: string) {
|
||||
it("should create valid " + algorithm + " hmac", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const computedMac = await cryptoFunctionService.hmac(
|
||||
Utils.fromUtf8ToArray("SignMe!!").buffer,
|
||||
Utils.fromUtf8ToArray("secretkey").buffer,
|
||||
Utils.fromUtf8ToArray("SignMe!!"),
|
||||
Utils.fromUtf8ToArray("secretkey"),
|
||||
algorithm
|
||||
);
|
||||
expect(Utils.fromBufferToHex(computedMac)).toBe(mac);
|
||||
@@ -519,14 +516,14 @@ function testHmac(algorithm: "sha1" | "sha256" | "sha512", mac: string) {
|
||||
function testHmacFast(algorithm: "sha1" | "sha256" | "sha512", mac: string) {
|
||||
it("should create valid " + algorithm + " hmac", async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const keyByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray("secretkey").buffer);
|
||||
const dataByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray("SignMe!!").buffer);
|
||||
const keyByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray("secretkey"));
|
||||
const dataByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray("SignMe!!"));
|
||||
const computedMac = await cryptoFunctionService.hmacFast(
|
||||
dataByteString,
|
||||
keyByteString,
|
||||
algorithm
|
||||
);
|
||||
expect(Utils.fromBufferToHex(Utils.fromByteStringToArray(computedMac).buffer)).toBe(mac);
|
||||
expect(Utils.fromBufferToHex(Utils.fromByteStringToArray(computedMac))).toBe(mac);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -535,7 +532,9 @@ function testRsaGenerateKeyPair(length: 1024 | 2048 | 4096) {
|
||||
"should successfully generate a " + length + " bit key pair",
|
||||
async () => {
|
||||
const cryptoFunctionService = getWebCryptoFunctionService();
|
||||
const keyPair = await cryptoFunctionService.rsaGenerateKeyPair(length);
|
||||
const keyPair = (await cryptoFunctionService.rsaGenerateKeyPair(length)).map(
|
||||
(k) => new Uint8Array(k)
|
||||
);
|
||||
expect(keyPair[0] == null || keyPair[1] == null).toBe(false);
|
||||
const publicKey = await cryptoFunctionService.rsaExtractPublicKey(keyPair[1]);
|
||||
expect(Utils.fromBufferToB64(keyPair[0])).toBe(Utils.fromBufferToB64(publicKey));
|
||||
|
||||
@@ -20,11 +20,11 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
}
|
||||
|
||||
async pbkdf2(
|
||||
password: string | ArrayBuffer,
|
||||
salt: string | ArrayBuffer,
|
||||
password: string | Uint8Array,
|
||||
salt: string | Uint8Array,
|
||||
algorithm: "sha256" | "sha512",
|
||||
iterations: number
|
||||
): Promise<ArrayBuffer> {
|
||||
): Promise<Uint8Array> {
|
||||
const wcLen = algorithm === "sha256" ? 256 : 512;
|
||||
const passwordBuf = this.toBuf(password);
|
||||
const saltBuf = this.toBuf(salt);
|
||||
@@ -43,16 +43,17 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
false,
|
||||
["deriveBits"]
|
||||
);
|
||||
return await this.subtle.deriveBits(pbkdf2Params, impKey, wcLen);
|
||||
const buffer = await this.subtle.deriveBits(pbkdf2Params as any, impKey, wcLen);
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
async argon2(
|
||||
password: string | ArrayBuffer,
|
||||
salt: string | ArrayBuffer,
|
||||
password: string | Uint8Array,
|
||||
salt: string | Uint8Array,
|
||||
iterations: number,
|
||||
memory: number,
|
||||
parallelism: number
|
||||
): Promise<ArrayBuffer> {
|
||||
): Promise<Uint8Array> {
|
||||
if (!this.wasmSupported) {
|
||||
throw "Webassembly support is required for the Argon2 KDF feature.";
|
||||
}
|
||||
@@ -69,16 +70,17 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
hashLen: 32,
|
||||
type: argon2.ArgonType.Argon2id,
|
||||
});
|
||||
argon2.unloadRuntime();
|
||||
return result.hash;
|
||||
}
|
||||
|
||||
async hkdf(
|
||||
ikm: ArrayBuffer,
|
||||
salt: string | ArrayBuffer,
|
||||
info: string | ArrayBuffer,
|
||||
ikm: Uint8Array,
|
||||
salt: string | Uint8Array,
|
||||
info: string | Uint8Array,
|
||||
outputByteSize: number,
|
||||
algorithm: "sha256" | "sha512"
|
||||
): Promise<ArrayBuffer> {
|
||||
): Promise<Uint8Array> {
|
||||
const saltBuf = this.toBuf(salt);
|
||||
const infoBuf = this.toBuf(info);
|
||||
|
||||
@@ -92,16 +94,17 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
const impKey = await this.subtle.importKey("raw", ikm, { name: "HKDF" } as any, false, [
|
||||
"deriveBits",
|
||||
]);
|
||||
return await this.subtle.deriveBits(hkdfParams as any, impKey, outputByteSize * 8);
|
||||
const buffer = await this.subtle.deriveBits(hkdfParams as any, impKey, outputByteSize * 8);
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
// ref: https://tools.ietf.org/html/rfc5869
|
||||
async hkdfExpand(
|
||||
prk: ArrayBuffer,
|
||||
info: string | ArrayBuffer,
|
||||
prk: Uint8Array,
|
||||
info: string | Uint8Array,
|
||||
outputByteSize: number,
|
||||
algorithm: "sha256" | "sha512"
|
||||
): Promise<ArrayBuffer> {
|
||||
): Promise<Uint8Array> {
|
||||
const hashLen = algorithm === "sha256" ? 32 : 64;
|
||||
if (outputByteSize > 255 * hashLen) {
|
||||
throw new Error("outputByteSize is too large.");
|
||||
@@ -121,49 +124,54 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
t.set(previousT);
|
||||
t.set(infoArr, previousT.length);
|
||||
t.set([i + 1], t.length - 1);
|
||||
previousT = new Uint8Array(await this.hmac(t.buffer, prk, algorithm));
|
||||
previousT = new Uint8Array(await this.hmac(t, prk, algorithm));
|
||||
okm.set(previousT, runningOkmLength);
|
||||
runningOkmLength += previousT.length;
|
||||
if (runningOkmLength >= outputByteSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return okm.slice(0, outputByteSize).buffer;
|
||||
return okm.slice(0, outputByteSize);
|
||||
}
|
||||
|
||||
async hash(
|
||||
value: string | ArrayBuffer,
|
||||
value: string | Uint8Array,
|
||||
algorithm: "sha1" | "sha256" | "sha512" | "md5"
|
||||
): Promise<ArrayBuffer> {
|
||||
): Promise<Uint8Array> {
|
||||
if (algorithm === "md5") {
|
||||
const md = algorithm === "md5" ? forge.md.md5.create() : forge.md.sha1.create();
|
||||
const valueBytes = this.toByteString(value);
|
||||
md.update(valueBytes, "raw");
|
||||
return Utils.fromByteStringToArray(md.digest().data).buffer;
|
||||
return Utils.fromByteStringToArray(md.digest().data);
|
||||
}
|
||||
|
||||
const valueBuf = this.toBuf(value);
|
||||
return await this.subtle.digest({ name: this.toWebCryptoAlgorithm(algorithm) }, valueBuf);
|
||||
const buffer = await this.subtle.digest(
|
||||
{ name: this.toWebCryptoAlgorithm(algorithm) },
|
||||
valueBuf
|
||||
);
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
async hmac(
|
||||
value: ArrayBuffer,
|
||||
key: ArrayBuffer,
|
||||
value: Uint8Array,
|
||||
key: Uint8Array,
|
||||
algorithm: "sha1" | "sha256" | "sha512"
|
||||
): Promise<ArrayBuffer> {
|
||||
): Promise<Uint8Array> {
|
||||
const signingAlgorithm = {
|
||||
name: "HMAC",
|
||||
hash: { name: this.toWebCryptoAlgorithm(algorithm) },
|
||||
};
|
||||
|
||||
const impKey = await this.subtle.importKey("raw", key, signingAlgorithm, false, ["sign"]);
|
||||
return await this.subtle.sign(signingAlgorithm, impKey, value);
|
||||
const buffer = await this.subtle.sign(signingAlgorithm, impKey, value);
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
// Safely compare two values in a way that protects against timing attacks (Double HMAC Verification).
|
||||
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
|
||||
// ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
|
||||
async compare(a: ArrayBuffer, b: ArrayBuffer): Promise<boolean> {
|
||||
async compare(a: Uint8Array, b: Uint8Array): Promise<boolean> {
|
||||
const macKey = await this.randomBytes(32);
|
||||
const signingAlgorithm = {
|
||||
name: "HMAC",
|
||||
@@ -218,11 +226,12 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
return equals;
|
||||
}
|
||||
|
||||
async aesEncrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
async aesEncrypt(data: Uint8Array, iv: Uint8Array, key: Uint8Array): Promise<Uint8Array> {
|
||||
const impKey = await this.subtle.importKey("raw", key, { name: "AES-CBC" } as any, false, [
|
||||
"encrypt",
|
||||
]);
|
||||
return await this.subtle.encrypt({ name: "AES-CBC", iv: iv }, impKey, data);
|
||||
const buffer = await this.subtle.encrypt({ name: "AES-CBC", iv: iv }, impKey, data);
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
aesDecryptFastParameters(
|
||||
@@ -274,18 +283,19 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
return Promise.resolve(val);
|
||||
}
|
||||
|
||||
async aesDecrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
async aesDecrypt(data: Uint8Array, iv: Uint8Array, key: Uint8Array): Promise<Uint8Array> {
|
||||
const impKey = await this.subtle.importKey("raw", key, { name: "AES-CBC" } as any, false, [
|
||||
"decrypt",
|
||||
]);
|
||||
return await this.subtle.decrypt({ name: "AES-CBC", iv: iv }, impKey, data);
|
||||
const buffer = await this.subtle.decrypt({ name: "AES-CBC", iv: iv }, impKey, data);
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
async rsaEncrypt(
|
||||
data: ArrayBuffer,
|
||||
publicKey: ArrayBuffer,
|
||||
data: Uint8Array,
|
||||
publicKey: Uint8Array,
|
||||
algorithm: "sha1" | "sha256"
|
||||
): Promise<ArrayBuffer> {
|
||||
): Promise<Uint8Array> {
|
||||
// Note: Edge browser requires that we specify name and hash for both key import and decrypt.
|
||||
// We cannot use the proper types here.
|
||||
const rsaParams = {
|
||||
@@ -293,14 +303,15 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
hash: { name: this.toWebCryptoAlgorithm(algorithm) },
|
||||
};
|
||||
const impKey = await this.subtle.importKey("spki", publicKey, rsaParams, false, ["encrypt"]);
|
||||
return await this.subtle.encrypt(rsaParams, impKey, data);
|
||||
const buffer = await this.subtle.encrypt(rsaParams, impKey, data);
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
async rsaDecrypt(
|
||||
data: ArrayBuffer,
|
||||
privateKey: ArrayBuffer,
|
||||
data: Uint8Array,
|
||||
privateKey: Uint8Array,
|
||||
algorithm: "sha1" | "sha256"
|
||||
): Promise<ArrayBuffer> {
|
||||
): Promise<Uint8Array> {
|
||||
// Note: Edge browser requires that we specify name and hash for both key import and decrypt.
|
||||
// We cannot use the proper types here.
|
||||
const rsaParams = {
|
||||
@@ -308,10 +319,11 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
hash: { name: this.toWebCryptoAlgorithm(algorithm) },
|
||||
};
|
||||
const impKey = await this.subtle.importKey("pkcs8", privateKey, rsaParams, false, ["decrypt"]);
|
||||
return await this.subtle.decrypt(rsaParams, impKey, data);
|
||||
const buffer = await this.subtle.decrypt(rsaParams, impKey, data);
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
async rsaExtractPublicKey(privateKey: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
async rsaExtractPublicKey(privateKey: Uint8Array): Promise<Uint8Array> {
|
||||
const rsaParams = {
|
||||
name: "RSA-OAEP",
|
||||
// Have to specify some algorithm
|
||||
@@ -331,10 +343,11 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
const impPublicKey = await this.subtle.importKey("jwk", jwkPublicKeyParams, rsaParams, true, [
|
||||
"encrypt",
|
||||
]);
|
||||
return await this.subtle.exportKey("spki", impPublicKey);
|
||||
const buffer = await this.subtle.exportKey("spki", impPublicKey);
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[ArrayBuffer, ArrayBuffer]> {
|
||||
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[Uint8Array, Uint8Array]> {
|
||||
const rsaParams = {
|
||||
name: "RSA-OAEP",
|
||||
modulusLength: length,
|
||||
@@ -348,26 +361,26 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
])) as CryptoKeyPair;
|
||||
const publicKey = await this.subtle.exportKey("spki", keyPair.publicKey);
|
||||
const privateKey = await this.subtle.exportKey("pkcs8", keyPair.privateKey);
|
||||
return [publicKey, privateKey];
|
||||
return [new Uint8Array(publicKey), new Uint8Array(privateKey)];
|
||||
}
|
||||
|
||||
randomBytes(length: number): Promise<CsprngArray> {
|
||||
const arr = new Uint8Array(length);
|
||||
this.crypto.getRandomValues(arr);
|
||||
return Promise.resolve(arr.buffer as CsprngArray);
|
||||
return Promise.resolve(arr as CsprngArray);
|
||||
}
|
||||
|
||||
private toBuf(value: string | ArrayBuffer): ArrayBuffer {
|
||||
let buf: ArrayBuffer;
|
||||
private toBuf(value: string | Uint8Array): Uint8Array {
|
||||
let buf: Uint8Array;
|
||||
if (typeof value === "string") {
|
||||
buf = Utils.fromUtf8ToArray(value).buffer;
|
||||
buf = Utils.fromUtf8ToArray(value);
|
||||
} else {
|
||||
buf = value;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
private toByteString(value: string | ArrayBuffer): string {
|
||||
private toByteString(value: string | Uint8Array): string {
|
||||
let bytes: string;
|
||||
if (typeof value === "string") {
|
||||
bytes = forge.util.encodeUtf8(value);
|
||||
|
||||
@@ -133,6 +133,7 @@ import { Utils } from "../platform/misc/utils";
|
||||
import { AttachmentRequest } from "../vault/models/request/attachment.request";
|
||||
import { CipherBulkDeleteRequest } from "../vault/models/request/cipher-bulk-delete.request";
|
||||
import { CipherBulkMoveRequest } from "../vault/models/request/cipher-bulk-move.request";
|
||||
import { CipherBulkRestoreRequest } from "../vault/models/request/cipher-bulk-restore.request";
|
||||
import { CipherBulkShareRequest } from "../vault/models/request/cipher-bulk-share.request";
|
||||
import { CipherCollectionsRequest } from "../vault/models/request/cipher-collections.request";
|
||||
import { CipherCreateRequest } from "../vault/models/request/cipher-create.request";
|
||||
@@ -614,12 +615,19 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
}
|
||||
|
||||
async putRestoreManyCiphers(
|
||||
request: CipherBulkDeleteRequest
|
||||
request: CipherBulkRestoreRequest
|
||||
): Promise<ListResponse<CipherResponse>> {
|
||||
const r = await this.send("PUT", "/ciphers/restore", request, true, true);
|
||||
return new ListResponse<CipherResponse>(r, CipherResponse);
|
||||
}
|
||||
|
||||
async putRestoreManyCiphersAdmin(
|
||||
request: CipherBulkRestoreRequest
|
||||
): Promise<ListResponse<CipherResponse>> {
|
||||
const r = await this.send("PUT", "/ciphers/restore-admin", request, true, true);
|
||||
return new ListResponse<CipherResponse>(r, CipherResponse);
|
||||
}
|
||||
|
||||
// Attachments APIs
|
||||
|
||||
async getAttachmentData(
|
||||
@@ -881,7 +889,7 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
// Plan APIs
|
||||
|
||||
async getPlans(): Promise<ListResponse<PlanResponse>> {
|
||||
const r = await this.send("GET", "/plans/", null, false, true);
|
||||
const r = await this.send("GET", "/plans/all", null, false, true);
|
||||
return new ListResponse(r, PlanResponse);
|
||||
}
|
||||
|
||||
|
||||
@@ -57,10 +57,10 @@ describe("deviceCryptoService", () => {
|
||||
let makeDeviceKeySpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRandomBytes = new Uint8Array(deviceKeyBytesLength).buffer as CsprngArray;
|
||||
mockRandomBytes = new Uint8Array(deviceKeyBytesLength) as CsprngArray;
|
||||
mockDeviceKey = new SymmetricCryptoKey(mockRandomBytes);
|
||||
existingDeviceKey = new SymmetricCryptoKey(
|
||||
new Uint8Array(deviceKeyBytesLength).buffer as CsprngArray
|
||||
new Uint8Array(deviceKeyBytesLength) as CsprngArray
|
||||
) as DeviceKey;
|
||||
|
||||
stateSvcGetDeviceKeySpy = jest.spyOn(stateService, "getDeviceKey");
|
||||
@@ -97,7 +97,7 @@ describe("deviceCryptoService", () => {
|
||||
|
||||
describe("makeDeviceKey", () => {
|
||||
it("creates a new non-null 64 byte device key, securely stores it, and returns it", async () => {
|
||||
const mockRandomBytes = new Uint8Array(deviceKeyBytesLength).buffer as CsprngArray;
|
||||
const mockRandomBytes = new Uint8Array(deviceKeyBytesLength) as CsprngArray;
|
||||
|
||||
const cryptoFuncSvcRandomBytesSpy = jest
|
||||
.spyOn(cryptoFunctionService, "randomBytes")
|
||||
@@ -128,9 +128,9 @@ describe("deviceCryptoService", () => {
|
||||
let mockUserSymKey: SymmetricCryptoKey;
|
||||
|
||||
const deviceRsaKeyLength = 2048;
|
||||
let mockDeviceRsaKeyPair: [ArrayBuffer, ArrayBuffer];
|
||||
let mockDevicePrivateKey: ArrayBuffer;
|
||||
let mockDevicePublicKey: ArrayBuffer;
|
||||
let mockDeviceRsaKeyPair: [Uint8Array, Uint8Array];
|
||||
let mockDevicePrivateKey: Uint8Array;
|
||||
let mockDevicePublicKey: Uint8Array;
|
||||
let mockDevicePublicKeyEncryptedUserSymKey: EncString;
|
||||
let mockUserSymKeyEncryptedDevicePublicKey: EncString;
|
||||
let mockDeviceKeyEncryptedDevicePrivateKey: EncString;
|
||||
@@ -156,15 +156,15 @@ describe("deviceCryptoService", () => {
|
||||
beforeEach(() => {
|
||||
// Setup all spies and default return values for the happy path
|
||||
|
||||
mockDeviceKeyRandomBytes = new Uint8Array(deviceKeyBytesLength).buffer as CsprngArray;
|
||||
mockDeviceKeyRandomBytes = new Uint8Array(deviceKeyBytesLength) as CsprngArray;
|
||||
mockDeviceKey = new SymmetricCryptoKey(mockDeviceKeyRandomBytes) as DeviceKey;
|
||||
|
||||
mockUserSymKeyRandomBytes = new Uint8Array(userSymKeyBytesLength).buffer as CsprngArray;
|
||||
mockUserSymKeyRandomBytes = new Uint8Array(userSymKeyBytesLength) as CsprngArray;
|
||||
mockUserSymKey = new SymmetricCryptoKey(mockUserSymKeyRandomBytes);
|
||||
|
||||
mockDeviceRsaKeyPair = [
|
||||
new ArrayBuffer(deviceRsaKeyLength),
|
||||
new ArrayBuffer(deviceRsaKeyLength),
|
||||
new Uint8Array(deviceRsaKeyLength),
|
||||
new Uint8Array(deviceRsaKeyLength),
|
||||
];
|
||||
|
||||
mockDevicePublicKey = mockDeviceRsaKeyPair[0];
|
||||
|
||||
@@ -206,6 +206,19 @@ export class OrganizationUserServiceImplementation implements OrganizationUserSe
|
||||
return new ListResponse(r, OrganizationUserBulkResponse);
|
||||
}
|
||||
|
||||
async putOrganizationUserBulkEnableSecretsManager(
|
||||
organizationId: string,
|
||||
ids: string[]
|
||||
): Promise<void> {
|
||||
await this.apiService.send(
|
||||
"PUT",
|
||||
"/organizations/" + organizationId + "/users/enable-secrets-manager",
|
||||
new OrganizationUserBulkRequest(ids),
|
||||
true,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
putOrganizationUser(
|
||||
organizationId: string,
|
||||
id: string,
|
||||
|
||||
@@ -162,7 +162,7 @@ export class TotpService implements TotpServiceAbstraction {
|
||||
timeBytes: Uint8Array,
|
||||
alg: "sha1" | "sha256" | "sha512"
|
||||
) {
|
||||
const signature = await this.cryptoFunctionService.hmac(timeBytes.buffer, keyBytes.buffer, alg);
|
||||
const signature = await this.cryptoFunctionService.hmac(timeBytes, keyBytes, alg);
|
||||
return new Uint8Array(signature);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export class SendView implements View {
|
||||
accessId: string = null;
|
||||
name: string = null;
|
||||
notes: string = null;
|
||||
key: ArrayBuffer;
|
||||
key: Uint8Array;
|
||||
cryptoKey: SymmetricCryptoKey;
|
||||
type: SendType = null;
|
||||
text = new SendTextView();
|
||||
@@ -82,7 +82,7 @@ export class SendView implements View {
|
||||
}
|
||||
|
||||
return Object.assign(new SendView(), json, {
|
||||
key: Utils.fromB64ToArray(json.key)?.buffer,
|
||||
key: Utils.fromB64ToArray(json.key),
|
||||
cryptoKey: SymmetricCryptoKey.fromJSON(json.cryptoKey),
|
||||
text: SendTextView.fromJSON(json.text),
|
||||
file: SendFileView.fromJSON(json.file),
|
||||
|
||||
@@ -241,7 +241,7 @@ export class SendService implements InternalSendServiceAbstraction {
|
||||
key: SymmetricCryptoKey
|
||||
): Promise<[EncString, EncArrayBuffer]> {
|
||||
const encFileName = await this.cryptoService.encrypt(fileName, key);
|
||||
const encFileData = await this.cryptoService.encryptToBytes(data, key);
|
||||
const encFileData = await this.cryptoService.encryptToBytes(new Uint8Array(data), key);
|
||||
return [encFileName, encFileData];
|
||||
}
|
||||
|
||||
|
||||
2
libs/common/src/types/csprng.d.ts
vendored
2
libs/common/src/types/csprng.d.ts
vendored
@@ -4,6 +4,6 @@ import { Opaque } from "type-fest";
|
||||
// represents an array or string value generated from a
|
||||
// cryptographic secure pseudorandom number generator (CSPRNG)
|
||||
|
||||
type CsprngArray = Opaque<ArrayBuffer, "CSPRNG">;
|
||||
type CsprngArray = Opaque<Uint8Array, "CSPRNG">;
|
||||
|
||||
type CsprngString = Opaque<string, "CSPRNG">;
|
||||
|
||||
@@ -33,8 +33,8 @@ export abstract class CipherService {
|
||||
updateLastUsedDate: (id: string) => Promise<void>;
|
||||
updateLastLaunchedDate: (id: string) => Promise<void>;
|
||||
saveNeverDomain: (domain: string) => Promise<void>;
|
||||
createWithServer: (cipher: Cipher) => Promise<Cipher>;
|
||||
updateWithServer: (cipher: Cipher) => Promise<any>;
|
||||
createWithServer: (cipher: Cipher, orgAdmin?: boolean) => Promise<any>;
|
||||
updateWithServer: (cipher: Cipher, orgAdmin?: boolean, isNotClone?: boolean) => Promise<any>;
|
||||
shareWithServer: (
|
||||
cipher: CipherView,
|
||||
organizationId: string,
|
||||
@@ -76,5 +76,9 @@ export abstract class CipherService {
|
||||
cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[]
|
||||
) => Promise<any>;
|
||||
restoreWithServer: (id: string, asAdmin?: boolean) => Promise<any>;
|
||||
restoreManyWithServer: (ids: string[]) => Promise<any>;
|
||||
restoreManyWithServer: (
|
||||
ids: string[],
|
||||
organizationId?: string,
|
||||
asAdmin?: boolean
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
export class CipherBulkRestoreRequest {
|
||||
ids: string[];
|
||||
organizationId: string;
|
||||
|
||||
constructor(ids: string[]) {
|
||||
constructor(ids: string[], organizationId?: string) {
|
||||
this.ids = ids == null ? [] : ids;
|
||||
this.organizationId = organizationId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,106 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
import { mock, mockReset } from "jest-mock-extended";
|
||||
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { SearchService } from "../../abstractions/search.service";
|
||||
import { SettingsService } from "../../abstractions/settings.service";
|
||||
import { UriMatchType, FieldType } from "../../enums";
|
||||
import { CryptoService } from "../../platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "../../platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "../../platform/abstractions/i18n.service";
|
||||
import { StateService } from "../../platform/abstractions/state.service";
|
||||
import { EncArrayBuffer } from "../../platform/models/domain/enc-array-buffer";
|
||||
import { EncString } from "../../platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
|
||||
import { CipherFileUploadService } from "../abstractions/file-upload/cipher-file-upload.service";
|
||||
import { CipherRepromptType } from "../enums/cipher-reprompt-type";
|
||||
import { CipherType } from "../enums/cipher-type";
|
||||
import { CipherData } from "../models/data/cipher.data";
|
||||
import { Cipher } from "../models/domain/cipher";
|
||||
import { CipherCreateRequest } from "../models/request/cipher-create.request";
|
||||
import { CipherPartialRequest } from "../models/request/cipher-partial.request";
|
||||
import { CipherRequest } from "../models/request/cipher.request";
|
||||
|
||||
import { CipherService } from "./cipher.service";
|
||||
|
||||
const ENCRYPTED_TEXT = "This data has been encrypted";
|
||||
const ENCRYPTED_BYTES = Substitute.for<EncArrayBuffer>();
|
||||
const cipherData: CipherData = {
|
||||
id: "id",
|
||||
organizationId: "orgId",
|
||||
folderId: "folderId",
|
||||
edit: true,
|
||||
viewPassword: true,
|
||||
organizationUseTotp: true,
|
||||
favorite: false,
|
||||
revisionDate: "2022-01-31T12:00:00.000Z",
|
||||
type: CipherType.Login,
|
||||
name: "EncryptedString",
|
||||
notes: "EncryptedString",
|
||||
creationDate: "2022-01-01T12:00:00.000Z",
|
||||
deletedDate: null,
|
||||
reprompt: CipherRepromptType.None,
|
||||
login: {
|
||||
uris: [{ uri: "EncryptedString", match: UriMatchType.Domain }],
|
||||
username: "EncryptedString",
|
||||
password: "EncryptedString",
|
||||
passwordRevisionDate: "2022-01-31T12:00:00.000Z",
|
||||
totp: "EncryptedString",
|
||||
autofillOnPageLoad: false,
|
||||
},
|
||||
passwordHistory: [{ password: "EncryptedString", lastUsedDate: "2022-01-31T12:00:00.000Z" }],
|
||||
attachments: [
|
||||
{
|
||||
id: "a1",
|
||||
url: "url",
|
||||
size: "1100",
|
||||
sizeName: "1.1 KB",
|
||||
fileName: "file",
|
||||
key: "EncKey",
|
||||
},
|
||||
{
|
||||
id: "a2",
|
||||
url: "url",
|
||||
size: "1100",
|
||||
sizeName: "1.1 KB",
|
||||
fileName: "file",
|
||||
key: "EncKey",
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
name: "EncryptedString",
|
||||
value: "EncryptedString",
|
||||
type: FieldType.Text,
|
||||
linkedId: null,
|
||||
},
|
||||
{
|
||||
name: "EncryptedString",
|
||||
value: "EncryptedString",
|
||||
type: FieldType.Hidden,
|
||||
linkedId: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe("Cipher Service", () => {
|
||||
let cryptoService: SubstituteOf<CryptoService>;
|
||||
let stateService: SubstituteOf<StateService>;
|
||||
let settingsService: SubstituteOf<SettingsService>;
|
||||
let apiService: SubstituteOf<ApiService>;
|
||||
let cipherFileUploadService: SubstituteOf<CipherFileUploadService>;
|
||||
let i18nService: SubstituteOf<I18nService>;
|
||||
let searchService: SubstituteOf<SearchService>;
|
||||
let encryptService: SubstituteOf<EncryptService>;
|
||||
const cryptoService = mock<CryptoService>();
|
||||
const stateService = mock<StateService>();
|
||||
const settingsService = mock<SettingsService>();
|
||||
const apiService = mock<ApiService>();
|
||||
const cipherFileUploadService = mock<CipherFileUploadService>();
|
||||
const i18nService = mock<I18nService>();
|
||||
const searchService = mock<SearchService>();
|
||||
const encryptService = mock<EncryptService>();
|
||||
|
||||
let cipherService: CipherService;
|
||||
let cipherObj: Cipher;
|
||||
|
||||
beforeEach(() => {
|
||||
cryptoService = Substitute.for<CryptoService>();
|
||||
stateService = Substitute.for<StateService>();
|
||||
settingsService = Substitute.for<SettingsService>();
|
||||
apiService = Substitute.for<ApiService>();
|
||||
cipherFileUploadService = Substitute.for<CipherFileUploadService>();
|
||||
i18nService = Substitute.for<I18nService>();
|
||||
searchService = Substitute.for<SearchService>();
|
||||
encryptService = Substitute.for<EncryptService>();
|
||||
|
||||
cryptoService.encryptToBytes(Arg.any(), Arg.any()).resolves(ENCRYPTED_BYTES);
|
||||
cryptoService.encrypt(Arg.any(), Arg.any()).resolves(new EncString(ENCRYPTED_TEXT));
|
||||
mockReset(apiService);
|
||||
mockReset(cryptoService);
|
||||
mockReset(stateService);
|
||||
mockReset(settingsService);
|
||||
mockReset(cipherFileUploadService);
|
||||
mockReset(i18nService);
|
||||
mockReset(searchService);
|
||||
mockReset(encryptService);
|
||||
|
||||
cipherService = new CipherService(
|
||||
cryptoService,
|
||||
@@ -54,17 +112,97 @@ describe("Cipher Service", () => {
|
||||
encryptService,
|
||||
cipherFileUploadService
|
||||
);
|
||||
|
||||
cipherObj = new Cipher(cipherData);
|
||||
});
|
||||
describe("saveAttachmentRawWithServer()", () => {
|
||||
it("should upload encrypted file contents with save attachments", async () => {
|
||||
const fileName = "filename";
|
||||
const fileData = new Uint8Array(10).buffer;
|
||||
cryptoService.getOrgKey.mockReturnValue(
|
||||
Promise.resolve<any>(new SymmetricCryptoKey(new Uint8Array(32)))
|
||||
);
|
||||
cryptoService.makeEncKey.mockReturnValue(
|
||||
Promise.resolve<any>(new SymmetricCryptoKey(new Uint8Array(32)))
|
||||
);
|
||||
const spy = jest.spyOn(cipherFileUploadService, "upload");
|
||||
|
||||
await cipherService.saveAttachmentRawWithServer(new Cipher(), fileName, fileData);
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("attachments upload encrypted file contents", async () => {
|
||||
const fileName = "filename";
|
||||
const fileData = new Uint8Array(10).buffer;
|
||||
cryptoService.getOrgKey(Arg.any()).resolves(new SymmetricCryptoKey(new Uint8Array(32).buffer));
|
||||
describe("createWithServer()", () => {
|
||||
it("should call apiService.postCipherAdmin when orgAdmin param is true", async () => {
|
||||
const spy = jest
|
||||
.spyOn(apiService, "postCipherAdmin")
|
||||
.mockImplementation(() => Promise.resolve<any>(cipherObj));
|
||||
cipherService.createWithServer(cipherObj, true);
|
||||
const expectedObj = new CipherCreateRequest(cipherObj);
|
||||
|
||||
await cipherService.saveAttachmentRawWithServer(new Cipher(), fileName, fileData);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(spy).toHaveBeenCalledWith(expectedObj);
|
||||
});
|
||||
|
||||
cipherFileUploadService
|
||||
.received(1)
|
||||
.upload(Arg.any(), Arg.any(), ENCRYPTED_BYTES, Arg.any(), Arg.any());
|
||||
it("should call apiService.postCipherCreate if collectionsIds != null", async () => {
|
||||
cipherObj.collectionIds = ["123"];
|
||||
const spy = jest
|
||||
.spyOn(apiService, "postCipherCreate")
|
||||
.mockImplementation(() => Promise.resolve<any>(cipherObj));
|
||||
cipherService.createWithServer(cipherObj);
|
||||
const expectedObj = new CipherCreateRequest(cipherObj);
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(spy).toHaveBeenCalledWith(expectedObj);
|
||||
});
|
||||
|
||||
it("should call apiService.postCipher when orgAdmin and collectionIds logic is false", async () => {
|
||||
const spy = jest
|
||||
.spyOn(apiService, "postCipher")
|
||||
.mockImplementation(() => Promise.resolve<any>(cipherObj));
|
||||
cipherService.createWithServer(cipherObj);
|
||||
const expectedObj = new CipherRequest(cipherObj);
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(spy).toHaveBeenCalledWith(expectedObj);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateWithServer()", () => {
|
||||
it("should call apiService.putCipherAdmin when orgAdmin and isNotClone params are true", async () => {
|
||||
const spy = jest
|
||||
.spyOn(apiService, "putCipherAdmin")
|
||||
.mockImplementation(() => Promise.resolve<any>(cipherObj));
|
||||
cipherService.updateWithServer(cipherObj, true, true);
|
||||
const expectedObj = new CipherRequest(cipherObj);
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(spy).toHaveBeenCalledWith(cipherObj.id, expectedObj);
|
||||
});
|
||||
|
||||
it("should call apiService.putCipher if cipher.edit is true", async () => {
|
||||
cipherObj.edit = true;
|
||||
const spy = jest
|
||||
.spyOn(apiService, "putCipher")
|
||||
.mockImplementation(() => Promise.resolve<any>(cipherObj));
|
||||
cipherService.updateWithServer(cipherObj);
|
||||
const expectedObj = new CipherRequest(cipherObj);
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(spy).toHaveBeenCalledWith(cipherObj.id, expectedObj);
|
||||
});
|
||||
|
||||
it("should call apiService.putPartialCipher when orgAdmin, isNotClone, and edit are false", async () => {
|
||||
cipherObj.edit = false;
|
||||
const spy = jest
|
||||
.spyOn(apiService, "putPartialCipher")
|
||||
.mockImplementation(() => Promise.resolve<any>(cipherObj));
|
||||
cipherService.updateWithServer(cipherObj);
|
||||
const expectedObj = new CipherPartialRequest(cipherObj);
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(spy).toHaveBeenCalledWith(cipherObj.id, expectedObj);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -520,9 +520,12 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
await this.stateService.setNeverDomains(domains);
|
||||
}
|
||||
|
||||
async createWithServer(cipher: Cipher): Promise<any> {
|
||||
async createWithServer(cipher: Cipher, orgAdmin?: boolean): Promise<any> {
|
||||
let response: CipherResponse;
|
||||
if (cipher.collectionIds != null) {
|
||||
if (orgAdmin) {
|
||||
const request = new CipherCreateRequest(cipher);
|
||||
response = await this.apiService.postCipherAdmin(request);
|
||||
} else if (cipher.collectionIds != null) {
|
||||
const request = new CipherCreateRequest(cipher);
|
||||
response = await this.apiService.postCipherCreate(request);
|
||||
} else {
|
||||
@@ -535,9 +538,12 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
await this.upsert(data);
|
||||
}
|
||||
|
||||
async updateWithServer(cipher: Cipher): Promise<any> {
|
||||
async updateWithServer(cipher: Cipher, orgAdmin?: boolean, isNotClone?: boolean): Promise<any> {
|
||||
let response: CipherResponse;
|
||||
if (cipher.edit) {
|
||||
if (orgAdmin && isNotClone) {
|
||||
const request = new CipherRequest(cipher);
|
||||
response = await this.apiService.putCipherAdmin(cipher.id, request);
|
||||
} else if (cipher.edit) {
|
||||
const request = new CipherRequest(cipher);
|
||||
response = await this.apiService.putCipher(cipher.id, request);
|
||||
} else {
|
||||
@@ -638,7 +644,7 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
const encFileName = await this.cryptoService.encrypt(filename, key);
|
||||
|
||||
const dataEncKey = await this.cryptoService.makeEncKey(key);
|
||||
const encData = await this.cryptoService.encryptToBytes(data, dataEncKey[0]);
|
||||
const encData = await this.cryptoService.encryptToBytes(new Uint8Array(data), dataEncKey[0]);
|
||||
|
||||
const response = await this.cipherFileUploadService.upload(
|
||||
cipher,
|
||||
@@ -741,10 +747,11 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
}
|
||||
|
||||
async deleteManyWithServer(ids: string[], asAdmin = false): Promise<any> {
|
||||
const request = new CipherBulkDeleteRequest(ids);
|
||||
if (asAdmin) {
|
||||
await this.apiService.deleteManyCiphersAdmin(new CipherBulkDeleteRequest(ids));
|
||||
await this.apiService.deleteManyCiphersAdmin(request);
|
||||
} else {
|
||||
await this.apiService.deleteManyCiphers(new CipherBulkDeleteRequest(ids));
|
||||
await this.apiService.deleteManyCiphers(request);
|
||||
}
|
||||
await this.delete(ids);
|
||||
}
|
||||
@@ -880,10 +887,11 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
}
|
||||
|
||||
async softDeleteManyWithServer(ids: string[], asAdmin = false): Promise<any> {
|
||||
const request = new CipherBulkDeleteRequest(ids);
|
||||
if (asAdmin) {
|
||||
await this.apiService.putDeleteManyCiphersAdmin(new CipherBulkDeleteRequest(ids));
|
||||
await this.apiService.putDeleteManyCiphersAdmin(request);
|
||||
} else {
|
||||
await this.apiService.putDeleteManyCiphers(new CipherBulkDeleteRequest(ids));
|
||||
await this.apiService.putDeleteManyCiphers(request);
|
||||
}
|
||||
|
||||
await this.softDelete(ids);
|
||||
@@ -916,14 +924,30 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
}
|
||||
|
||||
async restoreWithServer(id: string, asAdmin = false): Promise<any> {
|
||||
const response = asAdmin
|
||||
? await this.apiService.putRestoreCipherAdmin(id)
|
||||
: await this.apiService.putRestoreCipher(id);
|
||||
let response;
|
||||
if (asAdmin) {
|
||||
response = await this.apiService.putRestoreCipherAdmin(id);
|
||||
} else {
|
||||
response = await this.apiService.putRestoreCipher(id);
|
||||
}
|
||||
|
||||
await this.restore({ id: id, revisionDate: response.revisionDate });
|
||||
}
|
||||
|
||||
async restoreManyWithServer(ids: string[]): Promise<any> {
|
||||
const response = await this.apiService.putRestoreManyCiphers(new CipherBulkRestoreRequest(ids));
|
||||
async restoreManyWithServer(
|
||||
ids: string[],
|
||||
organizationId: string = null,
|
||||
asAdmin = false
|
||||
): Promise<void> {
|
||||
let response;
|
||||
if (asAdmin) {
|
||||
const request = new CipherBulkRestoreRequest(ids, organizationId);
|
||||
response = await this.apiService.putRestoreManyCiphersAdmin(request);
|
||||
} else {
|
||||
const request = new CipherBulkRestoreRequest(ids);
|
||||
response = await this.apiService.putRestoreManyCiphers(request);
|
||||
}
|
||||
|
||||
const restores: { id: string; revisionDate: string }[] = [];
|
||||
for (const cipher of response.data) {
|
||||
restores.push({ id: cipher.id, revisionDate: cipher.revisionDate });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ApiService } from "../../../abstractions/api.service";
|
||||
import { SettingsService } from "../../../abstractions/settings.service";
|
||||
import { InternalOrganizationService } from "../../../admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { InternalOrganizationServiceAbstraction } from "../../../admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { InternalPolicyService } from "../../../admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { ProviderService } from "../../../admin-console/abstractions/provider.service";
|
||||
import { OrganizationData } from "../../../admin-console/models/data/organization.data";
|
||||
@@ -55,7 +55,7 @@ export class SyncService implements SyncServiceAbstraction {
|
||||
private stateService: StateService,
|
||||
private providerService: ProviderService,
|
||||
private folderApiService: FolderApiServiceAbstraction,
|
||||
private organizationService: InternalOrganizationService,
|
||||
private organizationService: InternalOrganizationServiceAbstraction,
|
||||
private sendApiService: SendApiService,
|
||||
private logoutCallback: (expired: boolean) => Promise<void>
|
||||
) {}
|
||||
|
||||
Reference in New Issue
Block a user