diff --git a/apps/web/src/app/auth/core/services/login/web-login.service.ts b/apps/web/src/app/auth/core/services/login/web-login.service.ts index 35c3558116e..d6999674ea2 100644 --- a/apps/web/src/app/auth/core/services/login/web-login.service.ts +++ b/apps/web/src/app/auth/core/services/login/web-login.service.ts @@ -1,21 +1,66 @@ import { inject } from "@angular/core"; import { UrlTree } from "@angular/router"; +import { firstValueFrom } from "rxjs"; -import { DefaultLoginService, LoginService } from "@bitwarden/auth/angular"; +import { DefaultLoginService, LoginService, PasswordPolicies } from "@bitwarden/auth/angular"; +import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; +import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { OrganizationInvite } from "../../../../../../../../libs/auth/src/common/models/domain/organization-invite"; import { RouterService } from "../../../../core/router.service"; +import { AcceptOrganizationInviteService } from "../../../organization-invite/accept-organization.service"; export class WebLoginService extends DefaultLoginService implements LoginService { + acceptOrganizationInviteService = inject(AcceptOrganizationInviteService); + logService = inject(LogService); + policyApiService = inject(PolicyApiServiceAbstraction); + policyService = inject(InternalPolicyService); routerService = inject(RouterService); setPreviousUrl(route: UrlTree): void | null { this.routerService.setPreviousUrl(route.toString()); } - async handleExistingOrgInvite(): Promise { - const orgInvite = await this.acceptOrganizationInviteService.getOrganizationInvite(); - if (orgInvite != null) { - await this.initPasswordPolicies(orgInvite); + async getOrganizationInvite(): Promise { + return await this.acceptOrganizationInviteService.getOrganizationInvite(); + } + + async getPasswordPolicies(invite: OrganizationInvite): Promise { + let policies: Policy[]; + + try { + policies = await this.policyApiService.getPoliciesByToken( + invite.organizationId, + invite.token, + invite.email, + invite.organizationUserId, + ); + } catch (e) { + this.logService.error(e); } + + if (policies == null) { + return; + } + + const resetPasswordPolicy = this.policyService.getResetPasswordPolicyOptions( + policies, + invite.organizationId, + ); + + const isPolicyAndAutoEnrollEnabled = + resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled; + + const enforcedPasswordPolicyOptions = await firstValueFrom( + this.policyService.masterPasswordPolicyOptions$(policies), + ); + + return { + policies, + isPolicyAndAutoEnrollEnabled, + enforcedPasswordPolicyOptions, + }; } } diff --git a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts index 2faf3f85d10..a8502476144 100644 --- a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts +++ b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts @@ -15,8 +15,8 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym import { CsprngArray } from "@bitwarden/common/types/csprng"; import { MasterKey, UserKey } from "@bitwarden/common/types/key"; +import { OrganizationInvite } from "../../../../../../../../libs/auth/src/common/models/domain/organization-invite"; import { AcceptOrganizationInviteService } from "../../../organization-invite/accept-organization.service"; -import { OrganizationInvite } from "../../../organization-invite/organization-invite"; import { WebRegistrationFinishService } from "./web-registration-finish.service"; diff --git a/apps/web/src/app/auth/login/login.component.ts b/apps/web/src/app/auth/login/login.component.ts index 4daa87d22c2..4ca90a5c750 100644 --- a/apps/web/src/app/auth/login/login.component.ts +++ b/apps/web/src/app/auth/login/login.component.ts @@ -30,10 +30,10 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; +import { OrganizationInvite } from "../../../../../../libs/auth/src/common/models/domain/organization-invite"; import { flagEnabled } from "../../../utils/flags"; import { RouterService } from "../../core"; import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service"; -import { OrganizationInvite } from "../organization-invite/organization-invite"; @Component({ selector: "app-login", diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.component.ts b/apps/web/src/app/auth/organization-invite/accept-organization.component.ts index 6013688df22..576da0ff1c1 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.component.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.component.ts @@ -7,10 +7,10 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { OrganizationInvite } from "../../../../../../libs/auth/src/common/models/domain/organization-invite"; import { BaseAcceptComponent } from "../../common/base.accept.component"; import { AcceptOrganizationInviteService } from "./accept-organization.service"; -import { OrganizationInvite } from "./organization-invite"; @Component({ templateUrl: "accept-organization.component.html", diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts b/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts index 97a17a5997f..1f60d1977ea 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts @@ -17,13 +17,13 @@ import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { FakeGlobalState } from "@bitwarden/common/spec/fake-state"; import { OrgKey } from "@bitwarden/common/types/key"; +import { OrganizationInvite } from "../../../../../../libs/auth/src/common/models/domain/organization-invite"; import { I18nService } from "../../core/i18n.service"; import { AcceptOrganizationInviteService, ORGANIZATION_INVITE, } from "./accept-organization.service"; -import { OrganizationInvite } from "./organization-invite"; describe("AcceptOrganizationInviteService", () => { let sut: AcceptOrganizationInviteService; diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.service.ts b/apps/web/src/app/auth/organization-invite/accept-organization.service.ts index d1ffa61f6a9..d62b2a9087c 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.service.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.service.ts @@ -27,7 +27,7 @@ import { } from "@bitwarden/common/platform/state"; import { OrgKey } from "@bitwarden/common/types/key"; -import { OrganizationInvite } from "./organization-invite"; +import { OrganizationInvite } from "../../../../../../libs/auth/src/common/models/domain/organization-invite"; // We're storing the organization invite for 2 reasons: // 1. If the org requires a MP policy check, we need to keep track that the user has already been redirected when they return. diff --git a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts b/apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts index a7916ae946d..bbf66dca7f2 100644 --- a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts +++ b/apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts @@ -18,10 +18,10 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { OrganizationInvite } from "../../../../../../libs/auth/src/common/models/domain/organization-invite"; import { RouterService } from "../../core"; import { SharedModule } from "../../shared"; import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service"; -import { OrganizationInvite } from "../organization-invite/organization-invite"; import { TrialInitiationComponent } from "./trial-initiation.component"; import { VerticalStepperComponent } from "./vertical-stepper/vertical-stepper.component"; diff --git a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts b/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts index f8718b0a420..2605669e0bc 100644 --- a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts +++ b/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts @@ -14,13 +14,13 @@ import { ReferenceEventRequest } from "@bitwarden/common/models/request/referenc import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { OrganizationInvite } from "../../../../../../libs/auth/src/common/models/domain/organization-invite"; import { OrganizationCreatedEvent, SubscriptionProduct, TrialOrganizationType, } from "../../billing/accounts/trial-initiation/trial-billing-step.component"; import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service"; -import { OrganizationInvite } from "../organization-invite/organization-invite"; import { RouterService } from "./../../core/router.service"; import { VerticalStepperComponent } from "./vertical-stepper/vertical-stepper.component"; diff --git a/libs/auth/src/angular/login/default-login.service.ts b/libs/auth/src/angular/login/default-login.service.ts index b6f17736a06..3d383e99430 100644 --- a/libs/auth/src/angular/login/default-login.service.ts +++ b/libs/auth/src/angular/login/default-login.service.ts @@ -1,13 +1,19 @@ import { UrlTree } from "@angular/router"; -import { LoginService } from "./login.service"; +import { OrganizationInvite } from "@bitwarden/auth/common"; + +import { LoginService, PasswordPolicies } from "./login.service"; export class DefaultLoginService implements LoginService { setPreviousUrl(route: UrlTree): void | null { return null; } - async handleExistingOrgInvite(): Promise { + async getOrganizationInvite(): Promise { + return null; + } + + async getPasswordPolicies(invite: OrganizationInvite): Promise { return null; } } diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index 40c27bbbaf4..c932f557a5b 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -6,7 +6,12 @@ import { first, firstValueFrom, Subject, takeUntil } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { LoginEmailServiceAbstraction } from "@bitwarden/auth/common"; +import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; +import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { ClientType } from "@bitwarden/common/enums"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { AsyncActionsModule, @@ -41,18 +46,26 @@ export class LoginComponentV2 implements OnInit { private destroy$ = new Subject(); + // Web specific properties + enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions; + policies: Policy[]; + showResetPasswordAutoEnrollWarning = false; + constructor( private activatedRoute: ActivatedRoute, private formBuilder: FormBuilder, private loginEmailService: LoginEmailServiceAbstraction, private loginService: LoginService, + private logService: LogService, private platformUtilsService: PlatformUtilsService, + private policyApiService: PolicyApiServiceAbstraction, + private policyService: InternalPolicyService, private router: Router, - ) {} + ) { + this.clientType = this.platformUtilsService.getClientType(); + } async ngOnInit(): Promise { - this.clientType = this.platformUtilsService.getClientType(); - if (this.clientType === ClientType.Web) { await this.webOnInit(); } @@ -66,17 +79,30 @@ export class LoginComponentV2 implements OnInit { const qParamsEmail = params.email; + // If there is an email in the query params, set that email as the form field value if (qParamsEmail?.indexOf("@") > -1) { this.formGroup.controls.email.setValue(qParamsEmail); paramEmailIsSet = true; } }); + // If there is no email in the query params, attempt to load email settings from loginEmailService if (!paramEmailIsSet) { await this.loadEmailSettings(); } - // If there's an existing org invite, use it to get the password policies - await this.loginService.handleExistingOrgInvite(); + + if (this.clientType === ClientType.Web) { + // If there's an existing org invite, use it to get the password policies + const orgInvite = await this.loginService.getOrganizationInvite(); + if (orgInvite != null) { + const { policies, isPolicyAndAutoEnrollEnabled, enforcedPasswordPolicyOptions } = + await this.loginService.getPasswordPolicies(orgInvite); + + this.policies = policies; + this.showResetPasswordAutoEnrollWarning = isPolicyAndAutoEnrollEnabled; + this.enforcedPasswordPolicyOptions = enforcedPasswordPolicyOptions; + } + } } submit = async () => {}; diff --git a/libs/auth/src/angular/login/login.service.ts b/libs/auth/src/angular/login/login.service.ts index 1eb18a4675b..40b2d1d78fa 100644 --- a/libs/auth/src/angular/login/login.service.ts +++ b/libs/auth/src/angular/login/login.service.ts @@ -1,7 +1,18 @@ import { UrlTree } from "@angular/router"; +import { OrganizationInvite } from "@bitwarden/auth/common"; +import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; + +export interface PasswordPolicies { + policies: Policy[]; + isPolicyAndAutoEnrollEnabled: boolean; + enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions; +} + export abstract class LoginService { // Web specific + getOrganizationInvite: () => Promise; + getPasswordPolicies: (invite: OrganizationInvite) => Promise; setPreviousUrl: (route: UrlTree) => void | null; - handleExistingOrgInvite: () => Promise; } diff --git a/libs/auth/src/common/models/domain/index.ts b/libs/auth/src/common/models/domain/index.ts index b8b83711a4a..be3e322dcc6 100644 --- a/libs/auth/src/common/models/domain/index.ts +++ b/libs/auth/src/common/models/domain/index.ts @@ -1,3 +1,4 @@ +export * from "./organization-invite"; export * from "./rotateable-key-set"; export * from "./login-credentials"; export * from "./user-decryption-options"; diff --git a/apps/web/src/app/auth/organization-invite/organization-invite.ts b/libs/auth/src/common/models/domain/organization-invite.ts similarity index 100% rename from apps/web/src/app/auth/organization-invite/organization-invite.ts rename to libs/auth/src/common/models/domain/organization-invite.ts