mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 00:03:56 +00:00
Merge branch 'main' into ps/PM-7846-rust-ipc
This commit is contained in:
@@ -556,6 +556,18 @@
|
||||
"security": {
|
||||
"message": "Security"
|
||||
},
|
||||
"confirmMasterPassword": {
|
||||
"message": "Confirm master password"
|
||||
},
|
||||
"masterPassword": {
|
||||
"message": "Master password"
|
||||
},
|
||||
"masterPassImportant": {
|
||||
"message": "Your master password cannot be recovered if you forget it!"
|
||||
},
|
||||
"masterPassHintLabel": {
|
||||
"message": "Master password hint"
|
||||
},
|
||||
"errorOccurred": {
|
||||
"message": "An error has occurred"
|
||||
},
|
||||
@@ -2164,6 +2176,9 @@
|
||||
"emailVerificationRequired": {
|
||||
"message": "Email verification required"
|
||||
},
|
||||
"emailVerifiedV2": {
|
||||
"message": "Email verified"
|
||||
},
|
||||
"emailVerificationRequiredDesc": {
|
||||
"message": "You must verify your email to use this feature. You can verify your email in the web vault."
|
||||
},
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
</form>
|
||||
<p class="createAccountLink">
|
||||
{{ "newAroundHere" | i18n }}
|
||||
<a [routerLink]="registerRoute" (click)="setLoginEmailValues()">{{
|
||||
<a [routerLink]="registerRoute$ | async" (click)="setLoginEmailValues()">{{
|
||||
"createAccount" | i18n
|
||||
}}</a>
|
||||
</p>
|
||||
|
||||
@@ -4,9 +4,7 @@ import { Router } from "@angular/router";
|
||||
import { Subject, firstValueFrom, takeUntil } from "rxjs";
|
||||
|
||||
import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component";
|
||||
import { LoginEmailServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { LoginEmailServiceAbstraction, RegisterRouteService } from "@bitwarden/auth/common";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@@ -29,7 +27,7 @@ export class HomeComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
|
||||
// TODO: remove when email verification flag is removed
|
||||
registerRoute = "/register";
|
||||
registerRoute$ = this.registerRouteService.registerRoute$();
|
||||
|
||||
constructor(
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
@@ -39,19 +37,10 @@ export class HomeComponent implements OnInit, OnDestroy {
|
||||
private environmentService: EnvironmentService,
|
||||
private loginEmailService: LoginEmailServiceAbstraction,
|
||||
private accountSwitcherService: AccountSwitcherService,
|
||||
private configService: ConfigService,
|
||||
private registerRouteService: RegisterRouteService,
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
// TODO: remove when email verification flag is removed
|
||||
const emailVerification = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.EmailVerification,
|
||||
);
|
||||
|
||||
if (emailVerification) {
|
||||
this.registerRoute = "/signup";
|
||||
}
|
||||
|
||||
const email = this.loginEmailService.getEmail();
|
||||
const rememberEmail = this.loginEmailService.getRememberEmail();
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@ import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstrac
|
||||
import {
|
||||
LoginStrategyServiceAbstraction,
|
||||
LoginEmailServiceAbstraction,
|
||||
RegisterRouteService,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -52,7 +52,7 @@ export class LoginComponent extends BaseLoginComponent {
|
||||
loginEmailService: LoginEmailServiceAbstraction,
|
||||
ssoLoginService: SsoLoginServiceAbstraction,
|
||||
webAuthnLoginService: WebAuthnLoginServiceAbstraction,
|
||||
configService: ConfigService,
|
||||
registerRouteService: RegisterRouteService,
|
||||
) {
|
||||
super(
|
||||
devicesApiService,
|
||||
@@ -73,7 +73,7 @@ export class LoginComponent extends BaseLoginComponent {
|
||||
loginEmailService,
|
||||
ssoLoginService,
|
||||
webAuthnLoginService,
|
||||
configService,
|
||||
registerRouteService,
|
||||
);
|
||||
super.onSuccessfulLogin = async () => {
|
||||
await syncService.fullSync(true);
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
</div>
|
||||
<div class="sub-options">
|
||||
<p class="no-margin">{{ "newAroundHere" | i18n }}</p>
|
||||
<button type="button" class="text text-primary" [routerLink]="registerRoute">
|
||||
<button type="button" class="text text-primary" [routerLink]="registerRoute$ | async">
|
||||
{{ "createAccount" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -9,13 +9,13 @@ import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import {
|
||||
LoginStrategyServiceAbstraction,
|
||||
LoginEmailServiceAbstraction,
|
||||
RegisterRouteService,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -72,7 +72,7 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
|
||||
loginEmailService: LoginEmailServiceAbstraction,
|
||||
ssoLoginService: SsoLoginServiceAbstraction,
|
||||
webAuthnLoginService: WebAuthnLoginServiceAbstraction,
|
||||
configService: ConfigService,
|
||||
registerRouteService: RegisterRouteService,
|
||||
) {
|
||||
super(
|
||||
devicesApiService,
|
||||
@@ -93,7 +93,7 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
|
||||
loginEmailService,
|
||||
ssoLoginService,
|
||||
webAuthnLoginService,
|
||||
configService,
|
||||
registerRouteService,
|
||||
);
|
||||
super.onSuccessfulLogin = () => {
|
||||
return syncService.fullSync(true);
|
||||
|
||||
@@ -539,6 +539,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"masterPassword": {
|
||||
"message": "Master password"
|
||||
},
|
||||
"masterPassImportant": {
|
||||
"message": "Your master password cannot be recovered if you forget it!"
|
||||
},
|
||||
"confirmMasterPassword": {
|
||||
"message": "Confirm master password"
|
||||
},
|
||||
"masterPassHintLabel": {
|
||||
"message": "Master password hint"
|
||||
},
|
||||
"settings": {
|
||||
"message": "Settings"
|
||||
},
|
||||
@@ -1955,6 +1967,9 @@
|
||||
"emailVerificationRequired": {
|
||||
"message": "Email verification required"
|
||||
},
|
||||
"emailVerifiedV2": {
|
||||
"message": "Email verified"
|
||||
},
|
||||
"emailVerificationRequiredDesc": {
|
||||
"message": "You must verify your email to use this feature."
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Params } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { BaseAcceptComponent } from "../../../common/base.accept.component";
|
||||
|
||||
@@ -25,9 +26,9 @@ export class AcceptFamilySponsorshipComponent extends BaseAcceptComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["/login"], { queryParams: { email: qParams.email } });
|
||||
} else {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate([this.registerRoute], { queryParams: { email: qParams.email } });
|
||||
// TODO: remove when email verification flag is removed
|
||||
const registerRoute = await firstValueFrom(this.registerRoute$);
|
||||
await this.router.navigate([registerRoute], { queryParams: { email: qParams.email } });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./webauthn-login";
|
||||
export * from "./registration";
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./web-registration-finish.service";
|
||||
@@ -0,0 +1,241 @@
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { PasswordInputResult } from "@bitwarden/auth/angular";
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
import { PolicyService } 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 { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service";
|
||||
import { DEFAULT_KDF_CONFIG } from "@bitwarden/common/auth/models/domain/kdf-config";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { AcceptOrganizationInviteService } from "../../../organization-invite/accept-organization.service";
|
||||
import { OrganizationInvite } from "../../../organization-invite/organization-invite";
|
||||
|
||||
import { WebRegistrationFinishService } from "./web-registration-finish.service";
|
||||
|
||||
describe("DefaultRegistrationFinishService", () => {
|
||||
let service: WebRegistrationFinishService;
|
||||
|
||||
let cryptoService: MockProxy<CryptoService>;
|
||||
let accountApiService: MockProxy<AccountApiService>;
|
||||
let acceptOrgInviteService: MockProxy<AcceptOrganizationInviteService>;
|
||||
let policyApiService: MockProxy<PolicyApiServiceAbstraction>;
|
||||
let logService: MockProxy<LogService>;
|
||||
let policyService: MockProxy<PolicyService>;
|
||||
|
||||
beforeEach(() => {
|
||||
cryptoService = mock<CryptoService>();
|
||||
accountApiService = mock<AccountApiService>();
|
||||
acceptOrgInviteService = mock<AcceptOrganizationInviteService>();
|
||||
policyApiService = mock<PolicyApiServiceAbstraction>();
|
||||
logService = mock<LogService>();
|
||||
policyService = mock<PolicyService>();
|
||||
|
||||
service = new WebRegistrationFinishService(
|
||||
cryptoService,
|
||||
accountApiService,
|
||||
acceptOrgInviteService,
|
||||
policyApiService,
|
||||
logService,
|
||||
policyService,
|
||||
);
|
||||
});
|
||||
|
||||
it("instantiates", () => {
|
||||
expect(service).not.toBeFalsy();
|
||||
});
|
||||
|
||||
describe("getMasterPasswordPolicyOptsFromOrgInvite()", () => {
|
||||
let orgInvite: OrganizationInvite | null;
|
||||
|
||||
beforeEach(() => {
|
||||
orgInvite = new OrganizationInvite();
|
||||
orgInvite.organizationId = "organizationId";
|
||||
orgInvite.organizationUserId = "organizationUserId";
|
||||
orgInvite.token = "orgInviteToken";
|
||||
orgInvite.email = "email";
|
||||
});
|
||||
|
||||
it("returns null when the org invite is null", async () => {
|
||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||
|
||||
const result = await service.getMasterPasswordPolicyOptsFromOrgInvite();
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(acceptOrgInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns null when the policies are null", async () => {
|
||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||
policyApiService.getPoliciesByToken.mockResolvedValue(null);
|
||||
|
||||
const result = await service.getMasterPasswordPolicyOptsFromOrgInvite();
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(acceptOrgInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||
expect(policyApiService.getPoliciesByToken).toHaveBeenCalledWith(
|
||||
orgInvite.organizationId,
|
||||
orgInvite.token,
|
||||
orgInvite.email,
|
||||
orgInvite.organizationUserId,
|
||||
);
|
||||
});
|
||||
|
||||
it("logs an error and returns null when policies cannot be fetched", async () => {
|
||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||
policyApiService.getPoliciesByToken.mockRejectedValue(new Error("error"));
|
||||
|
||||
const result = await service.getMasterPasswordPolicyOptsFromOrgInvite();
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(acceptOrgInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||
expect(policyApiService.getPoliciesByToken).toHaveBeenCalledWith(
|
||||
orgInvite.organizationId,
|
||||
orgInvite.token,
|
||||
orgInvite.email,
|
||||
orgInvite.organizationUserId,
|
||||
);
|
||||
expect(logService.error).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns the master password policy options from the organization invite when it exists", async () => {
|
||||
const masterPasswordPolicies = [new Policy()];
|
||||
const masterPasswordPolicyOptions = new MasterPasswordPolicyOptions();
|
||||
|
||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||
policyApiService.getPoliciesByToken.mockResolvedValue(masterPasswordPolicies);
|
||||
policyService.masterPasswordPolicyOptions$.mockReturnValue(of(masterPasswordPolicyOptions));
|
||||
|
||||
const result = await service.getMasterPasswordPolicyOptsFromOrgInvite();
|
||||
|
||||
expect(result).toEqual(masterPasswordPolicyOptions);
|
||||
expect(acceptOrgInviteService.getOrganizationInvite).toHaveBeenCalled();
|
||||
expect(policyApiService.getPoliciesByToken).toHaveBeenCalledWith(
|
||||
orgInvite.organizationId,
|
||||
orgInvite.token,
|
||||
orgInvite.email,
|
||||
orgInvite.organizationUserId,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("finishRegistration()", () => {
|
||||
let email: string;
|
||||
let emailVerificationToken: string;
|
||||
let masterKey: MasterKey;
|
||||
let passwordInputResult: PasswordInputResult;
|
||||
let userKey: UserKey;
|
||||
let userKeyEncString: EncString;
|
||||
let userKeyPair: [string, EncString];
|
||||
let capchaBypassToken: string;
|
||||
|
||||
let orgInvite: OrganizationInvite;
|
||||
|
||||
beforeEach(() => {
|
||||
email = "test@email.com";
|
||||
emailVerificationToken = "emailVerificationToken";
|
||||
masterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey;
|
||||
passwordInputResult = {
|
||||
masterKey: masterKey,
|
||||
masterKeyHash: "masterKeyHash",
|
||||
kdfConfig: DEFAULT_KDF_CONFIG,
|
||||
hint: "hint",
|
||||
};
|
||||
|
||||
userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey;
|
||||
userKeyEncString = new EncString("userKeyEncrypted");
|
||||
|
||||
userKeyPair = ["publicKey", new EncString("privateKey")];
|
||||
capchaBypassToken = "capchaBypassToken";
|
||||
|
||||
orgInvite = new OrganizationInvite();
|
||||
orgInvite.organizationUserId = "organizationUserId";
|
||||
orgInvite.token = "orgInviteToken";
|
||||
});
|
||||
|
||||
it("throws an error if the user key cannot be created", async () => {
|
||||
cryptoService.makeUserKey.mockResolvedValue([null, null]);
|
||||
|
||||
await expect(service.finishRegistration(email, passwordInputResult)).rejects.toThrow(
|
||||
"User key could not be created",
|
||||
);
|
||||
});
|
||||
|
||||
it("registers the user and returns a captcha bypass token when given valid email verification input", async () => {
|
||||
cryptoService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
||||
cryptoService.makeKeyPair.mockResolvedValue(userKeyPair);
|
||||
accountApiService.registerFinish.mockResolvedValue(capchaBypassToken);
|
||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||
|
||||
const result = await service.finishRegistration(
|
||||
email,
|
||||
passwordInputResult,
|
||||
emailVerificationToken,
|
||||
);
|
||||
|
||||
expect(result).toEqual(capchaBypassToken);
|
||||
|
||||
expect(cryptoService.makeUserKey).toHaveBeenCalledWith(masterKey);
|
||||
expect(cryptoService.makeKeyPair).toHaveBeenCalledWith(userKey);
|
||||
expect(accountApiService.registerFinish).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
email,
|
||||
emailVerificationToken: emailVerificationToken,
|
||||
masterPasswordHash: passwordInputResult.masterKeyHash,
|
||||
masterPasswordHint: passwordInputResult.hint,
|
||||
userSymmetricKey: userKeyEncString.encryptedString,
|
||||
userAsymmetricKeys: {
|
||||
publicKey: userKeyPair[0],
|
||||
encryptedPrivateKey: userKeyPair[1].encryptedString,
|
||||
},
|
||||
kdf: passwordInputResult.kdfConfig.kdfType,
|
||||
kdfIterations: passwordInputResult.kdfConfig.iterations,
|
||||
kdfMemory: undefined,
|
||||
kdfParallelism: undefined,
|
||||
orgInviteToken: undefined,
|
||||
organizationUserId: undefined,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("it registers the user and returns a captcha bypass token when given an org invite", async () => {
|
||||
cryptoService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
||||
cryptoService.makeKeyPair.mockResolvedValue(userKeyPair);
|
||||
accountApiService.registerFinish.mockResolvedValue(capchaBypassToken);
|
||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||
|
||||
const result = await service.finishRegistration(email, passwordInputResult);
|
||||
|
||||
expect(result).toEqual(capchaBypassToken);
|
||||
|
||||
expect(cryptoService.makeUserKey).toHaveBeenCalledWith(masterKey);
|
||||
expect(cryptoService.makeKeyPair).toHaveBeenCalledWith(userKey);
|
||||
expect(accountApiService.registerFinish).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
email,
|
||||
emailVerificationToken: undefined,
|
||||
masterPasswordHash: passwordInputResult.masterKeyHash,
|
||||
masterPasswordHint: passwordInputResult.hint,
|
||||
userSymmetricKey: userKeyEncString.encryptedString,
|
||||
userAsymmetricKeys: {
|
||||
publicKey: userKeyPair[0],
|
||||
encryptedPrivateKey: userKeyPair[1].encryptedString,
|
||||
},
|
||||
kdf: passwordInputResult.kdfConfig.kdfType,
|
||||
kdfIterations: passwordInputResult.kdfConfig.iterations,
|
||||
kdfMemory: undefined,
|
||||
kdfParallelism: undefined,
|
||||
orgInviteToken: orgInvite.token,
|
||||
organizationUserId: orgInvite.organizationUserId,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import {
|
||||
DefaultRegistrationFinishService,
|
||||
PasswordInputResult,
|
||||
RegistrationFinishService,
|
||||
} from "@bitwarden/auth/angular";
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
import { PolicyService } 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 { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service";
|
||||
import { RegisterFinishRequest } from "@bitwarden/common/auth/models/request/registration/register-finish.request";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
|
||||
import { AcceptOrganizationInviteService } from "../../../organization-invite/accept-organization.service";
|
||||
|
||||
export class WebRegistrationFinishService
|
||||
extends DefaultRegistrationFinishService
|
||||
implements RegistrationFinishService
|
||||
{
|
||||
constructor(
|
||||
protected cryptoService: CryptoService,
|
||||
protected accountApiService: AccountApiService,
|
||||
private acceptOrgInviteService: AcceptOrganizationInviteService,
|
||||
private policyApiService: PolicyApiServiceAbstraction,
|
||||
private logService: LogService,
|
||||
private policyService: PolicyService,
|
||||
) {
|
||||
super(cryptoService, accountApiService);
|
||||
}
|
||||
|
||||
override async getMasterPasswordPolicyOptsFromOrgInvite(): Promise<MasterPasswordPolicyOptions | null> {
|
||||
// If there's a deep linked org invite, use it to get the password policies
|
||||
const orgInvite = await this.acceptOrgInviteService.getOrganizationInvite();
|
||||
|
||||
if (orgInvite == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let policies: Policy[] | null = null;
|
||||
try {
|
||||
policies = await this.policyApiService.getPoliciesByToken(
|
||||
orgInvite.organizationId,
|
||||
orgInvite.token,
|
||||
orgInvite.email,
|
||||
orgInvite.organizationUserId,
|
||||
);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
|
||||
if (policies == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const masterPasswordPolicyOpts: MasterPasswordPolicyOptions = await firstValueFrom(
|
||||
this.policyService.masterPasswordPolicyOptions$(policies),
|
||||
);
|
||||
|
||||
return masterPasswordPolicyOpts;
|
||||
}
|
||||
|
||||
// Note: the org invite token and email verification are mutually exclusive. Only one will be present.
|
||||
override async buildRegisterRequest(
|
||||
email: string,
|
||||
emailVerificationToken: string,
|
||||
passwordInputResult: PasswordInputResult,
|
||||
encryptedUserKey: EncryptedString,
|
||||
userAsymmetricKeys: [string, EncString],
|
||||
): Promise<RegisterFinishRequest> {
|
||||
const registerRequest = await super.buildRegisterRequest(
|
||||
email,
|
||||
emailVerificationToken,
|
||||
passwordInputResult,
|
||||
encryptedUserKey,
|
||||
userAsymmetricKeys,
|
||||
);
|
||||
|
||||
// web specific logic
|
||||
// Org invites are deep linked. Non-existent accounts are redirected to the register page.
|
||||
// Org user id and token are included here only for validation and two factor purposes.
|
||||
const orgInvite = await this.acceptOrgInviteService.getOrganizationInvite();
|
||||
if (orgInvite != null) {
|
||||
registerRequest.organizationUserId = orgInvite.organizationUserId;
|
||||
registerRequest.orgInviteToken = orgInvite.token;
|
||||
}
|
||||
// Invite is accepted after login (on deep link redirect).
|
||||
|
||||
return registerRequest;
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
<a
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
[routerLink]="registerRoute"
|
||||
[routerLink]="registerRoute$ | async"
|
||||
[queryParams]="{ email: email }"
|
||||
[block]="true"
|
||||
>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute, Params, Router } from "@angular/router";
|
||||
|
||||
import { RegisterRouteService } from "@bitwarden/auth/common";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
@@ -29,10 +29,10 @@ export class AcceptEmergencyComponent extends BaseAcceptComponent {
|
||||
i18nService: I18nService,
|
||||
route: ActivatedRoute,
|
||||
authService: AuthService,
|
||||
configService: ConfigService,
|
||||
registerRouteService: RegisterRouteService,
|
||||
private emergencyAccessService: EmergencyAccessService,
|
||||
) {
|
||||
super(router, platformUtilsService, i18nService, route, authService, configService);
|
||||
super(router, platformUtilsService, i18nService, route, authService, registerRouteService);
|
||||
}
|
||||
|
||||
async authedHandler(qParams: Params): Promise<void> {
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
clicking on the link. Mousedown fires before onBlur.
|
||||
-->
|
||||
<a
|
||||
[routerLink]="registerRoute"
|
||||
[routerLink]="registerRoute$ | async"
|
||||
[queryParams]="emailFormControl.valid ? { email: emailFormControl.value } : {}"
|
||||
(mousedown)="goToRegister()"
|
||||
>{{ "createAccount" | i18n }}</a
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, NgZone, OnInit } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { takeUntil } from "rxjs";
|
||||
import { firstValueFrom, takeUntil } from "rxjs";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component";
|
||||
@@ -9,6 +9,7 @@ import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstrac
|
||||
import {
|
||||
LoginStrategyServiceAbstraction,
|
||||
LoginEmailServiceAbstraction,
|
||||
RegisterRouteService,
|
||||
} 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";
|
||||
@@ -20,7 +21,6 @@ import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/
|
||||
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -68,7 +68,7 @@ export class LoginComponent extends BaseLoginComponent implements OnInit {
|
||||
loginEmailService: LoginEmailServiceAbstraction,
|
||||
ssoLoginService: SsoLoginServiceAbstraction,
|
||||
webAuthnLoginService: WebAuthnLoginServiceAbstraction,
|
||||
configService: ConfigService,
|
||||
registerRouteService: RegisterRouteService,
|
||||
) {
|
||||
super(
|
||||
devicesApiService,
|
||||
@@ -89,7 +89,7 @@ export class LoginComponent extends BaseLoginComponent implements OnInit {
|
||||
loginEmailService,
|
||||
ssoLoginService,
|
||||
webAuthnLoginService,
|
||||
configService,
|
||||
registerRouteService,
|
||||
);
|
||||
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
|
||||
this.showPasswordless = flagEnabled("showPasswordless");
|
||||
@@ -165,14 +165,17 @@ export class LoginComponent extends BaseLoginComponent implements OnInit {
|
||||
}
|
||||
|
||||
async goToRegister() {
|
||||
// TODO: remove when email verification flag is removed
|
||||
const registerRoute = await firstValueFrom(this.registerRoute$);
|
||||
|
||||
if (this.emailFormControl.valid) {
|
||||
await this.router.navigate([this.registerRoute], {
|
||||
await this.router.navigate([registerRoute], {
|
||||
queryParams: { email: this.emailFormControl.value },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await this.router.navigate([this.registerRoute]);
|
||||
await this.router.navigate([registerRoute]);
|
||||
}
|
||||
|
||||
protected override async handleMigrateEncryptionKey(result: AuthResult): Promise<boolean> {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
{{ "logIn" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
[routerLink]="registerRoute"
|
||||
[routerLink]="registerRoute$ | async"
|
||||
[queryParams]="{ email: email }"
|
||||
class="btn btn-primary btn-block ml-2 mt-0"
|
||||
>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute, Params, Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { RegisterRouteService } from "@bitwarden/auth/common";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
@@ -24,10 +25,10 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
|
||||
i18nService: I18nService,
|
||||
route: ActivatedRoute,
|
||||
authService: AuthService,
|
||||
configService: ConfigService,
|
||||
registerRouteService: RegisterRouteService,
|
||||
private acceptOrganizationInviteService: AcceptOrganizationInviteService,
|
||||
) {
|
||||
super(router, platformUtilsService, i18nService, route, authService, configService);
|
||||
super(router, platformUtilsService, i18nService, route, authService, registerRouteService);
|
||||
}
|
||||
|
||||
async authedHandler(qParams: Params): Promise<void> {
|
||||
@@ -91,22 +92,23 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
|
||||
|
||||
// TODO: update logic when email verification flag is removed
|
||||
let queryParams: Params;
|
||||
if (this.registerRoute === "/register") {
|
||||
let registerRoute = await firstValueFrom(this.registerRoute$);
|
||||
if (registerRoute === "/register") {
|
||||
queryParams = {
|
||||
fromOrgInvite: "true",
|
||||
email: invite.email,
|
||||
};
|
||||
} else if (this.registerRoute === "/signup") {
|
||||
} else if (registerRoute === "/signup") {
|
||||
// We have to override the base component route b/c it is correct for other components
|
||||
// that extend the base accept comp. We don't need users to complete email verification
|
||||
// if they are coming directly from an emailed org invite.
|
||||
this.registerRoute = "/finish-signup";
|
||||
registerRoute = "/finish-signup";
|
||||
queryParams = {
|
||||
email: invite.email,
|
||||
};
|
||||
}
|
||||
|
||||
await this.router.navigate([this.registerRoute], {
|
||||
await this.router.navigate([registerRoute], {
|
||||
queryParams: queryParams,
|
||||
});
|
||||
return;
|
||||
|
||||
@@ -3,10 +3,9 @@ import { ActivatedRoute, Params, Router } from "@angular/router";
|
||||
import { Subject, firstValueFrom } from "rxjs";
|
||||
import { first, switchMap, takeUntil } from "rxjs/operators";
|
||||
|
||||
import { RegisterRouteService } from "@bitwarden/auth/common";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
@@ -22,7 +21,7 @@ export abstract class BaseAcceptComponent implements OnInit {
|
||||
protected failedMessage = "inviteAcceptFailed";
|
||||
|
||||
// TODO: remove when email verification flag is removed
|
||||
registerRoute = "/register";
|
||||
registerRoute$ = this.registerRouteService.registerRoute$();
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
@@ -32,22 +31,13 @@ export abstract class BaseAcceptComponent implements OnInit {
|
||||
protected i18nService: I18nService,
|
||||
protected route: ActivatedRoute,
|
||||
protected authService: AuthService,
|
||||
private configService: ConfigService,
|
||||
protected registerRouteService: RegisterRouteService,
|
||||
) {}
|
||||
|
||||
abstract authedHandler(qParams: Params): Promise<void>;
|
||||
abstract unauthedHandler(qParams: Params): Promise<void>;
|
||||
|
||||
async ngOnInit() {
|
||||
// TODO: remove when email verification flag is removed
|
||||
const emailVerification = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.EmailVerification,
|
||||
);
|
||||
|
||||
if (emailVerification) {
|
||||
this.registerRoute = "/signup";
|
||||
}
|
||||
|
||||
this.route.queryParams
|
||||
.pipe(
|
||||
first(),
|
||||
|
||||
@@ -18,8 +18,13 @@ import {
|
||||
} from "@bitwarden/angular/services/injection-tokens";
|
||||
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
||||
import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service";
|
||||
import { RegistrationFinishService as RegistrationFinishServiceAbstraction } from "@bitwarden/auth/angular";
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/account-api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -45,6 +50,8 @@ import {
|
||||
import { VaultTimeout, VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type";
|
||||
|
||||
import { PolicyListService } from "../admin-console/core/policy-list.service";
|
||||
import { WebRegistrationFinishService } from "../auth";
|
||||
import { AcceptOrganizationInviteService } from "../auth/organization-invite/accept-organization.service";
|
||||
import { HtmlStorageService } from "../core/html-storage.service";
|
||||
import { I18nService } from "../core/i18n.service";
|
||||
import { WebEnvironmentService } from "../platform/web-environment.service";
|
||||
@@ -171,6 +178,18 @@ const safeProviders: SafeProvider[] = [
|
||||
provide: CLIENT_TYPE,
|
||||
useValue: ClientType.Web,
|
||||
}),
|
||||
safeProvider({
|
||||
provide: RegistrationFinishServiceAbstraction,
|
||||
useClass: WebRegistrationFinishService,
|
||||
deps: [
|
||||
CryptoServiceAbstraction,
|
||||
AccountApiServiceAbstraction,
|
||||
AcceptOrganizationInviteService,
|
||||
PolicyApiServiceAbstraction,
|
||||
LogService,
|
||||
PolicyService,
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
>Bitwarden Send</a
|
||||
>
|
||||
{{ "sendAccessTaglineOr" | i18n }}
|
||||
<a bitLink [routerLink]="registerRoute" target="_blank" rel="noreferrer">{{
|
||||
<a bitLink [routerLink]="registerRoute$ | async" target="_blank" rel="noreferrer">{{
|
||||
"sendAccessTaglineSignUp" | i18n
|
||||
}}</a>
|
||||
{{ "sendAccessTaglineTryToday" | i18n }}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component, OnInit } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { RegisterRouteService } from "@bitwarden/auth/common";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
@@ -56,7 +56,7 @@ export class AccessComponent implements OnInit {
|
||||
protected formGroup = this.formBuilder.group({});
|
||||
|
||||
// TODO: remove when email verification flag is removed
|
||||
registerRoute = "/register";
|
||||
registerRoute$ = this.registerRouteService.registerRoute$();
|
||||
|
||||
private id: string;
|
||||
private key: string;
|
||||
@@ -69,6 +69,7 @@ export class AccessComponent implements OnInit {
|
||||
private toastService: ToastService,
|
||||
private i18nService: I18nService,
|
||||
private configService: ConfigService,
|
||||
private registerRouteService: RegisterRouteService,
|
||||
protected formBuilder: FormBuilder,
|
||||
) {}
|
||||
|
||||
@@ -87,15 +88,6 @@ export class AccessComponent implements OnInit {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
// TODO: remove when email verification flag is removed
|
||||
const emailVerification = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.EmailVerification,
|
||||
);
|
||||
|
||||
if (emailVerification) {
|
||||
this.registerRoute = "/signup";
|
||||
}
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
this.route.params.subscribe(async (params) => {
|
||||
this.id = params.sendId;
|
||||
|
||||
@@ -3399,6 +3399,9 @@
|
||||
"emailVerified": {
|
||||
"message": "Account email verified"
|
||||
},
|
||||
"emailVerifiedV2": {
|
||||
"message": "Email verified"
|
||||
},
|
||||
"emailVerifiedFailed": {
|
||||
"message": "Unable to verify your email. Try sending a new verification email."
|
||||
},
|
||||
@@ -8523,6 +8526,14 @@
|
||||
},
|
||||
"noInvoicesToList": {
|
||||
"message": "There are no invoices to list",
|
||||
"description": "A paragraph on the Billing History page of the Provider Portal letting users know they can download a CSV report for their invoices that does not include prorations."
|
||||
"description": "A paragraph on the Billing History page of the Provider Portal letting users know they can download a CSV report for their invoices that does not include prorations."
|
||||
},
|
||||
"providerClientVaultPrivacyNotification": {
|
||||
"message": "Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions,",
|
||||
"description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions, please contact Bitwarden support'."
|
||||
},
|
||||
"contactBitwardenSupport": {
|
||||
"message": "contact Bitwarden support.",
|
||||
"description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions, please contact Bitwarden support'. 'Bitwarden' should not be translated"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user