diff --git a/src/abstractions/api.service.ts b/src/abstractions/api.service.ts index 2d99d4b200..84e810bd2d 100644 --- a/src/abstractions/api.service.ts +++ b/src/abstractions/api.service.ts @@ -31,6 +31,7 @@ import { ImportOrganizationCiphersRequest } from '../models/request/importOrgani import { KdfRequest } from '../models/request/kdfRequest'; import { KeysRequest } from '../models/request/keysRequest'; import { OrganizationCreateRequest } from '../models/request/organizationCreateRequest'; +import { OrganizationKeysRequest } from '../models/request/organizationKeysRequest'; import { OrganizationTaxInfoUpdateRequest } from '../models/request/organizationTaxInfoUpdateRequest'; import { OrganizationUpdateRequest } from '../models/request/organizationUpdateRequest'; import { OrganizationUpgradeRequest } from '../models/request/organizationUpgradeRequest'; @@ -39,6 +40,7 @@ import { OrganizationUserBulkRequest } from '../models/request/organizationUserB import { OrganizationUserConfirmRequest } from '../models/request/organizationUserConfirmRequest'; import { OrganizationUserInviteRequest } from '../models/request/organizationUserInviteRequest'; import { OrganizationUserResetPasswordEnrollmentRequest } from '../models/request/organizationUserResetPasswordEnrollmentRequest'; +import { OrganizationUserResetPasswordRequest } from '../models/request/organizationUserResetPasswordRequest'; import { OrganizationUserUpdateGroupsRequest } from '../models/request/organizationUserUpdateGroupsRequest'; import { OrganizationUserUpdateRequest } from '../models/request/organizationUserUpdateRequest'; import { PasswordHintRequest } from '../models/request/passwordHintRequest'; @@ -98,10 +100,12 @@ import { import { IdentityTokenResponse } from '../models/response/identityTokenResponse'; import { IdentityTwoFactorResponse } from '../models/response/identityTwoFactorResponse'; import { ListResponse } from '../models/response/listResponse'; +import { OrganizationKeysResponse } from '../models/response/organizationKeysResponse'; import { OrganizationResponse } from '../models/response/organizationResponse'; import { OrganizationSubscriptionResponse } from '../models/response/organizationSubscriptionResponse'; import { OrganizationUserDetailsResponse, + OrganizationUserResetPasswordDetailsReponse, OrganizationUserUserDetailsResponse, } from '../models/response/organizationUserResponse'; import { PaymentResponse } from '../models/response/paymentResponse'; @@ -271,6 +275,8 @@ export abstract class ApiService { getOrganizationUser: (organizationId: string, id: string) => Promise; getOrganizationUserGroups: (organizationId: string, id: string) => Promise; getOrganizationUsers: (organizationId: string) => Promise>; + getOrganizationUserResetPasswordDetails: (organizationId: string, id: string) + => Promise; postOrganizationUserInvite: (organizationId: string, request: OrganizationUserInviteRequest) => Promise; postOrganizationUserReinvite: (organizationId: string, id: string) => Promise; postManyOrganizationUserReinvite: (organizationId: string, request: OrganizationUserBulkRequest) => Promise; @@ -283,6 +289,8 @@ export abstract class ApiService { request: OrganizationUserUpdateGroupsRequest) => Promise; putOrganizationUserResetPasswordEnrollment: (organizationId: string, userId: string, request: OrganizationUserResetPasswordEnrollmentRequest) => Promise; + putOrganizationUserResetPassword: (organizationId: string, id: string, + request: OrganizationUserResetPasswordRequest) => Promise; deleteOrganizationUser: (organizationId: string, id: string) => Promise; deleteManyOrganizationUsers: (organizationId: string, request: OrganizationUserBulkRequest) => Promise; @@ -359,6 +367,8 @@ export abstract class ApiService { deleteOrganization: (id: string, request: PasswordVerificationRequest) => Promise; getPlans: () => Promise>; getTaxRates: () => Promise>; + getOrganizationKeys: (id: string) => Promise; + postOrganizationKeys: (id: string, request: OrganizationKeysRequest) => Promise; getEvents: (start: string, end: string, token: string) => Promise>; getEventsCipher: (id: string, start: string, end: string, token: string) => Promise>; diff --git a/src/abstractions/policy.service.ts b/src/abstractions/policy.service.ts index ca0d3c9ed7..6dc76d04d3 100644 --- a/src/abstractions/policy.service.ts +++ b/src/abstractions/policy.service.ts @@ -2,6 +2,7 @@ import { PolicyData } from '../models/data/policyData'; import { MasterPasswordPolicyOptions } from '../models/domain/masterPasswordPolicyOptions'; import { Policy } from '../models/domain/policy'; +import { ResetPasswordPolicyOptions } from '../models/domain/resetPasswordPolicyOptions'; import { PolicyType } from '../enums/policyType'; @@ -15,4 +16,5 @@ export abstract class PolicyService { getMasterPasswordPolicyOptions: (policies?: Policy[]) => Promise; evaluateMasterPassword: (passwordStrength: number, newPassword: string, enforcedPolicyOptions?: MasterPasswordPolicyOptions) => boolean; + getResetPasswordPolicyOptions: (policy: Policy) => ResetPasswordPolicyOptions; } diff --git a/src/enums/permissions.ts b/src/enums/permissions.ts index bcf76c6080..7d0b4232ba 100644 --- a/src/enums/permissions.ts +++ b/src/enums/permissions.ts @@ -9,4 +9,5 @@ export enum Permissions { ManageOrganization, ManagePolicies, ManageUsers, + ManageUsersPassword, } diff --git a/src/enums/policyType.ts b/src/enums/policyType.ts index 6b743b8ab1..8d5020988a 100644 --- a/src/enums/policyType.ts +++ b/src/enums/policyType.ts @@ -7,4 +7,5 @@ export enum PolicyType { PersonalOwnership = 5, // Disables personal vault ownership for adding/cloning items DisableSend = 6, // Disables the ability to create and edit Bitwarden Sends SendOptions = 7, // Sets restrictions or defaults for Bitwarden Sends + ResetPassword = 8, // Allows orgs to use reset password : also can enable auto-enrollment during invite flow } diff --git a/src/models/data/organizationData.ts b/src/models/data/organizationData.ts index 9e8818008a..985fa059f3 100644 --- a/src/models/data/organizationData.ts +++ b/src/models/data/organizationData.ts @@ -19,6 +19,7 @@ export class OrganizationData { useApi: boolean; useBusinessPortal: boolean; useSso: boolean; + useResetPassword: boolean; selfHost: boolean; usersGetPremium: boolean; seats: number; @@ -27,8 +28,9 @@ export class OrganizationData { ssoBound: boolean; identifier: string; permissions: PermissionsApi; - resetPasswordKey: string; + resetPasswordEnrolled: boolean; userId: string; + hasPublicAndPrivateKeys: boolean; constructor(response: ProfileOrganizationResponse) { this.id = response.id; @@ -45,6 +47,7 @@ export class OrganizationData { this.useApi = response.useApi; this.useBusinessPortal = response.useBusinessPortal; this.useSso = response.useSso; + this.useResetPassword = response.useResetPassword; this.selfHost = response.selfHost; this.usersGetPremium = response.usersGetPremium; this.seats = response.seats; @@ -53,7 +56,8 @@ export class OrganizationData { this.ssoBound = response.ssoBound; this.identifier = response.identifier; this.permissions = response.permissions; - this.resetPasswordKey = response.resetPasswordKey; + this.resetPasswordEnrolled = response.resetPasswordEnrolled; this.userId = response.userId; + this.hasPublicAndPrivateKeys = response.hasPublicAndPrivateKeys; } } diff --git a/src/models/domain/organization.ts b/src/models/domain/organization.ts index b13a8bba45..435be1b003 100644 --- a/src/models/domain/organization.ts +++ b/src/models/domain/organization.ts @@ -20,6 +20,7 @@ export class Organization { useApi: boolean; useBusinessPortal: boolean; useSso: boolean; + useResetPassword: boolean; selfHost: boolean; usersGetPremium: boolean; seats: number; @@ -28,8 +29,9 @@ export class Organization { ssoBound: boolean; identifier: string; permissions: PermissionsApi; - resetPasswordKey: string; + resetPasswordEnrolled: boolean; userId: string; + hasPublicAndPrivateKeys: boolean; constructor(obj?: OrganizationData) { if (obj == null) { @@ -50,6 +52,7 @@ export class Organization { this.useApi = obj.useApi; this.useBusinessPortal = obj.useBusinessPortal; this.useSso = obj.useSso; + this.useResetPassword = obj.useResetPassword; this.selfHost = obj.selfHost; this.usersGetPremium = obj.usersGetPremium; this.seats = obj.seats; @@ -58,8 +61,9 @@ export class Organization { this.ssoBound = obj.ssoBound; this.identifier = obj.identifier; this.permissions = obj.permissions; - this.resetPasswordKey = obj.resetPasswordKey; + this.resetPasswordEnrolled = obj.resetPasswordEnrolled; this.userId = obj.userId; + this.hasPublicAndPrivateKeys = obj.hasPublicAndPrivateKeys; } get canAccess() { @@ -122,7 +126,7 @@ export class Organization { return this.isAdmin || this.permissions.manageUsers; } - get isResetPasswordEnrolled() { - return this.resetPasswordKey != null; + get canManageUsersPassword() { + return this.isAdmin || this.permissions.manageResetPassword; } } diff --git a/src/models/domain/resetPasswordPolicyOptions.ts b/src/models/domain/resetPasswordPolicyOptions.ts new file mode 100644 index 0000000000..6a650e4c43 --- /dev/null +++ b/src/models/domain/resetPasswordPolicyOptions.ts @@ -0,0 +1,5 @@ +import Domain from './domainBase'; + +export class ResetPasswordPolicyOptions extends Domain { + autoEnrollEnabled: boolean = false; +} diff --git a/src/models/request/organizationCreateRequest.ts b/src/models/request/organizationCreateRequest.ts index 8366b4cff3..f8bc4d6149 100644 --- a/src/models/request/organizationCreateRequest.ts +++ b/src/models/request/organizationCreateRequest.ts @@ -1,12 +1,15 @@ import { PaymentMethodType } from '../../enums/paymentMethodType'; import { PlanType } from '../../enums/planType'; +import { OrganizationKeysRequest } from './organizationKeysRequest'; + export class OrganizationCreateRequest { name: string; businessName: string; billingEmail: string; planType: PlanType; key: string; + keys: OrganizationKeysRequest; paymentMethodType: PaymentMethodType; paymentToken: string; additionalSeats: number; diff --git a/src/models/request/organizationKeysRequest.ts b/src/models/request/organizationKeysRequest.ts new file mode 100644 index 0000000000..932f28ce03 --- /dev/null +++ b/src/models/request/organizationKeysRequest.ts @@ -0,0 +1,7 @@ +import { KeysRequest } from './keysRequest'; + +export class OrganizationKeysRequest extends KeysRequest { + constructor(publicKey: string, encryptedPrivateKey: string) { + super(publicKey, encryptedPrivateKey); + } +} diff --git a/src/models/request/organizationUpdateRequest.ts b/src/models/request/organizationUpdateRequest.ts index 306c20b305..6e20b671f8 100644 --- a/src/models/request/organizationUpdateRequest.ts +++ b/src/models/request/organizationUpdateRequest.ts @@ -1,6 +1,9 @@ +import { OrganizationKeysRequest } from './organizationKeysRequest'; + export class OrganizationUpdateRequest { name: string; identifier: string; businessName: string; billingEmail: string; + keys: OrganizationKeysRequest; } diff --git a/src/models/request/organizationUpgradeRequest.ts b/src/models/request/organizationUpgradeRequest.ts index 0c0bfa8a07..1000e19e86 100644 --- a/src/models/request/organizationUpgradeRequest.ts +++ b/src/models/request/organizationUpgradeRequest.ts @@ -1,5 +1,7 @@ import { PlanType } from '../../enums/planType'; +import { OrganizationKeysRequest } from './organizationKeysRequest'; + export class OrganizationUpgradeRequest { businessName: string; planType: PlanType; @@ -8,4 +10,5 @@ export class OrganizationUpgradeRequest { premiumAccessAddon: boolean; billingAddressCountry: string; billingAddressPostalCode: string; + keys: OrganizationKeysRequest; } diff --git a/src/models/request/organizationUserResetPasswordRequest.ts b/src/models/request/organizationUserResetPasswordRequest.ts new file mode 100644 index 0000000000..7e0b1bdb98 --- /dev/null +++ b/src/models/request/organizationUserResetPasswordRequest.ts @@ -0,0 +1,4 @@ +export class OrganizationUserResetPasswordRequest { + newMasterPasswordHash: string; + key: string; +} diff --git a/src/models/response/organizationKeysResponse.ts b/src/models/response/organizationKeysResponse.ts new file mode 100644 index 0000000000..a5ed77d3f4 --- /dev/null +++ b/src/models/response/organizationKeysResponse.ts @@ -0,0 +1,7 @@ +import { KeysResponse } from './keysResponse'; + +export class OrganizationKeysResponse extends KeysResponse { + constructor(response: any) { + super(response); + } +} diff --git a/src/models/response/organizationResponse.ts b/src/models/response/organizationResponse.ts index 466a2b3334..21d8d4385e 100644 --- a/src/models/response/organizationResponse.ts +++ b/src/models/response/organizationResponse.ts @@ -25,6 +25,8 @@ export class OrganizationResponse extends BaseResponse { useTotp: boolean; use2fa: boolean; useApi: boolean; + useResetPassword: boolean; + hasPublicAndPrivateKeys: boolean; constructor(response: any) { super(response); @@ -50,5 +52,7 @@ export class OrganizationResponse extends BaseResponse { this.useTotp = this.getResponseProperty('UseTotp'); this.use2fa = this.getResponseProperty('Use2fa'); this.useApi = this.getResponseProperty('UseApi'); + this.useResetPassword = this.getResponseProperty('UseResetPassword'); + this.hasPublicAndPrivateKeys = this.getResponseProperty('HasPublicAndPrivateKeys'); } } diff --git a/src/models/response/organizationUserResponse.ts b/src/models/response/organizationUserResponse.ts index 08d3dbed98..558f9db06c 100644 --- a/src/models/response/organizationUserResponse.ts +++ b/src/models/response/organizationUserResponse.ts @@ -3,6 +3,7 @@ import { SelectionReadOnlyResponse } from './selectionReadOnlyResponse'; import { PermissionsApi } from '../api/permissionsApi'; +import { KdfType } from '../../enums/kdfType'; import { OrganizationUserStatusType } from '../../enums/organizationUserStatusType'; import { OrganizationUserType } from '../../enums/organizationUserType'; @@ -13,6 +14,7 @@ export class OrganizationUserResponse extends BaseResponse { status: OrganizationUserStatusType; accessAll: boolean; permissions: PermissionsApi; + resetPasswordEnrolled: boolean; constructor(response: any) { super(response); @@ -22,6 +24,7 @@ export class OrganizationUserResponse extends BaseResponse { this.status = this.getResponseProperty('Status'); this.permissions = new PermissionsApi(this.getResponseProperty('Permissions')); this.accessAll = this.getResponseProperty('AccessAll'); + this.resetPasswordEnrolled = this.getResponseProperty('ResetPasswordEnrolled'); } } @@ -49,3 +52,18 @@ export class OrganizationUserDetailsResponse extends OrganizationUserResponse { } } } + +export class OrganizationUserResetPasswordDetailsReponse extends BaseResponse { + kdf: KdfType; + kdfIterations: number; + resetPasswordKey: string; + encryptedPrivateKey: string; + + constructor(response: any) { + super(response); + this.kdf = this.getResponseProperty('Kdf'); + this.kdfIterations = this.getResponseProperty('KdfIterations'); + this.resetPasswordKey = this.getResponseProperty('ResetPasswordKey'); + this.encryptedPrivateKey = this.getResponseProperty('EncryptedPrivateKey'); + } +} diff --git a/src/models/response/planResponse.ts b/src/models/response/planResponse.ts index 2cdd18acf0..cd201db97e 100644 --- a/src/models/response/planResponse.ts +++ b/src/models/response/planResponse.ts @@ -32,6 +32,7 @@ export class PlanResponse extends BaseResponse { has2fa: boolean; hasApi: boolean; hasSso: boolean; + hasResetPassword: boolean; usersGetPremium: boolean; upgradeSortOrder: number; @@ -76,6 +77,7 @@ export class PlanResponse extends BaseResponse { this.has2fa = this.getResponseProperty('Has2fa'); this.hasApi = this.getResponseProperty('HasApi'); this.hasSso = this.getResponseProperty('HasSso'); + this.hasResetPassword = this.getResponseProperty('HasResetPassword'); this.usersGetPremium = this.getResponseProperty('UsersGetPremium'); this.upgradeSortOrder = this.getResponseProperty('UpgradeSortOrder'); this.displaySortOrder = this.getResponseProperty('SortOrder'); diff --git a/src/models/response/profileOrganizationResponse.ts b/src/models/response/profileOrganizationResponse.ts index 012ca6e0b3..3a09c2e27f 100644 --- a/src/models/response/profileOrganizationResponse.ts +++ b/src/models/response/profileOrganizationResponse.ts @@ -16,19 +16,21 @@ export class ProfileOrganizationResponse extends BaseResponse { useApi: boolean; useBusinessPortal: boolean; useSso: boolean; + useResetPassword: boolean; selfHost: boolean; usersGetPremium: boolean; seats: number; maxCollections: number; maxStorageGb?: number; key: string; + hasPublicAndPrivateKeys: boolean; status: OrganizationUserStatusType; type: OrganizationUserType; enabled: boolean; ssoBound: boolean; identifier: string; permissions: PermissionsApi; - resetPasswordKey: string; + resetPasswordEnrolled: boolean; userId: string; constructor(response: any) { @@ -44,19 +46,21 @@ export class ProfileOrganizationResponse extends BaseResponse { this.useApi = this.getResponseProperty('UseApi'); this.useBusinessPortal = this.getResponseProperty('UseBusinessPortal'); this.useSso = this.getResponseProperty('UseSso'); + this.useResetPassword = this.getResponseProperty('UseResetPassword'); this.selfHost = this.getResponseProperty('SelfHost'); this.usersGetPremium = this.getResponseProperty('UsersGetPremium'); this.seats = this.getResponseProperty('Seats'); this.maxCollections = this.getResponseProperty('MaxCollections'); this.maxStorageGb = this.getResponseProperty('MaxStorageGb'); this.key = this.getResponseProperty('Key'); + this.hasPublicAndPrivateKeys = this.getResponseProperty('HasPublicAndPrivateKeys'); this.status = this.getResponseProperty('Status'); this.type = this.getResponseProperty('Type'); this.enabled = this.getResponseProperty('Enabled'); this.ssoBound = this.getResponseProperty('SsoBound'); this.identifier = this.getResponseProperty('Identifier'); this.permissions = new PermissionsApi(this.getResponseProperty('permissions')); - this.resetPasswordKey = this.getResponseProperty('ResetPasswordKey'); + this.resetPasswordEnrolled = this.getResponseProperty('ResetPasswordEnrolled'); this.userId = this.getResponseProperty('UserId'); } } diff --git a/src/services/api.service.ts b/src/services/api.service.ts index 69a74a2bac..0147a72018 100644 --- a/src/services/api.service.ts +++ b/src/services/api.service.ts @@ -35,6 +35,7 @@ import { ImportOrganizationCiphersRequest } from '../models/request/importOrgani import { KdfRequest } from '../models/request/kdfRequest'; import { KeysRequest } from '../models/request/keysRequest'; import { OrganizationCreateRequest } from '../models/request/organizationCreateRequest'; +import { OrganizationKeysRequest } from '../models/request/organizationKeysRequest'; import { OrganizationTaxInfoUpdateRequest } from '../models/request/organizationTaxInfoUpdateRequest'; import { OrganizationUpdateRequest } from '../models/request/organizationUpdateRequest'; import { OrganizationUpgradeRequest } from '../models/request/organizationUpgradeRequest'; @@ -43,6 +44,7 @@ import { OrganizationUserBulkRequest } from '../models/request/organizationUserB import { OrganizationUserConfirmRequest } from '../models/request/organizationUserConfirmRequest'; import { OrganizationUserInviteRequest } from '../models/request/organizationUserInviteRequest'; import { OrganizationUserResetPasswordEnrollmentRequest } from '../models/request/organizationUserResetPasswordEnrollmentRequest'; +import { OrganizationUserResetPasswordRequest } from '../models/request/organizationUserResetPasswordRequest'; import { OrganizationUserUpdateGroupsRequest } from '../models/request/organizationUserUpdateGroupsRequest'; import { OrganizationUserUpdateRequest } from '../models/request/organizationUserUpdateRequest'; import { PasswordHintRequest } from '../models/request/passwordHintRequest'; @@ -104,10 +106,12 @@ import { import { IdentityTokenResponse } from '../models/response/identityTokenResponse'; import { IdentityTwoFactorResponse } from '../models/response/identityTwoFactorResponse'; import { ListResponse } from '../models/response/listResponse'; +import { OrganizationKeysResponse } from '../models/response/organizationKeysResponse'; import { OrganizationResponse } from '../models/response/organizationResponse'; import { OrganizationSubscriptionResponse } from '../models/response/organizationSubscriptionResponse'; import { OrganizationUserDetailsResponse, + OrganizationUserResetPasswordDetailsReponse, OrganizationUserUserDetailsResponse, } from '../models/response/organizationUserResponse'; import { PaymentResponse } from '../models/response/paymentResponse'; @@ -794,6 +798,13 @@ export class ApiService implements ApiServiceAbstraction { return new ListResponse(r, OrganizationUserUserDetailsResponse); } + async getOrganizationUserResetPasswordDetails(organizationId: string, id: string): + Promise { + const r = await this.send('GET', '/organizations/' + organizationId + '/users/' + id + + '/reset-password-details', null, true, true); + return new OrganizationUserResetPasswordDetailsReponse(r); + } + postOrganizationUserInvite(organizationId: string, request: OrganizationUserInviteRequest): Promise { return this.send('POST', '/organizations/' + organizationId + '/users/invite', request, true, false); } @@ -832,6 +843,12 @@ export class ApiService implements ApiServiceAbstraction { request, true, false); } + putOrganizationUserResetPassword(organizationId: string, id: string, + request: OrganizationUserResetPasswordRequest): Promise { + return this.send('PUT', '/organizations/' + organizationId + '/users/' + id + '/reset-password', + request, true, false); + } + deleteOrganizationUser(organizationId: string, id: string): Promise { return this.send('DELETE', '/organizations/' + organizationId + '/users/' + id, null, true, false); } @@ -1176,6 +1193,16 @@ export class ApiService implements ApiServiceAbstraction { return this.send('DELETE', '/organizations/' + id, request, true, false); } + async getOrganizationKeys(id: string): Promise { + const r = await this.send('GET', '/organizations/' + id + '/keys', null, true, true); + return new OrganizationKeysResponse(r); + } + + async postOrganizationKeys(id: string, request: OrganizationKeysRequest): Promise { + const r = await this.send('POST', '/organizations/' + id + '/keys', request, true, true); + return new OrganizationKeysResponse(r); + } + // Event APIs async getEvents(start: string, end: string, token: string): Promise> { diff --git a/src/services/policy.service.ts b/src/services/policy.service.ts index a929aec9d4..d9c5db2dba 100644 --- a/src/services/policy.service.ts +++ b/src/services/policy.service.ts @@ -8,6 +8,7 @@ import { MasterPasswordPolicyOptions } from '../models/domain/masterPasswordPoli import { Policy } from '../models/domain/policy'; import { PolicyType } from '../enums/policyType'; +import { ResetPasswordPolicyOptions } from '../models/domain/resetPasswordPolicyOptions'; const Keys = { policiesPrefix: 'policies_', @@ -138,4 +139,14 @@ export class PolicyService implements PolicyServiceAbstraction { return true; } + + getResetPasswordPolicyOptions(policy: Policy): ResetPasswordPolicyOptions { + const resetPasswordPolicyOptions = new ResetPasswordPolicyOptions(); + + if (policy != null && policy.enabled && policy.data != null) { + resetPasswordPolicyOptions.autoEnrollEnabled = policy.data.autoEnrollEnabled; + } + + return resetPasswordPolicyOptions; + } }