mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 09:13:33 +00:00
Merged with master and fixed conflicts
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { OrganizationConnectionType } from "../admin-console/enums/organization-connection-type";
|
||||
import { OrganizationConnectionType } from "../admin-console/enums";
|
||||
import { CollectionRequest } from "../admin-console/models/request/collection.request";
|
||||
import { OrganizationConnectionRequest } from "../admin-console/models/request/organization-connection.request";
|
||||
import { OrganizationSponsorshipCreateRequest } from "../admin-console/models/request/organization/organization-sponsorship-create.request";
|
||||
@@ -80,6 +80,7 @@ import { IdentityCaptchaResponse } from "../auth/models/response/identity-captch
|
||||
import { IdentityTokenResponse } from "../auth/models/response/identity-token.response";
|
||||
import { IdentityTwoFactorResponse } from "../auth/models/response/identity-two-factor.response";
|
||||
import { KeyConnectorUserKeyResponse } from "../auth/models/response/key-connector-user-key.response";
|
||||
import { MasterPasswordPolicyResponse } from "../auth/models/response/master-password-policy.response";
|
||||
import { PreloginResponse } from "../auth/models/response/prelogin.response";
|
||||
import { RegisterResponse } from "../auth/models/response/register.response";
|
||||
import { SsoPreValidateResponse } from "../auth/models/response/sso-pre-validate.response";
|
||||
@@ -187,7 +188,9 @@ export abstract class ApiService {
|
||||
postAccountKeys: (request: KeysRequest) => Promise<any>;
|
||||
postAccountVerifyEmail: () => Promise<any>;
|
||||
postAccountVerifyEmailToken: (request: VerifyEmailRequest) => Promise<any>;
|
||||
postAccountVerifyPassword: (request: SecretVerificationRequest) => Promise<any>;
|
||||
postAccountVerifyPassword: (
|
||||
request: SecretVerificationRequest
|
||||
) => Promise<MasterPasswordPolicyResponse>;
|
||||
postAccountRecoverDelete: (request: DeleteRecoverRequest) => Promise<any>;
|
||||
postAccountRecoverDeleteToken: (request: VerifyDeleteRecoverRequest) => Promise<any>;
|
||||
postAccountKdf: (request: KdfRequest) => Promise<any>;
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { FeatureFlag } from "../../enums/feature-flag.enum";
|
||||
|
||||
import { ServerConfig } from "./server-config";
|
||||
|
||||
export abstract class ConfigServiceAbstraction {
|
||||
serverConfig$: Observable<ServerConfig | null>;
|
||||
fetchServerConfig: () => Promise<ServerConfig>;
|
||||
getFeatureFlagBool: (key: FeatureFlag, defaultValue?: boolean) => Promise<boolean>;
|
||||
getFeatureFlagString: (key: FeatureFlag, defaultValue?: string) => Promise<string>;
|
||||
getFeatureFlagNumber: (key: FeatureFlag, defaultValue?: number) => Promise<number>;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export class ServerConfig {
|
||||
server?: ThirdPartyServerConfigData;
|
||||
environment?: EnvironmentServerConfigData;
|
||||
utcDate: Date;
|
||||
featureStates: { [key: string]: string } = {};
|
||||
|
||||
constructor(serverConfigData: ServerConfigData) {
|
||||
this.version = serverConfigData.version;
|
||||
@@ -22,6 +23,7 @@ export class ServerConfig {
|
||||
this.server = serverConfigData.server;
|
||||
this.utcDate = new Date(serverConfigData.utcDate);
|
||||
this.environment = serverConfigData.environment;
|
||||
this.featureStates = serverConfigData.featureStates;
|
||||
|
||||
if (this.server?.name == null && this.server?.url == null) {
|
||||
this.server = null;
|
||||
|
||||
@@ -2,9 +2,7 @@ import { ProfileOrganizationResponse } from "../admin-console/models/response/pr
|
||||
import { ProfileProviderOrganizationResponse } from "../admin-console/models/response/profile-provider-organization.response";
|
||||
import { ProfileProviderResponse } from "../admin-console/models/response/profile-provider.response";
|
||||
import { KdfConfig } from "../auth/models/domain/kdf-config";
|
||||
import { HashPurpose } from "../enums/hashPurpose";
|
||||
import { KdfType } from "../enums/kdfType";
|
||||
import { KeySuffixOptions } from "../enums/keySuffixOptions";
|
||||
import { HashPurpose, KdfType, KeySuffixOptions } from "../enums";
|
||||
import { EncArrayBuffer } from "../models/domain/enc-array-buffer";
|
||||
import { EncString } from "../models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { DecryptParameters } from "../models/domain/decrypt-parameters";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
||||
import { CsprngArray } from "../types/csprng";
|
||||
|
||||
export abstract class CryptoFunctionService {
|
||||
pbkdf2: (
|
||||
@@ -65,5 +66,5 @@ export abstract class CryptoFunctionService {
|
||||
) => Promise<ArrayBuffer>;
|
||||
rsaExtractPublicKey: (privateKey: ArrayBuffer) => Promise<ArrayBuffer>;
|
||||
rsaGenerateKeyPair: (length: 1024 | 2048 | 4096) => Promise<[ArrayBuffer, ArrayBuffer]>;
|
||||
randomBytes: (length: number) => Promise<ArrayBuffer>;
|
||||
randomBytes: (length: number) => Promise<CsprngArray>;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EventType } from "../../enums/eventType";
|
||||
import { EventType } from "../../enums";
|
||||
|
||||
export abstract class EventCollectionService {
|
||||
collect: (
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { EventView } from "../models/view/event.view";
|
||||
|
||||
export type ExportFormat = "csv" | "json" | "encrypted_json";
|
||||
|
||||
export abstract class ExportService {
|
||||
getExport: (format?: ExportFormat, organizationId?: string) => Promise<string>;
|
||||
getPasswordProtectedExport: (password: string, organizationId?: string) => Promise<string>;
|
||||
getOrganizationExport: (organizationId: string, format?: ExportFormat) => Promise<string>;
|
||||
getEventExport: (events: EventView[]) => Promise<string>;
|
||||
getFileName: (prefix?: string, extension?: string) => string;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FileUploadType } from "../../enums/fileUploadType";
|
||||
import { FileUploadType } from "../../enums";
|
||||
import { EncArrayBuffer } from "../../models/domain/enc-array-buffer";
|
||||
import { EncString } from "../../models/domain/enc-string";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LogLevelType } from "../enums/logLevelType";
|
||||
import { LogLevelType } from "../enums";
|
||||
|
||||
export abstract class LogService {
|
||||
debug: (message: string) => void;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ListResponse } from "../../models/response/list.response";
|
||||
|
||||
import {
|
||||
OrganizationUserAcceptInitRequest,
|
||||
OrganizationUserAcceptRequest,
|
||||
OrganizationUserBulkConfirmRequest,
|
||||
OrganizationUserConfirmRequest,
|
||||
@@ -14,7 +15,7 @@ import {
|
||||
OrganizationUserBulkPublicKeyResponse,
|
||||
OrganizationUserBulkResponse,
|
||||
OrganizationUserDetailsResponse,
|
||||
OrganizationUserResetPasswordDetailsReponse,
|
||||
OrganizationUserResetPasswordDetailsResponse,
|
||||
OrganizationUserUserDetailsResponse,
|
||||
} from "./responses";
|
||||
|
||||
@@ -64,7 +65,7 @@ export abstract class OrganizationUserService {
|
||||
abstract getOrganizationUserResetPasswordDetails(
|
||||
organizationId: string,
|
||||
id: string
|
||||
): Promise<OrganizationUserResetPasswordDetailsReponse>;
|
||||
): Promise<OrganizationUserResetPasswordDetailsResponse>;
|
||||
|
||||
/**
|
||||
* Create new organization user invite(s) for the specified organization
|
||||
@@ -94,6 +95,20 @@ export abstract class OrganizationUserService {
|
||||
ids: string[]
|
||||
): Promise<ListResponse<OrganizationUserBulkResponse>>;
|
||||
|
||||
/**
|
||||
* Accept an invitation to initialize and join an organization created via the Admin Portal **only**.
|
||||
* This is only used once for the initial Owner, because it also creates the organization's encryption keys.
|
||||
* This should not be used for organizations created via the Web client.
|
||||
* @param organizationId - Identifier for the organization to accept
|
||||
* @param id - Organization user identifier
|
||||
* @param request - Request details for accepting the invitation
|
||||
*/
|
||||
abstract postOrganizationUserAcceptInit(
|
||||
organizationId: string,
|
||||
id: string,
|
||||
request: OrganizationUserAcceptInitRequest
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Accept an organization user invitation
|
||||
* @param organizationId - Identifier for the organization to accept
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./organization-user-accept-init.request";
|
||||
export * from "./organization-user-accept.request";
|
||||
export * from "./organization-user-bulk-confirm.request";
|
||||
export * from "./organization-user-confirm.request";
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { OrganizationKeysRequest } from "../../../admin-console/models/request/organization-keys.request";
|
||||
|
||||
export class OrganizationUserAcceptInitRequest {
|
||||
token: string;
|
||||
key: string;
|
||||
keys: OrganizationKeysRequest;
|
||||
collectionName: string;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OrganizationUserType } from "../../../admin-console/enums/organization-user-type";
|
||||
import { OrganizationUserType } from "../../../admin-console/enums";
|
||||
import { PermissionsApi } from "../../../admin-console/models/api/permissions.api";
|
||||
import { SelectionReadOnlyRequest } from "../../../admin-console/models/request/selection-read-only.request";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OrganizationUserType } from "../../../admin-console/enums/organization-user-type";
|
||||
import { OrganizationUserType } from "../../../admin-console/enums";
|
||||
import { PermissionsApi } from "../../../admin-console/models/api/permissions.api";
|
||||
import { SelectionReadOnlyRequest } from "../../../admin-console/models/request/selection-read-only.request";
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { OrganizationUserStatusType } from "../../../admin-console/enums/organization-user-status-type";
|
||||
import { OrganizationUserType } from "../../../admin-console/enums/organization-user-type";
|
||||
import { OrganizationUserStatusType, OrganizationUserType } from "../../../admin-console/enums";
|
||||
import { PermissionsApi } from "../../../admin-console/models/api/permissions.api";
|
||||
import { SelectionReadOnlyResponse } from "../../../admin-console/models/response/selection-read-only.response";
|
||||
import { KdfType } from "../../../enums/kdfType";
|
||||
import { KdfType } from "../../../enums";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class OrganizationUserResponse extends BaseResponse {
|
||||
@@ -64,7 +63,7 @@ export class OrganizationUserDetailsResponse extends OrganizationUserResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export class OrganizationUserResetPasswordDetailsReponse extends BaseResponse {
|
||||
export class OrganizationUserResetPasswordDetailsResponse extends BaseResponse {
|
||||
kdf: KdfType;
|
||||
kdfIterations: number;
|
||||
kdfMemory?: number;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ClientType } from "../enums/clientType";
|
||||
import { DeviceType } from "../enums/deviceType";
|
||||
import { ClientType, DeviceType } from "../enums";
|
||||
|
||||
interface ToastOptions {
|
||||
timeout?: number;
|
||||
@@ -28,15 +27,6 @@ export abstract class PlatformUtilsService {
|
||||
text: string | string[],
|
||||
options?: ToastOptions
|
||||
) => void;
|
||||
showDialog: (
|
||||
body: string,
|
||||
title?: string,
|
||||
confirmText?: string,
|
||||
cancelText?: string,
|
||||
type?: string,
|
||||
bodyIsHtml?: boolean,
|
||||
target?: string
|
||||
) => Promise<boolean>;
|
||||
isDev: () => boolean;
|
||||
isSelfHost: () => boolean;
|
||||
copyToClipboard: (text: string, options?: any) => void | boolean;
|
||||
|
||||
@@ -5,7 +5,7 @@ export abstract class SearchService {
|
||||
indexedEntityId?: string = null;
|
||||
clearIndex: () => void;
|
||||
isSearchable: (query: string) => boolean;
|
||||
indexCiphers: (indexedEntityGuid?: string, ciphersToIndex?: CipherView[]) => Promise<void>;
|
||||
indexCiphers: (ciphersToIndex: CipherView[], indexedEntityGuid?: string) => void;
|
||||
searchCiphers: (
|
||||
query: string,
|
||||
filter?: ((cipher: CipherView) => boolean) | ((cipher: CipherView) => boolean)[],
|
||||
|
||||
@@ -4,8 +4,11 @@ import { AccountSettingsSettings } from "../models/domain/account";
|
||||
|
||||
export abstract class SettingsService {
|
||||
settings$: Observable<AccountSettingsSettings>;
|
||||
disableFavicon$: Observable<boolean>;
|
||||
|
||||
setEquivalentDomains: (equivalentDomains: string[][]) => Promise<any>;
|
||||
getEquivalentDomains: (url: string) => string[];
|
||||
getEquivalentDomains: (url: string) => Set<string>;
|
||||
setDisableFavicon: (value: boolean) => Promise<any>;
|
||||
getDisableFavicon: () => boolean;
|
||||
clear: (userId?: string) => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ import { ProviderData } from "../admin-console/models/data/provider.data";
|
||||
import { Policy } from "../admin-console/models/domain/policy";
|
||||
import { CollectionView } from "../admin-console/models/view/collection.view";
|
||||
import { EnvironmentUrls } from "../auth/models/domain/environment-urls";
|
||||
import { ForceResetPasswordReason } from "../auth/models/domain/force-reset-password-reason";
|
||||
import { KdfConfig } from "../auth/models/domain/kdf-config";
|
||||
import { KdfType } from "../enums/kdfType";
|
||||
import { ThemeType } from "../enums/themeType";
|
||||
import { UriMatchType } from "../enums/uriMatchType";
|
||||
import { BiometricKey } from "../auth/types/biometric-key";
|
||||
import { KdfType, ThemeType, UriMatchType } from "../enums";
|
||||
import { EventData } from "../models/data/event.data";
|
||||
import { ServerConfigData } from "../models/data/server-config.data";
|
||||
import { Account, AccountSettingsSettings } from "../models/domain/account";
|
||||
@@ -79,7 +79,7 @@ export abstract class StateService<T extends Account = Account> {
|
||||
setCryptoMasterKeyB64: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise<string>;
|
||||
hasCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise<boolean>;
|
||||
setCryptoMasterKeyBiometric: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
setCryptoMasterKeyBiometric: (value: BiometricKey, options?: StorageOptions) => Promise<void>;
|
||||
getDecryptedCiphers: (options?: StorageOptions) => Promise<CipherView[]>;
|
||||
setDecryptedCiphers: (value: CipherView[], options?: StorageOptions) => Promise<void>;
|
||||
getDecryptedCollections: (options?: StorageOptions) => Promise<CollectionView[]>;
|
||||
@@ -145,7 +145,13 @@ export abstract class StateService<T extends Account = Account> {
|
||||
) => Promise<void>;
|
||||
getDisableContextMenuItem: (options?: StorageOptions) => Promise<boolean>;
|
||||
setDisableContextMenuItem: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
/**
|
||||
* @deprecated Do not call this, use SettingsService
|
||||
*/
|
||||
getDisableFavicon: (options?: StorageOptions) => Promise<boolean>;
|
||||
/**
|
||||
* @deprecated Do not call this, use SettingsService
|
||||
*/
|
||||
setDisableFavicon: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getDisableGa: (options?: StorageOptions) => Promise<boolean>;
|
||||
setDisableGa: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
@@ -165,8 +171,6 @@ export abstract class StateService<T extends Account = Account> {
|
||||
setEnableAlwaysOnTop: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getEnableAutoFillOnPageLoad: (options?: StorageOptions) => Promise<boolean>;
|
||||
setEnableAutoFillOnPageLoad: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getEnableBiometric: (options?: StorageOptions) => Promise<boolean>;
|
||||
setEnableBiometric: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getEnableBrowserIntegration: (options?: StorageOptions) => Promise<boolean>;
|
||||
setEnableBrowserIntegration: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getEnableBrowserIntegrationFingerprint: (options?: StorageOptions) => Promise<boolean>;
|
||||
@@ -263,8 +267,11 @@ export abstract class StateService<T extends Account = Account> {
|
||||
setEventCollection: (value: EventData[], options?: StorageOptions) => Promise<void>;
|
||||
getEverBeenUnlocked: (options?: StorageOptions) => Promise<boolean>;
|
||||
setEverBeenUnlocked: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getForcePasswordReset: (options?: StorageOptions) => Promise<boolean>;
|
||||
setForcePasswordReset: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getForcePasswordResetReason: (options?: StorageOptions) => Promise<ForceResetPasswordReason>;
|
||||
setForcePasswordResetReason: (
|
||||
value: ForceResetPasswordReason,
|
||||
options?: StorageOptions
|
||||
) => Promise<void>;
|
||||
getInstalledVersion: (options?: StorageOptions) => Promise<string>;
|
||||
setInstalledVersion: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getIsAuthenticated: (options?: StorageOptions) => Promise<boolean>;
|
||||
@@ -291,14 +298,14 @@ export abstract class StateService<T extends Account = Account> {
|
||||
setMinimizeOnCopyToClipboard: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getNeverDomains: (options?: StorageOptions) => Promise<{ [id: string]: any }>;
|
||||
setNeverDomains: (value: { [id: string]: any }, options?: StorageOptions) => Promise<void>;
|
||||
getNoAutoPromptBiometrics: (options?: StorageOptions) => Promise<boolean>;
|
||||
setNoAutoPromptBiometrics: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getNoAutoPromptBiometricsText: (options?: StorageOptions) => Promise<string>;
|
||||
setNoAutoPromptBiometricsText: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getOpenAtLogin: (options?: StorageOptions) => Promise<boolean>;
|
||||
setOpenAtLogin: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getOrganizationInvitation: (options?: StorageOptions) => Promise<any>;
|
||||
setOrganizationInvitation: (value: any, options?: StorageOptions) => Promise<void>;
|
||||
getEmergencyAccessInvitation: (options?: StorageOptions) => Promise<any>;
|
||||
setEmergencyAccessInvitation: (value: any, options?: StorageOptions) => Promise<void>;
|
||||
/**
|
||||
* @deprecated Do not call this directly, use OrganizationService
|
||||
*/
|
||||
@@ -348,7 +355,7 @@ export abstract class StateService<T extends Account = Account> {
|
||||
setTwoFactorToken: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getUserId: (options?: StorageOptions) => Promise<string>;
|
||||
getUsesKeyConnector: (options?: StorageOptions) => Promise<boolean>;
|
||||
setUsesKeyConnector: (vaule: boolean, options?: StorageOptions) => Promise<void>;
|
||||
setUsesKeyConnector: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getVaultTimeout: (options?: StorageOptions) => Promise<number>;
|
||||
setVaultTimeout: (value: number, options?: StorageOptions) => Promise<void>;
|
||||
getVaultTimeoutAction: (options?: StorageOptions) => Promise<string>;
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum";
|
||||
|
||||
export abstract class VaultTimeoutSettingsService {
|
||||
setVaultTimeoutOptions: (vaultTimeout: number, vaultTimeoutAction: string) => Promise<void>;
|
||||
setVaultTimeoutOptions: (
|
||||
vaultTimeout: number,
|
||||
vaultTimeoutAction: VaultTimeoutAction
|
||||
) => Promise<void>;
|
||||
getVaultTimeout: (userId?: string) => Promise<number>;
|
||||
getVaultTimeoutAction: (userId?: string) => Promise<VaultTimeoutAction>;
|
||||
isPinLockSet: () => Promise<[boolean, boolean]>;
|
||||
isBiometricLockSet: () => Promise<boolean>;
|
||||
clear: (userId?: string) => Promise<void>;
|
||||
|
||||
@@ -15,7 +15,7 @@ import { SeatRequest } from "../../../models/request/seat.request";
|
||||
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/organization-api-key-type";
|
||||
import { OrganizationApiKeyType } from "../../enums";
|
||||
import { OrganizationCreateRequest } from "../../models/request/organization-create.request";
|
||||
import { OrganizationKeysRequest } from "../../models/request/organization-keys.request";
|
||||
import { OrganizationUpdateRequest } from "../../models/request/organization-update.request";
|
||||
|
||||
@@ -56,13 +56,22 @@ export function canAccessAdmin(i18nService: I18nService) {
|
||||
);
|
||||
}
|
||||
|
||||
export function isNotProviderUser(org: Organization): boolean {
|
||||
return !org.isProviderUser;
|
||||
/**
|
||||
* Returns `true` if a user is a member of an organization (rather than only being a ProviderUser)
|
||||
* @deprecated Use organizationService.memberOrganizations$ instead
|
||||
*/
|
||||
export function isMember(org: Organization): boolean {
|
||||
return org.isMember;
|
||||
}
|
||||
|
||||
export abstract class OrganizationService {
|
||||
organizations$: Observable<Organization[]>;
|
||||
|
||||
/**
|
||||
* Organizations that the user is a member of (excludes organizations that they only have access to via a provider)
|
||||
*/
|
||||
memberOrganizations$: Observable<Organization[]>;
|
||||
|
||||
get$: (id: string) => Observable<Organization | undefined>;
|
||||
get: (id: string) => Organization;
|
||||
getByIdentifier: (identifier: string) => Organization;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ListResponse } from "../../../models/response/list.response";
|
||||
import { PolicyType } from "../../enums/policy-type";
|
||||
import { PolicyType } from "../../enums";
|
||||
import { MasterPasswordPolicyOptions } from "../../models/domain/master-password-policy-options";
|
||||
import { PolicyRequest } from "../../models/request/policy.request";
|
||||
import { PolicyResponse } from "../../models/response/policy.response";
|
||||
@@ -7,6 +7,7 @@ import { PolicyResponse } from "../../models/response/policy.response";
|
||||
export class PolicyApiServiceAbstraction {
|
||||
getPolicy: (organizationId: string, type: PolicyType) => Promise<PolicyResponse>;
|
||||
getPolicies: (organizationId: string) => Promise<ListResponse<PolicyResponse>>;
|
||||
|
||||
getPoliciesByToken: (
|
||||
organizationId: string,
|
||||
token: string,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { ListResponse } from "../../../models/response/list.response";
|
||||
import { PolicyType } from "../../enums/policy-type";
|
||||
import { PolicyType } from "../../enums";
|
||||
import { PolicyData } from "../../models/data/policy.data";
|
||||
import { MasterPasswordPolicyOptions } from "../../models/domain/master-password-policy-options";
|
||||
import { Policy } from "../../models/domain/policy";
|
||||
@@ -10,6 +10,7 @@ import { PolicyResponse } from "../../models/response/policy.response";
|
||||
|
||||
export abstract class PolicyService {
|
||||
policies$: Observable<Policy[]>;
|
||||
get$: (policyType: PolicyType, policyFilter?: (policy: Policy) => boolean) => Observable<Policy>;
|
||||
masterPasswordPolicyOptions$: (policies?: Policy[]) => Observable<MasterPasswordPolicyOptions>;
|
||||
policyAppliesToActiveUser$: (
|
||||
policyType: PolicyType,
|
||||
|
||||
8
libs/common/src/admin-console/enums/index.ts
Normal file
8
libs/common/src/admin-console/enums/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export * from "./organization-api-key-type.enum";
|
||||
export * from "./organization-connection-type.enum";
|
||||
export * from "./organization-user-status-type.enum";
|
||||
export * from "./organization-user-type.enum";
|
||||
export * from "./policy-type.enum";
|
||||
export * from "./provider-user-status-type.enum";
|
||||
export * from "./provider-user-type.enum";
|
||||
export * from "./scim-provider-type.enum";
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { ScimProviderType } from "../../enums/scim-provider-type";
|
||||
import { ScimProviderType } from "../../enums";
|
||||
|
||||
export class ScimConfigApi extends BaseResponse {
|
||||
enabled: boolean;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ProductType } from "../../../enums/productType";
|
||||
import { OrganizationUserStatusType } from "../../enums/organization-user-status-type";
|
||||
import { OrganizationUserType } from "../../enums/organization-user-type";
|
||||
import { ProductType, ProviderType } from "../../../enums";
|
||||
import { OrganizationUserStatusType, OrganizationUserType } from "../../enums";
|
||||
import { PermissionsApi } from "../api/permissions.api";
|
||||
import { ProfileOrganizationResponse } from "../response/profile-organization.response";
|
||||
|
||||
@@ -37,7 +36,9 @@ export class OrganizationData {
|
||||
hasPublicAndPrivateKeys: boolean;
|
||||
providerId: string;
|
||||
providerName: string;
|
||||
providerType?: ProviderType;
|
||||
isProviderUser: boolean;
|
||||
isMember: boolean;
|
||||
familySponsorshipFriendlyName: string;
|
||||
familySponsorshipAvailable: boolean;
|
||||
planProductType: ProductType;
|
||||
@@ -48,7 +49,13 @@ export class OrganizationData {
|
||||
familySponsorshipToDelete?: boolean;
|
||||
accessSecretsManager: boolean;
|
||||
|
||||
constructor(response: ProfileOrganizationResponse) {
|
||||
constructor(
|
||||
response: ProfileOrganizationResponse,
|
||||
options: {
|
||||
isMember: boolean;
|
||||
isProviderUser: boolean;
|
||||
}
|
||||
) {
|
||||
this.id = response.id;
|
||||
this.name = response.name;
|
||||
this.status = response.status;
|
||||
@@ -81,6 +88,7 @@ export class OrganizationData {
|
||||
this.hasPublicAndPrivateKeys = response.hasPublicAndPrivateKeys;
|
||||
this.providerId = response.providerId;
|
||||
this.providerName = response.providerName;
|
||||
this.providerType = response.providerType;
|
||||
this.familySponsorshipFriendlyName = response.familySponsorshipFriendlyName;
|
||||
this.familySponsorshipAvailable = response.familySponsorshipAvailable;
|
||||
this.planProductType = response.planProductType;
|
||||
@@ -90,5 +98,8 @@ export class OrganizationData {
|
||||
this.familySponsorshipValidUntil = response.familySponsorshipValidUntil;
|
||||
this.familySponsorshipToDelete = response.familySponsorshipToDelete;
|
||||
this.accessSecretsManager = response.accessSecretsManager;
|
||||
|
||||
this.isMember = options.isMember;
|
||||
this.isProviderUser = options.isProviderUser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PolicyType } from "../../enums/policy-type";
|
||||
import { PolicyType } from "../../enums";
|
||||
import { PolicyResponse } from "../response/policy.response";
|
||||
|
||||
export class PolicyData {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ProviderUserStatusType } from "../../enums/provider-user-status-type";
|
||||
import { ProviderUserType } from "../../enums/provider-user-type";
|
||||
import { ProviderUserStatusType, ProviderUserType } from "../../enums";
|
||||
import { ProfileProviderResponse } from "../response/profile-provider.response";
|
||||
|
||||
export class ProviderData {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { MasterPasswordPolicyResponse } from "../../../auth/models/response/master-password-policy.response";
|
||||
import Domain from "../../../models/domain/domain-base";
|
||||
|
||||
export class MasterPasswordPolicyOptions extends Domain {
|
||||
@@ -7,4 +8,26 @@ export class MasterPasswordPolicyOptions extends Domain {
|
||||
requireLower = false;
|
||||
requireNumbers = false;
|
||||
requireSpecial = false;
|
||||
|
||||
/**
|
||||
* Flag to indicate if the policy should be enforced on login.
|
||||
* If true, and the user's password does not meet the policy requirements,
|
||||
* the user will be forced to update their password.
|
||||
*/
|
||||
enforceOnLogin = false;
|
||||
|
||||
static fromResponse(policy: MasterPasswordPolicyResponse): MasterPasswordPolicyOptions {
|
||||
if (policy == null) {
|
||||
return null;
|
||||
}
|
||||
const options = new MasterPasswordPolicyOptions();
|
||||
options.minComplexity = policy.minComplexity;
|
||||
options.minLength = policy.minLength;
|
||||
options.requireUpper = policy.requireUpper;
|
||||
options.requireLower = policy.requireLower;
|
||||
options.requireNumbers = policy.requireNumbers;
|
||||
options.requireSpecial = policy.requireSpecial;
|
||||
options.enforceOnLogin = policy.enforceOnLogin;
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { ProductType } from "../../../enums/productType";
|
||||
import { OrganizationUserStatusType } from "../../enums/organization-user-status-type";
|
||||
import { OrganizationUserType } from "../../enums/organization-user-type";
|
||||
import { ProductType, ProviderType } from "../../../enums";
|
||||
import { OrganizationUserStatusType, OrganizationUserType } from "../../enums";
|
||||
import { PermissionsApi } from "../api/permissions.api";
|
||||
import { OrganizationData } from "../data/organization.data";
|
||||
|
||||
@@ -10,7 +9,14 @@ export class Organization {
|
||||
id: string;
|
||||
name: string;
|
||||
status: OrganizationUserStatusType;
|
||||
|
||||
/**
|
||||
* The member's role in the organization.
|
||||
* Avoid using this for permission checks - use the getters instead (e.g. isOwner, isAdmin, canManageX), because they
|
||||
* properly handle permission inheritance and relationships.
|
||||
*/
|
||||
type: OrganizationUserType;
|
||||
|
||||
enabled: boolean;
|
||||
usePolicies: boolean;
|
||||
useGroups: boolean;
|
||||
@@ -39,7 +45,15 @@ export class Organization {
|
||||
hasPublicAndPrivateKeys: boolean;
|
||||
providerId: string;
|
||||
providerName: string;
|
||||
providerType?: ProviderType;
|
||||
/**
|
||||
* Indicates that a user is a ProviderUser for the organization
|
||||
*/
|
||||
isProviderUser: boolean;
|
||||
/**
|
||||
* Indicates that a user is a member for the organization (may be `false` if they have access via a Provider only)
|
||||
*/
|
||||
isMember: boolean;
|
||||
familySponsorshipFriendlyName: string;
|
||||
familySponsorshipAvailable: boolean;
|
||||
planProductType: ProductType;
|
||||
@@ -87,7 +101,9 @@ export class Organization {
|
||||
this.hasPublicAndPrivateKeys = obj.hasPublicAndPrivateKeys;
|
||||
this.providerId = obj.providerId;
|
||||
this.providerName = obj.providerName;
|
||||
this.providerType = obj.providerType;
|
||||
this.isProviderUser = obj.isProviderUser;
|
||||
this.isMember = obj.isMember;
|
||||
this.familySponsorshipFriendlyName = obj.familySponsorshipFriendlyName;
|
||||
this.familySponsorshipAvailable = obj.familySponsorshipAvailable;
|
||||
this.planProductType = obj.planProductType;
|
||||
@@ -100,24 +116,29 @@ export class Organization {
|
||||
}
|
||||
|
||||
get canAccess() {
|
||||
if (this.type === OrganizationUserType.Owner) {
|
||||
if (this.isOwner) {
|
||||
return true;
|
||||
}
|
||||
return this.enabled && this.status === OrganizationUserStatusType.Confirmed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a user has Manager permissions or greater
|
||||
*/
|
||||
get isManager() {
|
||||
return (
|
||||
this.type === OrganizationUserType.Manager ||
|
||||
this.type === OrganizationUserType.Owner ||
|
||||
this.type === OrganizationUserType.Admin
|
||||
);
|
||||
return this.type === OrganizationUserType.Manager || this.isAdmin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a user has Admin permissions or greater
|
||||
*/
|
||||
get isAdmin() {
|
||||
return this.type === OrganizationUserType.Owner || this.type === OrganizationUserType.Admin;
|
||||
return this.type === OrganizationUserType.Admin || this.isOwner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a user has Owner permissions (including ProviderUsers)
|
||||
*/
|
||||
get isOwner() {
|
||||
return this.type === OrganizationUserType.Owner || this.isProviderUser;
|
||||
}
|
||||
@@ -198,8 +219,26 @@ export class Organization {
|
||||
return this.canManagePolicies;
|
||||
}
|
||||
|
||||
get canManageBilling() {
|
||||
return this.isOwner && (this.isProviderUser || !this.hasProvider);
|
||||
get canViewSubscription() {
|
||||
if (this.canEditSubscription) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.hasProvider && this.providerType === ProviderType.Msp
|
||||
? this.isProviderUser
|
||||
: this.isOwner;
|
||||
}
|
||||
|
||||
get canEditSubscription() {
|
||||
return this.hasProvider ? this.isProviderUser : this.isOwner;
|
||||
}
|
||||
|
||||
get canEditPaymentMethods() {
|
||||
return this.canEditSubscription;
|
||||
}
|
||||
|
||||
get canViewBillingHistory() {
|
||||
return this.canEditSubscription;
|
||||
}
|
||||
|
||||
get hasProvider() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Domain from "../../../models/domain/domain-base";
|
||||
import { PolicyType } from "../../enums/policy-type";
|
||||
import { PolicyType } from "../../enums";
|
||||
import { PolicyData } from "../data/policy.data";
|
||||
|
||||
export class Policy extends Domain {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Collection } from "../domain/collection";
|
||||
import { CollectionRequest } from "../request/collection.request";
|
||||
|
||||
export class CollectionWithIdRequest extends CollectionRequest {
|
||||
id: string;
|
||||
|
||||
constructor(collection?: Collection) {
|
||||
if (collection == null) {
|
||||
return;
|
||||
}
|
||||
super(collection);
|
||||
this.id = collection.id;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request";
|
||||
import { OrganizationApiKeyType } from "../../enums/organization-api-key-type";
|
||||
import { OrganizationApiKeyType } from "../../enums";
|
||||
|
||||
export class OrganizationApiKeyRequest extends SecretVerificationRequest {
|
||||
type: OrganizationApiKeyType = OrganizationApiKeyType.Default;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BillingSyncConfigRequest } from "../../../billing/models/request/billing-sync-config.request";
|
||||
import { OrganizationConnectionType } from "../../enums/organization-connection-type";
|
||||
import { OrganizationConnectionType } from "../../enums";
|
||||
|
||||
import { ScimConfigRequest } from "./scim-config.request";
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { PaymentMethodType } from "../../../billing/enums/payment-method-type";
|
||||
import { PlanType } from "../../../billing/enums/plan-type";
|
||||
import { PaymentMethodType, PlanType } from "../../../billing/enums";
|
||||
|
||||
import { OrganizationKeysRequest } from "./organization-keys.request";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PlanType } from "../../../billing/enums/plan-type";
|
||||
import { PlanType } from "../../../billing/enums";
|
||||
|
||||
import { OrganizationKeysRequest } from "./organization-keys.request";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PlanSponsorshipType } from "../../../../billing/enums/plan-sponsorship-type";
|
||||
import { PlanSponsorshipType } from "../../../../billing/enums";
|
||||
|
||||
export class OrganizationSponsorshipCreateRequest {
|
||||
sponsoredEmail: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PlanSponsorshipType } from "../../../../billing/enums/plan-sponsorship-type";
|
||||
import { PlanSponsorshipType } from "../../../../billing/enums";
|
||||
|
||||
export class OrganizationSponsorshipRedeemRequest {
|
||||
planSponsorshipType: PlanSponsorshipType;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PolicyType } from "../../enums/policy-type";
|
||||
import { PolicyType } from "../../enums";
|
||||
|
||||
export class PolicyRequest {
|
||||
type: PolicyType;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ProviderUserType } from "../../../enums/provider-user-type";
|
||||
import { ProviderUserType } from "../../../enums";
|
||||
|
||||
export class ProviderUserInviteRequest {
|
||||
emails: string[] = [];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ProviderUserType } from "../../../enums/provider-user-type";
|
||||
import { ProviderUserType } from "../../../enums";
|
||||
|
||||
export class ProviderUserUpdateRequest {
|
||||
type: ProviderUserType;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ScimProviderType } from "../../enums/scim-provider-type";
|
||||
import { ScimProviderType } from "../../enums";
|
||||
|
||||
export class ScimConfigRequest {
|
||||
constructor(private enabled: boolean, private scimProvider: ScimProviderType = null) {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { OrganizationApiKeyType } from "../../enums/organization-api-key-type";
|
||||
import { OrganizationApiKeyType } from "../../enums";
|
||||
|
||||
export class OrganizationApiKeyInformationResponse extends BaseResponse {
|
||||
keyType: OrganizationApiKeyType;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BillingSyncConfigApi } from "../../../billing/models/api/billing-sync-config.api";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { OrganizationConnectionType } from "../../enums/organization-connection-type";
|
||||
import { OrganizationConnectionType } from "../../enums";
|
||||
import { ScimConfigApi } from "../api/scim-config.api";
|
||||
|
||||
/**API response config types for OrganizationConnectionResponse */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PlanType } from "../../../billing/enums/plan-type";
|
||||
import { PlanType } from "../../../billing/enums";
|
||||
import { PlanResponse } from "../../../billing/models/response/plan.response";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { PolicyType } from "../../enums/policy-type";
|
||||
import { PolicyType } from "../../enums";
|
||||
|
||||
export class PolicyResponse extends BaseResponse {
|
||||
id: string;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ProductType } from "../../../enums/productType";
|
||||
import { ProductType, ProviderType } from "../../../enums";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { OrganizationUserStatusType } from "../../enums/organization-user-status-type";
|
||||
import { OrganizationUserType } from "../../enums/organization-user-type";
|
||||
import { OrganizationUserStatusType, OrganizationUserType } from "../../enums";
|
||||
import { PermissionsApi } from "../api/permissions.api";
|
||||
|
||||
export class ProfileOrganizationResponse extends BaseResponse {
|
||||
@@ -38,6 +37,7 @@ export class ProfileOrganizationResponse extends BaseResponse {
|
||||
userId: string;
|
||||
providerId: string;
|
||||
providerName: string;
|
||||
providerType?: ProviderType;
|
||||
familySponsorshipFriendlyName: string;
|
||||
familySponsorshipAvailable: boolean;
|
||||
planProductType: ProductType;
|
||||
@@ -83,6 +83,7 @@ export class ProfileOrganizationResponse extends BaseResponse {
|
||||
this.userId = this.getResponseProperty("UserId");
|
||||
this.providerId = this.getResponseProperty("ProviderId");
|
||||
this.providerName = this.getResponseProperty("ProviderName");
|
||||
this.providerType = this.getResponseProperty("ProviderType");
|
||||
this.familySponsorshipFriendlyName = this.getResponseProperty("FamilySponsorshipFriendlyName");
|
||||
this.familySponsorshipAvailable = this.getResponseProperty("FamilySponsorshipAvailable");
|
||||
this.planProductType = this.getResponseProperty("PlanProductType");
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { ProviderUserStatusType } from "../../enums/provider-user-status-type";
|
||||
import { ProviderUserType } from "../../enums/provider-user-type";
|
||||
import { ProviderUserStatusType, ProviderUserType } from "../../enums";
|
||||
import { PermissionsApi } from "../api/permissions.api";
|
||||
|
||||
export class ProfileProviderResponse extends BaseResponse {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { BaseResponse } from "../../../../models/response/base.response";
|
||||
import { ProviderUserStatusType } from "../../../enums/provider-user-status-type";
|
||||
import { ProviderUserType } from "../../../enums/provider-user-type";
|
||||
import { ProviderUserStatusType, ProviderUserType } from "../../../enums";
|
||||
import { PermissionsApi } from "../../api/permissions.api";
|
||||
|
||||
export class ProviderUserResponse extends BaseResponse {
|
||||
|
||||
@@ -3,6 +3,8 @@ import { View } from "../../../models/view/view";
|
||||
import { Collection } from "../domain/collection";
|
||||
import { CollectionAccessDetailsResponse } from "../response/collection.response";
|
||||
|
||||
export const NestingDelimiter = "/";
|
||||
|
||||
export class CollectionView implements View, ITreeNodeObject {
|
||||
id: string = null;
|
||||
organizationId: string = null;
|
||||
|
||||
@@ -18,7 +18,7 @@ import { VerifyBankRequest } from "../../../models/request/verify-bank.request";
|
||||
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/organization-api-key-type";
|
||||
import { OrganizationApiKeyType } from "../../enums";
|
||||
import { OrganizationCreateRequest } from "../../models/request/organization-create.request";
|
||||
import { OrganizationKeysRequest } from "../../models/request/organization-keys.request";
|
||||
import { OrganizationUpdateRequest } from "../../models/request/organization-update.request";
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { BehaviorSubject, concatMap, map, Observable } from "rxjs";
|
||||
|
||||
import { StateService } from "../../../abstractions/state.service";
|
||||
import { InternalOrganizationService as InternalOrganizationServiceAbstraction } from "../../abstractions/organization/organization.service.abstraction";
|
||||
import {
|
||||
InternalOrganizationService as InternalOrganizationServiceAbstraction,
|
||||
isMember,
|
||||
} from "../../abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationData } from "../../models/data/organization.data";
|
||||
import { Organization } from "../../models/domain/organization";
|
||||
|
||||
@@ -9,6 +12,7 @@ export class OrganizationService implements InternalOrganizationServiceAbstracti
|
||||
protected _organizations = new BehaviorSubject<Organization[]>([]);
|
||||
|
||||
organizations$ = this._organizations.asObservable();
|
||||
memberOrganizations$ = this.organizations$.pipe(map((orgs) => orgs.filter(isMember)));
|
||||
|
||||
constructor(private stateService: StateService) {
|
||||
this.stateService.activeAccountUnlocked$
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Utils } from "../../../misc/utils";
|
||||
import { ListResponse } from "../../../models/response/list.response";
|
||||
import { PolicyApiServiceAbstraction } from "../../abstractions/policy/policy-api.service.abstraction";
|
||||
import { InternalPolicyService } from "../../abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "../../enums/policy-type";
|
||||
import { PolicyType } from "../../enums";
|
||||
import { PolicyData } from "../../models/data/policy.data";
|
||||
import { MasterPasswordPolicyOptions } from "../../models/domain/master-password-policy-options";
|
||||
import { PolicyRequest } from "../../models/request/policy.request";
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { of, concatMap, BehaviorSubject, Observable, map } from "rxjs";
|
||||
import { BehaviorSubject, concatMap, map, Observable, of } from "rxjs";
|
||||
|
||||
import { StateService } from "../../../abstractions/state.service";
|
||||
import { Utils } from "../../../misc/utils";
|
||||
import { ListResponse } from "../../../models/response/list.response";
|
||||
import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction";
|
||||
import { InternalPolicyService as InternalPolicyServiceAbstraction } from "../../abstractions/policy/policy.service.abstraction";
|
||||
import { OrganizationUserStatusType } from "../../enums/organization-user-status-type";
|
||||
import { OrganizationUserType } from "../../enums/organization-user-type";
|
||||
import { PolicyType } from "../../enums/policy-type";
|
||||
import { OrganizationUserStatusType, OrganizationUserType, PolicyType } from "../../enums";
|
||||
import { PolicyData } from "../../models/data/policy.data";
|
||||
import { MasterPasswordPolicyOptions } from "../../models/domain/master-password-policy-options";
|
||||
import { Organization } from "../../models/domain/organization";
|
||||
@@ -44,6 +42,28 @@ export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first policy found that applies to the active user
|
||||
* @param policyType Policy type to search for
|
||||
* @param policyFilter Additional filter to apply to the policy
|
||||
*/
|
||||
get$(policyType: PolicyType, policyFilter?: (policy: Policy) => boolean): Observable<Policy> {
|
||||
return this.policies$.pipe(
|
||||
concatMap(async (policies) => {
|
||||
const userId = await this.stateService.getUserId();
|
||||
const appliesToCurrentUser = await this.checkPoliciesThatApplyToUser(
|
||||
policies,
|
||||
policyType,
|
||||
policyFilter,
|
||||
userId
|
||||
);
|
||||
if (appliesToCurrentUser) {
|
||||
return policies.find((policy) => policy.type === policyType && policy.enabled);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Do not call this, use the policies$ observable collection
|
||||
*/
|
||||
@@ -117,6 +137,10 @@ export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
if (currentPolicy.data.requireSpecial) {
|
||||
enforcedOptions.requireSpecial = true;
|
||||
}
|
||||
|
||||
if (currentPolicy.data.enforceOnLogin) {
|
||||
enforcedOptions.enforceOnLogin = true;
|
||||
}
|
||||
});
|
||||
|
||||
return enforcedOptions;
|
||||
@@ -240,7 +264,7 @@ export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
await this.stateService.setEncryptedPolicies(null, { userId: userId });
|
||||
}
|
||||
|
||||
private isExcemptFromPolicies(organization: Organization, policyType: PolicyType) {
|
||||
private isExemptFromPolicies(organization: Organization, policyType: PolicyType) {
|
||||
if (policyType === PolicyType.MaximumVaultTimeout) {
|
||||
return organization.type === OrganizationUserType.Owner;
|
||||
}
|
||||
@@ -271,7 +295,7 @@ export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
o.status >= OrganizationUserStatusType.Accepted &&
|
||||
o.usePolicies &&
|
||||
policySet.has(o.id) &&
|
||||
!this.isExcemptFromPolicies(o, policyType)
|
||||
!this.isExemptFromPolicies(o, policyType)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export abstract class AuthService {
|
||||
email: string;
|
||||
accessCode: string;
|
||||
authRequestId: string;
|
||||
ssoEmail2FaSessionToken: string;
|
||||
|
||||
logIn: (
|
||||
credentials:
|
||||
@@ -37,11 +38,11 @@ export abstract class AuthService {
|
||||
authingWithPassword: () => boolean;
|
||||
authingWithPasswordless: () => boolean;
|
||||
getAuthStatus: (userId?: string) => Promise<AuthenticationStatus>;
|
||||
authResponsePushNotifiction: (notification: AuthRequestPushNotification) => Promise<any>;
|
||||
authResponsePushNotification: (notification: AuthRequestPushNotification) => Promise<any>;
|
||||
passwordlessLogin: (
|
||||
id: string,
|
||||
key: string,
|
||||
requestApproved: boolean
|
||||
) => Promise<AuthRequestResponse>;
|
||||
getPushNotifcationObs$: () => Observable<any>;
|
||||
getPushNotificationObs$: () => Observable<any>;
|
||||
}
|
||||
|
||||
@@ -7,20 +7,24 @@ import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { Account, AccountProfile, AccountTokens } from "../../models/domain/account";
|
||||
import { EncString } from "../../models/domain/enc-string";
|
||||
import { PasswordGenerationService } from "../../tools/generator/password";
|
||||
import { AuthService } from "../abstractions/auth.service";
|
||||
import { TokenService } from "../abstractions/token.service";
|
||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||
import { TwoFactorProviderType } from "../enums/two-factor-provider-type";
|
||||
import { AuthResult } from "../models/domain/auth-result";
|
||||
import { ForceResetPasswordReason } from "../models/domain/force-reset-password-reason";
|
||||
import { PasswordLogInCredentials } from "../models/domain/log-in-credentials";
|
||||
import { PasswordTokenRequest } from "../models/request/identity-token/password-token.request";
|
||||
import { TokenTwoFactorRequest } from "../models/request/identity-token/token-two-factor.request";
|
||||
import { IdentityCaptchaResponse } from "../models/response/identity-captcha.response";
|
||||
import { IdentityTokenResponse } from "../models/response/identity-token.response";
|
||||
import { IdentityTwoFactorResponse } from "../models/response/identity-two-factor.response";
|
||||
import { MasterPasswordPolicyResponse } from "../models/response/master-password-policy.response";
|
||||
|
||||
import { PasswordLogInStrategy } from "./password-login.strategy";
|
||||
|
||||
@@ -50,7 +54,9 @@ const twoFactorProviderType = TwoFactorProviderType.Authenticator;
|
||||
const twoFactorToken = "TWO_FACTOR_TOKEN";
|
||||
const twoFactorRemember = true;
|
||||
|
||||
export function identityTokenResponseFactory() {
|
||||
export function identityTokenResponseFactory(
|
||||
masterPasswordPolicyResponse: MasterPasswordPolicyResponse = null
|
||||
) {
|
||||
return new IdentityTokenResponse({
|
||||
ForcePasswordReset: false,
|
||||
Kdf: kdf,
|
||||
@@ -63,6 +69,7 @@ export function identityTokenResponseFactory() {
|
||||
refresh_token: refreshToken,
|
||||
scope: "api offline_access",
|
||||
token_type: "Bearer",
|
||||
MasterPasswordPolicy: masterPasswordPolicyResponse,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -77,6 +84,8 @@ describe("LogInStrategy", () => {
|
||||
let stateService: MockProxy<StateService>;
|
||||
let twoFactorService: MockProxy<TwoFactorService>;
|
||||
let authService: MockProxy<AuthService>;
|
||||
let policyService: MockProxy<PolicyService>;
|
||||
let passwordGenerationService: MockProxy<PasswordGenerationService>;
|
||||
|
||||
let passwordLogInStrategy: PasswordLogInStrategy;
|
||||
let credentials: PasswordLogInCredentials;
|
||||
@@ -92,6 +101,8 @@ describe("LogInStrategy", () => {
|
||||
stateService = mock<StateService>();
|
||||
twoFactorService = mock<TwoFactorService>();
|
||||
authService = mock<AuthService>();
|
||||
policyService = mock<PolicyService>();
|
||||
passwordGenerationService = mock<PasswordGenerationService>();
|
||||
|
||||
appIdService.getAppId.mockResolvedValue(deviceId);
|
||||
tokenService.decodeToken.calledWith(accessToken).mockResolvedValue(decodedToken);
|
||||
@@ -107,6 +118,8 @@ describe("LogInStrategy", () => {
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService,
|
||||
passwordGenerationService,
|
||||
policyService,
|
||||
authService
|
||||
);
|
||||
credentials = new PasswordLogInCredentials(email, masterPassword);
|
||||
@@ -155,7 +168,7 @@ describe("LogInStrategy", () => {
|
||||
const result = await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(result).toEqual({
|
||||
forcePasswordReset: true,
|
||||
forcePasswordReset: ForceResetPasswordReason.AdminForcePasswordReset,
|
||||
resetMasterPassword: true,
|
||||
twoFactorProviders: null,
|
||||
captchaSiteKey: "",
|
||||
@@ -210,6 +223,9 @@ describe("LogInStrategy", () => {
|
||||
TwoFactorProviders2: { 0: null },
|
||||
error: "invalid_grant",
|
||||
error_description: "Two factor required.",
|
||||
// only sent for emailed 2FA
|
||||
email: undefined,
|
||||
ssoEmail2faSessionToken: undefined,
|
||||
});
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
@@ -225,6 +241,39 @@ describe("LogInStrategy", () => {
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it("rejects login if 2FA via email is required + maps required information", async () => {
|
||||
// Sample response where Email 2FA required
|
||||
|
||||
const userEmail = "kyle@bitwarden.com";
|
||||
const ssoEmail2FaSessionToken =
|
||||
"BwSsoEmail2FaSessionToken_CfDJ8AMrVzKqBFpKqzzsahUx8ubIi9AhHm6aLHDLpCUYc3QV3qC14iuSVkNg57Q7-kGQUn1z87bGY1WP58jFMNJ6ndaurIgQWNfPNN4DG-dBhvzarOAZ0RKY5oKT5futWm6_k9NMMGd8PcGGHg5Pq1_koOIwRtiXO3IpD-bemB7m8oEvbj__JTQP3Mcz-UediFlCbYBKU3wyIiBL_tF8hW5D4RAUa5ZzXIuauJiiCdDS7QOzBcqcusVAPGFfKjfIdAwFfKSOYd5KmYrhK7Y7ymjweP_igPYKB5aMfcVaYr5ux-fdffeJTGqtJorwNjLUYNv7KA";
|
||||
|
||||
const tokenResponse = new IdentityTwoFactorResponse({
|
||||
TwoFactorProviders: ["1"],
|
||||
TwoFactorProviders2: { "1": { Email: "k***@bitwarden.com" } },
|
||||
error: "invalid_grant",
|
||||
error_description: "Two factor required.",
|
||||
// only sent for emailed 2FA
|
||||
email: userEmail,
|
||||
ssoEmail2faSessionToken: ssoEmail2FaSessionToken,
|
||||
});
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
|
||||
const result = await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(stateService.addAccount).not.toHaveBeenCalled();
|
||||
expect(messagingService.send).not.toHaveBeenCalled();
|
||||
|
||||
const expected = new AuthResult();
|
||||
expected.twoFactorProviders = new Map<TwoFactorProviderType, { [key: string]: string }>();
|
||||
expected.twoFactorProviders.set(1, { Email: "k***@bitwarden.com" });
|
||||
expected.email = userEmail;
|
||||
expected.ssoEmail2FaSessionToken = ssoEmail2FaSessionToken;
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it("sends stored 2FA token to server", async () => {
|
||||
tokenService.getTwoFactorToken.mockResolvedValue(twoFactorToken);
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
|
||||
@@ -11,11 +11,12 @@ import { TokenService } from "../abstractions/token.service";
|
||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||
import { TwoFactorProviderType } from "../enums/two-factor-provider-type";
|
||||
import { AuthResult } from "../models/domain/auth-result";
|
||||
import { ForceResetPasswordReason } from "../models/domain/force-reset-password-reason";
|
||||
import {
|
||||
UserApiLogInCredentials,
|
||||
PasswordlessLogInCredentials,
|
||||
PasswordLogInCredentials,
|
||||
SsoLogInCredentials,
|
||||
PasswordlessLogInCredentials,
|
||||
UserApiLogInCredentials,
|
||||
} from "../models/domain/log-in-credentials";
|
||||
import { DeviceRequest } from "../models/request/identity-token/device.request";
|
||||
import { PasswordTokenRequest } from "../models/request/identity-token/password-token.request";
|
||||
@@ -26,6 +27,8 @@ import { IdentityCaptchaResponse } from "../models/response/identity-captcha.res
|
||||
import { IdentityTokenResponse } from "../models/response/identity-token.response";
|
||||
import { IdentityTwoFactorResponse } from "../models/response/identity-two-factor.response";
|
||||
|
||||
type IdentityResponse = IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse;
|
||||
|
||||
export abstract class LogInStrategy {
|
||||
protected abstract tokenRequest: UserApiTokenRequest | PasswordTokenRequest | SsoTokenRequest;
|
||||
protected captchaBypassToken: string = null;
|
||||
@@ -58,20 +61,21 @@ export abstract class LogInStrategy {
|
||||
captchaResponse: string = null
|
||||
): Promise<AuthResult> {
|
||||
this.tokenRequest.setTwoFactor(twoFactor);
|
||||
return this.startLogIn();
|
||||
const [authResult] = await this.startLogIn();
|
||||
return authResult;
|
||||
}
|
||||
|
||||
protected async startLogIn(): Promise<AuthResult> {
|
||||
protected async startLogIn(): Promise<[AuthResult, IdentityResponse]> {
|
||||
this.twoFactorService.clearSelectedProvider();
|
||||
|
||||
const response = await this.apiService.postIdentityToken(this.tokenRequest);
|
||||
|
||||
if (response instanceof IdentityTwoFactorResponse) {
|
||||
return this.processTwoFactorResponse(response);
|
||||
return [await this.processTwoFactorResponse(response), response];
|
||||
} else if (response instanceof IdentityCaptchaResponse) {
|
||||
return this.processCaptchaResponse(response);
|
||||
return [await this.processCaptchaResponse(response), response];
|
||||
} else if (response instanceof IdentityTokenResponse) {
|
||||
return this.processTokenResponse(response);
|
||||
return [await this.processTokenResponse(response), response];
|
||||
}
|
||||
|
||||
throw new Error("Invalid response object.");
|
||||
@@ -126,7 +130,10 @@ export abstract class LogInStrategy {
|
||||
protected async processTokenResponse(response: IdentityTokenResponse): Promise<AuthResult> {
|
||||
const result = new AuthResult();
|
||||
result.resetMasterPassword = response.resetMasterPassword;
|
||||
result.forcePasswordReset = response.forcePasswordReset;
|
||||
|
||||
if (response.forcePasswordReset) {
|
||||
result.forcePasswordReset = ForceResetPasswordReason.AdminForcePasswordReset;
|
||||
}
|
||||
|
||||
await this.saveAccountInformation(response);
|
||||
|
||||
@@ -153,8 +160,11 @@ export abstract class LogInStrategy {
|
||||
private async processTwoFactorResponse(response: IdentityTwoFactorResponse): Promise<AuthResult> {
|
||||
const result = new AuthResult();
|
||||
result.twoFactorProviders = response.twoFactorProviders2;
|
||||
|
||||
this.twoFactorService.setProviders(response);
|
||||
this.captchaBypassToken = response.captchaToken ?? null;
|
||||
result.ssoEmail2FaSessionToken = response.ssoEmail2faSessionToken;
|
||||
result.email = response.email;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,19 @@ import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { HashPurpose } from "../../enums/hashPurpose";
|
||||
import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { HashPurpose } from "../../enums";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
|
||||
import { PasswordGenerationService } from "../../tools/generator/password";
|
||||
import { AuthService } from "../abstractions/auth.service";
|
||||
import { TokenService } from "../abstractions/token.service";
|
||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||
import { TwoFactorProviderType } from "../enums/two-factor-provider-type";
|
||||
import { ForceResetPasswordReason } from "../models/domain/force-reset-password-reason";
|
||||
import { PasswordLogInCredentials } from "../models/domain/log-in-credentials";
|
||||
import { IdentityTwoFactorResponse } from "../models/response/identity-two-factor.response";
|
||||
import { MasterPasswordPolicyResponse } from "../models/response/master-password-policy.response";
|
||||
|
||||
import { identityTokenResponseFactory } from "./login.strategy.spec";
|
||||
import { PasswordLogInStrategy } from "./password-login.strategy";
|
||||
@@ -28,6 +34,10 @@ const preloginKey = new SymmetricCryptoKey(
|
||||
)
|
||||
);
|
||||
const deviceId = Utils.newGuid();
|
||||
const masterPasswordPolicy = new MasterPasswordPolicyResponse({
|
||||
EnforceOnLogin: true,
|
||||
MinLength: 8,
|
||||
});
|
||||
|
||||
describe("PasswordLogInStrategy", () => {
|
||||
let cryptoService: MockProxy<CryptoService>;
|
||||
@@ -40,6 +50,8 @@ describe("PasswordLogInStrategy", () => {
|
||||
let stateService: MockProxy<StateService>;
|
||||
let twoFactorService: MockProxy<TwoFactorService>;
|
||||
let authService: MockProxy<AuthService>;
|
||||
let policyService: MockProxy<PolicyService>;
|
||||
let passwordGenerationService: MockProxy<PasswordGenerationService>;
|
||||
|
||||
let passwordLogInStrategy: PasswordLogInStrategy;
|
||||
let credentials: PasswordLogInCredentials;
|
||||
@@ -55,6 +67,8 @@ describe("PasswordLogInStrategy", () => {
|
||||
stateService = mock<StateService>();
|
||||
twoFactorService = mock<TwoFactorService>();
|
||||
authService = mock<AuthService>();
|
||||
policyService = mock<PolicyService>();
|
||||
passwordGenerationService = mock<PasswordGenerationService>();
|
||||
|
||||
appIdService.getAppId.mockResolvedValue(deviceId);
|
||||
tokenService.decodeToken.mockResolvedValue({});
|
||||
@@ -68,6 +82,8 @@ describe("PasswordLogInStrategy", () => {
|
||||
.calledWith(masterPassword, expect.anything(), HashPurpose.LocalAuthorization)
|
||||
.mockResolvedValue(localHashedPassword);
|
||||
|
||||
policyService.evaluateMasterPassword.mockReturnValue(true);
|
||||
|
||||
passwordLogInStrategy = new PasswordLogInStrategy(
|
||||
cryptoService,
|
||||
apiService,
|
||||
@@ -78,11 +94,15 @@ describe("PasswordLogInStrategy", () => {
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService,
|
||||
passwordGenerationService,
|
||||
policyService,
|
||||
authService
|
||||
);
|
||||
credentials = new PasswordLogInCredentials(email, masterPassword);
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
apiService.postIdentityToken.mockResolvedValue(
|
||||
identityTokenResponseFactory(masterPasswordPolicy)
|
||||
);
|
||||
});
|
||||
|
||||
it("sends master password credentials to the server", async () => {
|
||||
@@ -110,4 +130,75 @@ describe("PasswordLogInStrategy", () => {
|
||||
expect(cryptoService.setKey).toHaveBeenCalledWith(preloginKey);
|
||||
expect(cryptoService.setKeyHash).toHaveBeenCalledWith(localHashedPassword);
|
||||
});
|
||||
|
||||
it("does not force the user to update their master password when there are no requirements", async () => {
|
||||
apiService.postIdentityToken.mockResolvedValueOnce(identityTokenResponseFactory(null));
|
||||
|
||||
const result = await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(policyService.evaluateMasterPassword).not.toHaveBeenCalled();
|
||||
expect(result.forcePasswordReset).toEqual(ForceResetPasswordReason.None);
|
||||
});
|
||||
|
||||
it("does not force the user to update their master password when it meets requirements", async () => {
|
||||
passwordGenerationService.passwordStrength.mockReturnValue({ score: 5 } as any);
|
||||
policyService.evaluateMasterPassword.mockReturnValue(true);
|
||||
|
||||
const result = await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(policyService.evaluateMasterPassword).toHaveBeenCalled();
|
||||
expect(result.forcePasswordReset).toEqual(ForceResetPasswordReason.None);
|
||||
});
|
||||
|
||||
it("forces the user to update their master password on successful login when it does not meet master password policy requirements", async () => {
|
||||
passwordGenerationService.passwordStrength.mockReturnValue({ score: 0 } as any);
|
||||
policyService.evaluateMasterPassword.mockReturnValue(false);
|
||||
|
||||
const result = await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(policyService.evaluateMasterPassword).toHaveBeenCalled();
|
||||
expect(stateService.setForcePasswordResetReason).toHaveBeenCalledWith(
|
||||
ForceResetPasswordReason.WeakMasterPassword
|
||||
);
|
||||
expect(result.forcePasswordReset).toEqual(ForceResetPasswordReason.WeakMasterPassword);
|
||||
});
|
||||
|
||||
it("forces the user to update their master password on successful 2FA login when it does not meet master password policy requirements", async () => {
|
||||
passwordGenerationService.passwordStrength.mockReturnValue({ score: 0 } as any);
|
||||
policyService.evaluateMasterPassword.mockReturnValue(false);
|
||||
|
||||
const token2FAResponse = new IdentityTwoFactorResponse({
|
||||
TwoFactorProviders: ["0"],
|
||||
TwoFactorProviders2: { 0: null },
|
||||
error: "invalid_grant",
|
||||
error_description: "Two factor required.",
|
||||
MasterPasswordPolicy: masterPasswordPolicy,
|
||||
});
|
||||
|
||||
// First login request fails requiring 2FA
|
||||
apiService.postIdentityToken.mockResolvedValueOnce(token2FAResponse);
|
||||
const firstResult = await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
// Second login request succeeds
|
||||
apiService.postIdentityToken.mockResolvedValueOnce(
|
||||
identityTokenResponseFactory(masterPasswordPolicy)
|
||||
);
|
||||
const secondResult = await passwordLogInStrategy.logInTwoFactor(
|
||||
{
|
||||
provider: TwoFactorProviderType.Authenticator,
|
||||
token: "123456",
|
||||
remember: false,
|
||||
},
|
||||
""
|
||||
);
|
||||
|
||||
// First login attempt should not save the force password reset options
|
||||
expect(firstResult.forcePasswordReset).toEqual(ForceResetPasswordReason.None);
|
||||
|
||||
// Second login attempt should save the force password reset options and return in result
|
||||
expect(stateService.setForcePasswordResetReason).toHaveBeenCalledWith(
|
||||
ForceResetPasswordReason.WeakMasterPassword
|
||||
);
|
||||
expect(secondResult.forcePasswordReset).toEqual(ForceResetPasswordReason.WeakMasterPassword);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,15 +5,22 @@ import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { HashPurpose } from "../../enums/hashPurpose";
|
||||
import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { MasterPasswordPolicyOptions } from "../../admin-console/models/domain/master-password-policy-options";
|
||||
import { HashPurpose } from "../../enums";
|
||||
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
|
||||
import { PasswordGenerationServiceAbstraction } from "../../tools/generator/password";
|
||||
import { AuthService } from "../abstractions/auth.service";
|
||||
import { TokenService } from "../abstractions/token.service";
|
||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||
import { AuthResult } from "../models/domain/auth-result";
|
||||
import { ForceResetPasswordReason } from "../models/domain/force-reset-password-reason";
|
||||
import { PasswordLogInCredentials } from "../models/domain/log-in-credentials";
|
||||
import { PasswordTokenRequest } from "../models/request/identity-token/password-token.request";
|
||||
import { TokenTwoFactorRequest } from "../models/request/identity-token/token-two-factor.request";
|
||||
import { IdentityCaptchaResponse } from "../models/response/identity-captcha.response";
|
||||
import { IdentityTokenResponse } from "../models/response/identity-token.response";
|
||||
import { IdentityTwoFactorResponse } from "../models/response/identity-two-factor.response";
|
||||
|
||||
import { LogInStrategy } from "./login.strategy";
|
||||
|
||||
@@ -31,6 +38,12 @@ export class PasswordLogInStrategy extends LogInStrategy {
|
||||
private localHashedPassword: string;
|
||||
private key: SymmetricCryptoKey;
|
||||
|
||||
/**
|
||||
* Options to track if the user needs to update their password due to a password that does not meet an organization's
|
||||
* master password policy.
|
||||
*/
|
||||
private forcePasswordResetReason: ForceResetPasswordReason = ForceResetPasswordReason.None;
|
||||
|
||||
constructor(
|
||||
cryptoService: CryptoService,
|
||||
apiService: ApiService,
|
||||
@@ -39,8 +52,10 @@ export class PasswordLogInStrategy extends LogInStrategy {
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
messagingService: MessagingService,
|
||||
logService: LogService,
|
||||
stateService: StateService,
|
||||
protected stateService: StateService,
|
||||
twoFactorService: TwoFactorService,
|
||||
private passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||
private policyService: PolicyService,
|
||||
private authService: AuthService
|
||||
) {
|
||||
super(
|
||||
@@ -66,7 +81,19 @@ export class PasswordLogInStrategy extends LogInStrategy {
|
||||
captchaResponse: string
|
||||
): Promise<AuthResult> {
|
||||
this.tokenRequest.captchaResponse = captchaResponse ?? this.captchaBypassToken;
|
||||
return super.logInTwoFactor(twoFactor);
|
||||
const result = await super.logInTwoFactor(twoFactor);
|
||||
|
||||
// 2FA was successful, save the force update password options with the state service if defined
|
||||
if (
|
||||
!result.requiresTwoFactor &&
|
||||
!result.requiresCaptcha &&
|
||||
this.forcePasswordResetReason != ForceResetPasswordReason.None
|
||||
) {
|
||||
await this.stateService.setForcePasswordResetReason(this.forcePasswordResetReason);
|
||||
result.forcePasswordReset = this.forcePasswordResetReason;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async logIn(credentials: PasswordLogInCredentials) {
|
||||
@@ -90,6 +117,52 @@ export class PasswordLogInStrategy extends LogInStrategy {
|
||||
await this.buildDeviceRequest()
|
||||
);
|
||||
|
||||
return this.startLogIn();
|
||||
const [authResult, identityResponse] = await this.startLogIn();
|
||||
const masterPasswordPolicyOptions =
|
||||
this.getMasterPasswordPolicyOptionsFromResponse(identityResponse);
|
||||
|
||||
// The identity result can contain master password policies for the user's organizations
|
||||
if (masterPasswordPolicyOptions?.enforceOnLogin) {
|
||||
// If there is a policy active, evaluate the supplied password before its no longer in memory
|
||||
const meetsRequirements = this.evaluateMasterPassword(
|
||||
credentials,
|
||||
masterPasswordPolicyOptions
|
||||
);
|
||||
|
||||
if (!meetsRequirements) {
|
||||
if (authResult.requiresCaptcha || authResult.requiresTwoFactor) {
|
||||
// Save the flag to this strategy for later use as the master password is about to pass out of scope
|
||||
this.forcePasswordResetReason = ForceResetPasswordReason.WeakMasterPassword;
|
||||
} else {
|
||||
// Authentication was successful, save the force update password options with the state service
|
||||
await this.stateService.setForcePasswordResetReason(
|
||||
ForceResetPasswordReason.WeakMasterPassword
|
||||
);
|
||||
authResult.forcePasswordReset = ForceResetPasswordReason.WeakMasterPassword;
|
||||
}
|
||||
}
|
||||
}
|
||||
return authResult;
|
||||
}
|
||||
|
||||
private getMasterPasswordPolicyOptionsFromResponse(
|
||||
response: IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse
|
||||
): MasterPasswordPolicyOptions {
|
||||
if (response == null || response instanceof IdentityCaptchaResponse) {
|
||||
return null;
|
||||
}
|
||||
return MasterPasswordPolicyOptions.fromResponse(response.masterPasswordPolicy);
|
||||
}
|
||||
|
||||
private evaluateMasterPassword(
|
||||
{ masterPassword, email }: PasswordLogInCredentials,
|
||||
options: MasterPasswordPolicyOptions
|
||||
): boolean {
|
||||
const passwordStrength = this.passwordGenerationService.passwordStrength(
|
||||
masterPassword,
|
||||
email
|
||||
)?.score;
|
||||
|
||||
return this.policyService.evaluateMasterPassword(passwordStrength, masterPassword, options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ export class PasswordlessLogInStrategy extends LogInStrategy {
|
||||
);
|
||||
|
||||
this.tokenRequest.setPasswordlessAccessCode(credentials.authRequestId);
|
||||
return this.startLogIn();
|
||||
const [authResult] = await this.startLogIn();
|
||||
return authResult;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,12 @@ export class SsoLogInStrategy extends LogInStrategy {
|
||||
tokenRequest: SsoTokenRequest;
|
||||
orgId: string;
|
||||
|
||||
// A session token server side to serve as an authentication factor for the user
|
||||
// in order to send email OTPs to the user's configured 2FA email address
|
||||
// as we don't have a master password hash or other verifiable secret when using SSO.
|
||||
ssoEmail2FaSessionToken?: string;
|
||||
email?: string; // email not preserved through SSO process so get from server
|
||||
|
||||
constructor(
|
||||
cryptoService: CryptoService,
|
||||
apiService: ApiService,
|
||||
@@ -65,6 +71,11 @@ export class SsoLogInStrategy extends LogInStrategy {
|
||||
await this.buildDeviceRequest()
|
||||
);
|
||||
|
||||
return this.startLogIn();
|
||||
const [ssoAuthResult] = await this.startLogIn();
|
||||
|
||||
this.email = ssoAuthResult.email;
|
||||
this.ssoEmail2FaSessionToken = ssoAuthResult.ssoEmail2FaSessionToken;
|
||||
|
||||
return ssoAuthResult;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,8 @@ export class UserApiLogInStrategy extends LogInStrategy {
|
||||
await this.buildDeviceRequest()
|
||||
);
|
||||
|
||||
return this.startLogIn();
|
||||
const [authResult] = await this.startLogIn();
|
||||
return authResult;
|
||||
}
|
||||
|
||||
protected async saveAccountInformation(tokenResponse: IdentityTokenResponse) {
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { Utils } from "../../../misc/utils";
|
||||
import { TwoFactorProviderType } from "../../enums/two-factor-provider-type";
|
||||
|
||||
import { ForceResetPasswordReason } from "./force-reset-password-reason";
|
||||
|
||||
export class AuthResult {
|
||||
captchaSiteKey = "";
|
||||
resetMasterPassword = false;
|
||||
forcePasswordReset = false;
|
||||
forcePasswordReset: ForceResetPasswordReason = ForceResetPasswordReason.None;
|
||||
twoFactorProviders: Map<TwoFactorProviderType, { [key: string]: string }> = null;
|
||||
ssoEmail2FaSessionToken?: string;
|
||||
email: string;
|
||||
|
||||
get requiresCaptcha() {
|
||||
return !Utils.isNullOrWhitespace(this.captchaSiteKey);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
export enum ForceResetPasswordReason {
|
||||
/**
|
||||
* A password reset should not be forced.
|
||||
*/
|
||||
None,
|
||||
|
||||
/**
|
||||
* Occurs when an organization admin forces a user to reset their password.
|
||||
*/
|
||||
AdminForcePasswordReset,
|
||||
|
||||
/**
|
||||
* Occurs when a user logs in / unlocks their vault with a master password that does not meet an organization's
|
||||
* master password policy that is enforced on login/unlock.
|
||||
*/
|
||||
WeakMasterPassword,
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PlatformUtilsService } from "../../../../abstractions/platformUtils.service";
|
||||
import { DeviceType } from "../../../../enums/deviceType";
|
||||
import { DeviceType } from "../../../../enums";
|
||||
|
||||
export class DeviceRequest {
|
||||
type: DeviceType;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ClientType } from "../../../../enums/clientType";
|
||||
import { ClientType } from "../../../../enums";
|
||||
import { Utils } from "../../../../misc/utils";
|
||||
import { CaptchaProtectedRequest } from "../captcha-protected.request";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { KdfType } from "../../../enums/kdfType";
|
||||
import { KdfType } from "../../../enums";
|
||||
import { KeysRequest } from "../../../models/request/keys.request";
|
||||
import { KdfConfig } from "../domain/kdf-config";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { KdfType } from "../../../enums/kdfType";
|
||||
import { KdfType } from "../../../enums";
|
||||
import { KeysRequest } from "../../../models/request/keys.request";
|
||||
|
||||
export class SetPasswordRequest {
|
||||
|
||||
@@ -4,4 +4,5 @@ export class TwoFactorEmailRequest extends SecretVerificationRequest {
|
||||
email: string;
|
||||
deviceIdentifier: string;
|
||||
authRequestId: string;
|
||||
ssoEmail2FaSessionToken?: string;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DeviceType } from "../../../enums/deviceType";
|
||||
import { DeviceType } from "../../../enums";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
const RequestTimeOut = 60000 * 15; //15 Minutes
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DeviceType } from "../../../enums/deviceType";
|
||||
import { DeviceType } from "../../../enums";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class DeviceResponse extends BaseResponse {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { KdfType } from "../../../enums/kdfType";
|
||||
import { KdfType } from "../../../enums";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { CipherResponse } from "../../../vault/models/response/cipher.response";
|
||||
import { EmergencyAccessStatusType } from "../../enums/emergency-access-status-type";
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { KdfType } from "../../../enums/kdfType";
|
||||
import { KdfType } from "../../../enums";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
import { MasterPasswordPolicyResponse } from "./master-password-policy.response";
|
||||
|
||||
export class IdentityTokenResponse extends BaseResponse {
|
||||
accessToken: string;
|
||||
expiresIn: number;
|
||||
@@ -16,6 +18,7 @@ export class IdentityTokenResponse extends BaseResponse {
|
||||
kdfMemory?: number;
|
||||
kdfParallelism?: number;
|
||||
forcePasswordReset: boolean;
|
||||
masterPasswordPolicy: MasterPasswordPolicyResponse;
|
||||
apiUseKeyConnector: boolean;
|
||||
keyConnectorUrl: string;
|
||||
|
||||
@@ -37,5 +40,8 @@ export class IdentityTokenResponse extends BaseResponse {
|
||||
this.forcePasswordReset = this.getResponseProperty("ForcePasswordReset");
|
||||
this.apiUseKeyConnector = this.getResponseProperty("ApiUseKeyConnector");
|
||||
this.keyConnectorUrl = this.getResponseProperty("KeyConnectorUrl");
|
||||
this.masterPasswordPolicy = new MasterPasswordPolicyResponse(
|
||||
this.getResponseProperty("MasterPasswordPolicy")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { TwoFactorProviderType } from "../../enums/two-factor-provider-type";
|
||||
|
||||
import { MasterPasswordPolicyResponse } from "./master-password-policy.response";
|
||||
|
||||
export class IdentityTwoFactorResponse extends BaseResponse {
|
||||
twoFactorProviders: TwoFactorProviderType[];
|
||||
twoFactorProviders2 = new Map<TwoFactorProviderType, { [key: string]: string }>();
|
||||
captchaToken: string;
|
||||
ssoEmail2faSessionToken: string;
|
||||
email?: string;
|
||||
masterPasswordPolicy?: MasterPasswordPolicyResponse;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
@@ -19,5 +24,11 @@ export class IdentityTwoFactorResponse extends BaseResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.masterPasswordPolicy = new MasterPasswordPolicyResponse(
|
||||
this.getResponseProperty("MasterPasswordPolicy")
|
||||
);
|
||||
|
||||
this.ssoEmail2faSessionToken = this.getResponseProperty("SsoEmail2faSessionToken");
|
||||
this.email = this.getResponseProperty("Email");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class MasterPasswordPolicyResponse extends BaseResponse {
|
||||
minComplexity: number;
|
||||
minLength: number;
|
||||
requireUpper: boolean;
|
||||
requireLower: boolean;
|
||||
requireNumbers: boolean;
|
||||
requireSpecial: boolean;
|
||||
|
||||
/**
|
||||
* Flag to indicate if the policy should be enforced on login.
|
||||
* If true, and the user's password does not meet the policy requirements,
|
||||
* the user will be forced to update their password.
|
||||
*/
|
||||
enforceOnLogin: boolean;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
|
||||
this.minComplexity = this.getResponseProperty("MinComplexity");
|
||||
this.minLength = this.getResponseProperty("MinLength");
|
||||
this.requireUpper = this.getResponseProperty("RequireUpper");
|
||||
this.requireLower = this.getResponseProperty("RequireLower");
|
||||
this.requireNumbers = this.getResponseProperty("RequireNumbers");
|
||||
this.requireSpecial = this.getResponseProperty("RequireSpecial");
|
||||
this.enforceOnLogin = this.getResponseProperty("EnforceOnLogin");
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { KdfType } from "../../../enums/kdfType";
|
||||
import { KdfType } from "../../../enums";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class PreloginResponse extends BaseResponse {
|
||||
|
||||
@@ -10,13 +10,14 @@ import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { KdfType } from "../../enums/kdfType";
|
||||
import { KeySuffixOptions } from "../../enums/keySuffixOptions";
|
||||
import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { KdfType, KeySuffixOptions } from "../../enums";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
|
||||
import { PreloginRequest } from "../../models/request/prelogin.request";
|
||||
import { ErrorResponse } from "../../models/response/error.response";
|
||||
import { AuthRequestPushNotification } from "../../models/response/notification.response";
|
||||
import { PasswordGenerationServiceAbstraction } from "../../tools/generator/password";
|
||||
import { AuthService as AuthServiceAbstraction } from "../abstractions/auth.service";
|
||||
import { KeyConnectorService } from "../abstractions/key-connector.service";
|
||||
import { TokenService } from "../abstractions/token.service";
|
||||
@@ -30,10 +31,10 @@ import { UserApiLogInStrategy } from "../login-strategies/user-api-login.strateg
|
||||
import { AuthResult } from "../models/domain/auth-result";
|
||||
import { KdfConfig } from "../models/domain/kdf-config";
|
||||
import {
|
||||
UserApiLogInCredentials,
|
||||
PasswordlessLogInCredentials,
|
||||
PasswordLogInCredentials,
|
||||
SsoLogInCredentials,
|
||||
PasswordlessLogInCredentials,
|
||||
UserApiLogInCredentials,
|
||||
} from "../models/domain/log-in-credentials";
|
||||
import { TokenTwoFactorRequest } from "../models/request/identity-token/token-two-factor.request";
|
||||
import { PasswordlessAuthRequest } from "../models/request/passwordless-auth.request";
|
||||
@@ -45,7 +46,8 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
get email(): string {
|
||||
if (
|
||||
this.logInStrategy instanceof PasswordLogInStrategy ||
|
||||
this.logInStrategy instanceof PasswordlessLogInStrategy
|
||||
this.logInStrategy instanceof PasswordlessLogInStrategy ||
|
||||
this.logInStrategy instanceof SsoLogInStrategy
|
||||
) {
|
||||
return this.logInStrategy.email;
|
||||
}
|
||||
@@ -71,6 +73,12 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
: null;
|
||||
}
|
||||
|
||||
get ssoEmail2FaSessionToken(): string {
|
||||
return this.logInStrategy instanceof SsoLogInStrategy
|
||||
? this.logInStrategy.ssoEmail2FaSessionToken
|
||||
: null;
|
||||
}
|
||||
|
||||
private logInStrategy:
|
||||
| UserApiLogInStrategy
|
||||
| PasswordLogInStrategy
|
||||
@@ -93,7 +101,9 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
protected stateService: StateService,
|
||||
protected twoFactorService: TwoFactorService,
|
||||
protected i18nService: I18nService,
|
||||
protected encryptService: EncryptService
|
||||
protected encryptService: EncryptService,
|
||||
protected passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||
protected policyService: PolicyService
|
||||
) {}
|
||||
|
||||
async logIn(
|
||||
@@ -123,6 +133,8 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
this.logService,
|
||||
this.stateService,
|
||||
this.twoFactorService,
|
||||
this.passwordGenerationService,
|
||||
this.policyService,
|
||||
this
|
||||
);
|
||||
break;
|
||||
@@ -272,11 +284,11 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
return this.cryptoService.makeKey(masterPassword, email, kdf, kdfConfig);
|
||||
}
|
||||
|
||||
async authResponsePushNotifiction(notification: AuthRequestPushNotification): Promise<any> {
|
||||
async authResponsePushNotification(notification: AuthRequestPushNotification): Promise<any> {
|
||||
this.pushNotificationSubject.next(notification.id);
|
||||
}
|
||||
|
||||
getPushNotifcationObs$(): Observable<any> {
|
||||
getPushNotificationObs$(): Observable<any> {
|
||||
return this.pushNotificationSubject.asObservable();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { CryptoFunctionService } from "../../abstractions/cryptoFunction.service
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { OrganizationService } from "../../admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationUserType } from "../../admin-console/enums/organization-user-type";
|
||||
import { OrganizationUserType } from "../../admin-console/enums";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
|
||||
import { KeysRequest } from "../../models/request/keys.request";
|
||||
|
||||
6
libs/common/src/auth/types/biometric-key.d.ts
vendored
Normal file
6
libs/common/src/auth/types/biometric-key.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import { CsprngString } from "../../types/csprng";
|
||||
|
||||
export type BiometricKey = {
|
||||
key: string;
|
||||
clientEncKeyHalf: CsprngString;
|
||||
};
|
||||
4
libs/common/src/billing/enums/index.ts
Normal file
4
libs/common/src/billing/enums/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./payment-method-type.enum";
|
||||
export * from "./plan-sponsorship-type.enum";
|
||||
export * from "./plan-type.enum";
|
||||
export * from "./transaction-type.enum";
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PaymentMethodType } from "../../enums/payment-method-type";
|
||||
import { PaymentMethodType } from "../../enums";
|
||||
|
||||
import { OrganizationTaxInfoUpdateRequest } from "./organization-tax-info-update.request";
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { PaymentMethodType } from "../../enums/payment-method-type";
|
||||
import { TransactionType } from "../../enums/transaction-type";
|
||||
import { PaymentMethodType, TransactionType } from "../../enums";
|
||||
|
||||
export class BillingResponse extends BaseResponse {
|
||||
balance: number;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ProductType } from "../../../enums/productType";
|
||||
import { ProductType } from "../../../enums";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { PlanType } from "../../enums/plan-type";
|
||||
import { PlanType } from "../../enums";
|
||||
|
||||
export class PlanResponse extends BaseResponse {
|
||||
type: PlanType;
|
||||
|
||||
@@ -75,7 +75,7 @@ export class BillingSubscriptionItemResponse extends BaseResponse {
|
||||
|
||||
export class BillingSubscriptionUpcomingInvoiceResponse extends BaseResponse {
|
||||
date: string;
|
||||
amount: number;
|
||||
amount?: number;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user