mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 13:53:34 +00:00
chore(captcha): [PM-15162] Remove handling of captcha enforcement and bypass token
* Removed captcha references. * Removed connectors from webpack * Fixed extra parameter. * Resolve merge conflicts. * Fixed extra argument. * Fixed failing tests. * Fixed failing test. * Accessibility cookie cleanup * Cleaned up accessibility component. * Deleted old registration endpoint * Remove unused register request object. * Fixed merge error that changed font family. * Fixed formatting from merge. * Linting
This commit is contained in:
@@ -211,12 +211,6 @@ p.lead {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#hcaptcha_iframe {
|
|
||||||
width: 100%;
|
|
||||||
border: none;
|
|
||||||
transition: height 0.25s linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.linux-webauthn {
|
body.linux-webauthn {
|
||||||
width: 485px !important;
|
width: 485px !important;
|
||||||
#web-authn-frame {
|
#web-authn-frame {
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import { ClientType } from "@bitwarden/common/enums";
|
|||||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||||
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
|
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
|
||||||
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
@@ -222,7 +221,7 @@ export class LoginCommand {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
response = await this.loginStrategyService.logIn(
|
response = await this.loginStrategyService.logIn(
|
||||||
new PasswordLoginCredentials(email, password, null, twoFactor),
|
new PasswordLoginCredentials(email, password, twoFactor),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (response.requiresEncryptionKeyMigration) {
|
if (response.requiresEncryptionKeyMigration) {
|
||||||
@@ -230,17 +229,6 @@ export class LoginCommand {
|
|||||||
"Encryption key migration required. Please login through the web vault to update your encryption key.",
|
"Encryption key migration required. Please login through the web vault to update your encryption key.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (response.captchaSiteKey) {
|
|
||||||
const credentials = new PasswordLoginCredentials(email, password);
|
|
||||||
const handledResponse = await this.handleCaptchaRequired(twoFactor, credentials);
|
|
||||||
|
|
||||||
// Error Response
|
|
||||||
if (handledResponse instanceof Response) {
|
|
||||||
return handledResponse;
|
|
||||||
} else {
|
|
||||||
response = handledResponse;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (response.requiresTwoFactor) {
|
if (response.requiresTwoFactor) {
|
||||||
const twoFactorProviders = await this.twoFactorService.getSupportedProviders(null);
|
const twoFactorProviders = await this.twoFactorService.getSupportedProviders(null);
|
||||||
if (twoFactorProviders.length === 0) {
|
if (twoFactorProviders.length === 0) {
|
||||||
@@ -312,7 +300,6 @@ export class LoginCommand {
|
|||||||
|
|
||||||
response = await this.loginStrategyService.logInTwoFactor(
|
response = await this.loginStrategyService.logInTwoFactor(
|
||||||
new TokenTwoFactorRequest(selectedProvider.type, twoFactorToken),
|
new TokenTwoFactorRequest(selectedProvider.type, twoFactorToken),
|
||||||
null,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,18 +323,6 @@ export class LoginCommand {
|
|||||||
response = await this.loginStrategyService.logInNewDeviceVerification(newDeviceToken);
|
response = await this.loginStrategyService.logInNewDeviceVerification(newDeviceToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.captchaSiteKey) {
|
|
||||||
const twoFactorRequest = new TokenTwoFactorRequest(selectedProvider.type, twoFactorToken);
|
|
||||||
const handledResponse = await this.handleCaptchaRequired(twoFactorRequest);
|
|
||||||
|
|
||||||
// Error Response
|
|
||||||
if (handledResponse instanceof Response) {
|
|
||||||
return handledResponse;
|
|
||||||
} else {
|
|
||||||
response = handledResponse;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.requiresTwoFactor) {
|
if (response.requiresTwoFactor) {
|
||||||
return Response.error("Login failed.");
|
return Response.error("Login failed.");
|
||||||
}
|
}
|
||||||
@@ -629,48 +604,6 @@ export class LoginCommand {
|
|||||||
return { newPasswordHash, newUserKey: newUserKey, hint: masterPasswordHint };
|
return { newPasswordHash, newUserKey: newUserKey, hint: masterPasswordHint };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleCaptchaRequired(
|
|
||||||
twoFactorRequest: TokenTwoFactorRequest,
|
|
||||||
credentials: PasswordLoginCredentials = null,
|
|
||||||
): Promise<AuthResult | Response> {
|
|
||||||
const badCaptcha = Response.badRequest(
|
|
||||||
"Your authentication request has been flagged and will require user interaction to proceed.\n" +
|
|
||||||
"Please use your API key to validate this request and ensure BW_CLIENTSECRET is correct, if set.\n" +
|
|
||||||
"(https://bitwarden.com/help/cli-auth-challenges)",
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const captchaClientSecret = await this.apiClientSecret(true);
|
|
||||||
if (Utils.isNullOrWhitespace(captchaClientSecret)) {
|
|
||||||
return badCaptcha;
|
|
||||||
}
|
|
||||||
|
|
||||||
let authResultResponse: AuthResult = null;
|
|
||||||
if (credentials != null) {
|
|
||||||
credentials.captchaToken = captchaClientSecret;
|
|
||||||
credentials.twoFactor = twoFactorRequest;
|
|
||||||
authResultResponse = await this.loginStrategyService.logIn(credentials);
|
|
||||||
} else {
|
|
||||||
authResultResponse = await this.loginStrategyService.logInTwoFactor(
|
|
||||||
twoFactorRequest,
|
|
||||||
captchaClientSecret,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return authResultResponse;
|
|
||||||
} catch (e) {
|
|
||||||
if (
|
|
||||||
e instanceof ErrorResponse ||
|
|
||||||
(e.constructor.name === ErrorResponse.name &&
|
|
||||||
(e as ErrorResponse).message.includes("Captcha is invalid"))
|
|
||||||
) {
|
|
||||||
return badCaptcha;
|
|
||||||
} else {
|
|
||||||
return Response.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async apiClientId(): Promise<string> {
|
private async apiClientId(): Promise<string> {
|
||||||
let clientId: string = null;
|
let clientId: string = null;
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ import {
|
|||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { LockComponent } from "@bitwarden/key-management-ui";
|
import { LockComponent } from "@bitwarden/key-management-ui";
|
||||||
|
|
||||||
import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.component";
|
|
||||||
import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard";
|
import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard";
|
||||||
import { SetPasswordComponent } from "../auth/set-password.component";
|
import { SetPasswordComponent } from "../auth/set-password.component";
|
||||||
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
|
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
|
||||||
@@ -111,7 +110,6 @@ const routes: Routes = [
|
|||||||
canActivate: [authGuard],
|
canActivate: [authGuard],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{ path: "accessibility-cookie", component: AccessibilityCookieComponent },
|
|
||||||
{ path: "set-password", component: SetPasswordComponent },
|
{ path: "set-password", component: SetPasswordComponent },
|
||||||
{
|
{
|
||||||
path: "send",
|
path: "send",
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-
|
|||||||
import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe";
|
import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe";
|
||||||
import { CalloutModule, DialogModule } from "@bitwarden/components";
|
import { CalloutModule, DialogModule } from "@bitwarden/components";
|
||||||
|
|
||||||
import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.component";
|
|
||||||
import { DeleteAccountComponent } from "../auth/delete-account.component";
|
import { DeleteAccountComponent } from "../auth/delete-account.component";
|
||||||
import { LoginModule } from "../auth/login/login.module";
|
import { LoginModule } from "../auth/login/login.module";
|
||||||
import { SetPasswordComponent } from "../auth/set-password.component";
|
import { SetPasswordComponent } from "../auth/set-password.component";
|
||||||
@@ -59,7 +58,6 @@ import { SharedModule } from "./shared/shared.module";
|
|||||||
VaultV2Component,
|
VaultV2Component,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AccessibilityCookieComponent,
|
|
||||||
AccountSwitcherComponent,
|
AccountSwitcherComponent,
|
||||||
AddEditComponent,
|
AddEditComponent,
|
||||||
AddEditCustomFieldsComponent,
|
AddEditCustomFieldsComponent,
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
<form id="accessibility-cookie-page" #form [formGroup]="accessibilityForm" (ngSubmit)="submit()">
|
|
||||||
<div class="content">
|
|
||||||
<h1>{{ "loadAccessibilityCookie" | i18n }}</h1>
|
|
||||||
<p>
|
|
||||||
{{ "registerAccessibilityUser" | i18n }}
|
|
||||||
<a (click)="registerhCaptcha()">hcaptcha.com</a>.
|
|
||||||
{{ "copyPasteLink" | i18n }}
|
|
||||||
</p>
|
|
||||||
<div class="box last">
|
|
||||||
<div class="box-content">
|
|
||||||
<div class="box-content-row" appBoxRow>
|
|
||||||
<label for="link">{{ "hCaptchaUrl" | i18n }}</label>
|
|
||||||
<input
|
|
||||||
id="link"
|
|
||||||
type="text"
|
|
||||||
name="Link"
|
|
||||||
aria-describedby="linkHelp"
|
|
||||||
formControlName="link"
|
|
||||||
placeholder="{{ 'ex' | i18n }} https://accounts.hcaptcha.com/verify_email"
|
|
||||||
appAutofocus
|
|
||||||
appInputVerbatim
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="linkHelp" class="box-footer">{{ "enterhCaptchaUrl" | i18n }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="buttons">
|
|
||||||
<button type="submit" class="btn primary block" [disabled]="!accessibilityForm.valid">
|
|
||||||
{{ "submit" | i18n }}
|
|
||||||
</button>
|
|
||||||
<button type="button" (click)="close()" class="btn block">{{ "done" | i18n }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
|
||||||
// @ts-strict-ignore
|
|
||||||
import { Component, NgZone } from "@angular/core";
|
|
||||||
import { UntypedFormControl, UntypedFormGroup, Validators } from "@angular/forms";
|
|
||||||
import { Router } from "@angular/router";
|
|
||||||
|
|
||||||
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";
|
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
|
||||||
import { ToastService } from "@bitwarden/components";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: "app-accessibility-cookie",
|
|
||||||
templateUrl: "accessibility-cookie.component.html",
|
|
||||||
})
|
|
||||||
export class AccessibilityCookieComponent {
|
|
||||||
listenForCookie = false;
|
|
||||||
hCaptchaWindow: Window;
|
|
||||||
|
|
||||||
accessibilityForm = new UntypedFormGroup({
|
|
||||||
link: new UntypedFormControl("", Validators.required),
|
|
||||||
});
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected router: Router,
|
|
||||||
protected platformUtilsService: PlatformUtilsService,
|
|
||||||
protected environmentService: EnvironmentService,
|
|
||||||
protected i18nService: I18nService,
|
|
||||||
protected ngZone: NgZone,
|
|
||||||
private toastService: ToastService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
registerhCaptcha() {
|
|
||||||
this.platformUtilsService.launchUri("https://www.hcaptcha.com/accessibility");
|
|
||||||
}
|
|
||||||
|
|
||||||
async close() {
|
|
||||||
const [cookie] = await ipc.auth.getHcaptchaAccessibilityCookie();
|
|
||||||
if (cookie) {
|
|
||||||
this.onCookieSavedSuccess();
|
|
||||||
} else {
|
|
||||||
this.onCookieSavedFailure();
|
|
||||||
}
|
|
||||||
await this.router.navigate(["/login"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
onCookieSavedSuccess() {
|
|
||||||
this.toastService.showToast({
|
|
||||||
variant: "success",
|
|
||||||
title: null,
|
|
||||||
message: this.i18nService.t("accessibilityCookieSaved"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onCookieSavedFailure() {
|
|
||||||
this.toastService.showToast({
|
|
||||||
variant: "error",
|
|
||||||
title: null,
|
|
||||||
message: this.i18nService.t("noAccessibilityCookieSaved"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async submit() {
|
|
||||||
if (Utils.getHostname(this.accessibilityForm.value.link) !== "accounts.hcaptcha.com") {
|
|
||||||
this.toastService.showToast({
|
|
||||||
variant: "error",
|
|
||||||
title: this.i18nService.t("errorOccurred"),
|
|
||||||
message: this.i18nService.t("invalidUrl"),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.listenForCookie = true;
|
|
||||||
window.open(this.accessibilityForm.value.link, "_blank", "noopener noreferrer");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getHcaptchaAccessibilityCookie: (): Promise<[string]> =>
|
|
||||||
ipcRenderer.invoke("getCookie", { url: "https://www.hcaptcha.com/", name: "hc_accessibility" }),
|
|
||||||
|
|
||||||
loginRequest: (alertTitle: string, alertBody: string, buttonText: string): Promise<void> =>
|
loginRequest: (alertTitle: string, alertBody: string, buttonText: string): Promise<void> =>
|
||||||
ipcRenderer.invoke("loginRequest", {
|
ipcRenderer.invoke("loginRequest", {
|
||||||
alertTitle,
|
alertTitle,
|
||||||
|
|||||||
@@ -1725,40 +1725,9 @@
|
|||||||
"filePasswordAndConfirmFilePasswordDoNotMatch": {
|
"filePasswordAndConfirmFilePasswordDoNotMatch": {
|
||||||
"message": "“File password” and “Confirm file password“ do not match."
|
"message": "“File password” and “Confirm file password“ do not match."
|
||||||
},
|
},
|
||||||
"hCaptchaUrl": {
|
|
||||||
"message": "hCaptcha Url",
|
|
||||||
"description": "hCaptcha is the name of a website, should not be translated"
|
|
||||||
},
|
|
||||||
"loadAccessibilityCookie": {
|
|
||||||
"message": "Load accessibility cookie"
|
|
||||||
},
|
|
||||||
"registerAccessibilityUser": {
|
|
||||||
"message": "Register as an accessibility user at",
|
|
||||||
"description": "ex. Register as an accessibility user at hcaptcha.com"
|
|
||||||
},
|
|
||||||
"copyPasteLink": {
|
|
||||||
"message": "Copy and paste the link sent to your email below"
|
|
||||||
},
|
|
||||||
"enterhCaptchaUrl": {
|
|
||||||
"message": "Enter URL to load accessibility cookie for hCaptcha",
|
|
||||||
"description": "hCaptcha is the name of a website, should not be translated"
|
|
||||||
},
|
|
||||||
"hCaptchaUrlRequired": {
|
|
||||||
"message": "hCaptcha Url is required",
|
|
||||||
"description": "hCaptcha is the name of a website, should not be translated"
|
|
||||||
},
|
|
||||||
"invalidUrl": {
|
|
||||||
"message": "Invalid Url"
|
|
||||||
},
|
|
||||||
"done": {
|
"done": {
|
||||||
"message": "Done"
|
"message": "Done"
|
||||||
},
|
},
|
||||||
"accessibilityCookieSaved": {
|
|
||||||
"message": "Accessibility cookie saved!"
|
|
||||||
},
|
|
||||||
"noAccessibilityCookieSaved": {
|
|
||||||
"message": "No accessibility cookie saved"
|
|
||||||
},
|
|
||||||
"warning": {
|
"warning": {
|
||||||
"message": "WARNING",
|
"message": "WARNING",
|
||||||
"description": "WARNING (should stay in capitalized letters if the language permits)"
|
"description": "WARNING (should stay in capitalized letters if the language permits)"
|
||||||
|
|||||||
@@ -252,12 +252,6 @@ p.lead {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#hcaptcha_iframe {
|
|
||||||
width: 100%;
|
|
||||||
border: none;
|
|
||||||
transition: height 0.25s linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
form,
|
form,
|
||||||
.form {
|
.form {
|
||||||
.form-group {
|
.form-group {
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#accessibility-cookie-page,
|
|
||||||
#register-page,
|
#register-page,
|
||||||
#hint-page,
|
#hint-page,
|
||||||
#update-temp-password-page,
|
#update-temp-password-page,
|
||||||
@@ -43,7 +42,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#accessibility-cookie-page,
|
|
||||||
#register-page,
|
#register-page,
|
||||||
#hint-page,
|
#hint-page,
|
||||||
#lock-page,
|
#lock-page,
|
||||||
|
|||||||
@@ -52,10 +52,6 @@ export class ElectronMainMessagingService implements MessageSender {
|
|||||||
return windowMain.win?.isVisible();
|
return windowMain.win?.isVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle("getCookie", async (event, options) => {
|
|
||||||
return await this.windowMain.session.cookies.get(options);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle("loginRequest", async (event, options) => {
|
ipcMain.handle("loginRequest", async (event, options) => {
|
||||||
const alert = new Notification({
|
const alert = new Notification({
|
||||||
title: options.alertTitle,
|
title: options.alertTitle,
|
||||||
|
|||||||
@@ -172,7 +172,6 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
let userKey: UserKey;
|
let userKey: UserKey;
|
||||||
let userKeyEncString: EncString;
|
let userKeyEncString: EncString;
|
||||||
let userKeyPair: [string, EncString];
|
let userKeyPair: [string, EncString];
|
||||||
let capchaBypassToken: string;
|
|
||||||
|
|
||||||
let orgInvite: OrganizationInvite;
|
let orgInvite: OrganizationInvite;
|
||||||
let orgSponsoredFreeFamilyPlanToken: string;
|
let orgSponsoredFreeFamilyPlanToken: string;
|
||||||
@@ -198,7 +197,6 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
userKeyEncString = new EncString("userKeyEncrypted");
|
userKeyEncString = new EncString("userKeyEncrypted");
|
||||||
|
|
||||||
userKeyPair = ["publicKey", new EncString("privateKey")];
|
userKeyPair = ["publicKey", new EncString("privateKey")];
|
||||||
capchaBypassToken = "capchaBypassToken";
|
|
||||||
|
|
||||||
orgInvite = new OrganizationInvite();
|
orgInvite = new OrganizationInvite();
|
||||||
orgInvite.organizationUserId = "organizationUserId";
|
orgInvite.organizationUserId = "organizationUserId";
|
||||||
@@ -219,19 +217,13 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("registers the user and returns a captcha bypass token when given valid email verification input", async () => {
|
it("registers the user when given valid email verification input", async () => {
|
||||||
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
||||||
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
||||||
accountApiService.registerFinish.mockResolvedValue(capchaBypassToken);
|
accountApiService.registerFinish.mockResolvedValue();
|
||||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||||
|
|
||||||
const result = await service.finishRegistration(
|
await service.finishRegistration(email, passwordInputResult, emailVerificationToken);
|
||||||
email,
|
|
||||||
passwordInputResult,
|
|
||||||
emailVerificationToken,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result).toEqual(capchaBypassToken);
|
|
||||||
|
|
||||||
expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey);
|
expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey);
|
||||||
expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey);
|
expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey);
|
||||||
@@ -261,15 +253,13 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("it registers the user and returns a captcha bypass token when given an org invite", async () => {
|
it("it registers the user when given an org invite", async () => {
|
||||||
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
||||||
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
||||||
accountApiService.registerFinish.mockResolvedValue(capchaBypassToken);
|
accountApiService.registerFinish.mockResolvedValue();
|
||||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
|
||||||
|
|
||||||
const result = await service.finishRegistration(email, passwordInputResult);
|
await service.finishRegistration(email, passwordInputResult);
|
||||||
|
|
||||||
expect(result).toEqual(capchaBypassToken);
|
|
||||||
|
|
||||||
expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey);
|
expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey);
|
||||||
expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey);
|
expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey);
|
||||||
@@ -299,21 +289,19 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("registers the user and returns a captcha bypass token when given an org sponsored free family plan token", async () => {
|
it("registers the user when given an org sponsored free family plan token", async () => {
|
||||||
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
||||||
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
||||||
accountApiService.registerFinish.mockResolvedValue(capchaBypassToken);
|
accountApiService.registerFinish.mockResolvedValue();
|
||||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||||
|
|
||||||
const result = await service.finishRegistration(
|
await service.finishRegistration(
|
||||||
email,
|
email,
|
||||||
passwordInputResult,
|
passwordInputResult,
|
||||||
undefined,
|
undefined,
|
||||||
orgSponsoredFreeFamilyPlanToken,
|
orgSponsoredFreeFamilyPlanToken,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result).toEqual(capchaBypassToken);
|
|
||||||
|
|
||||||
expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey);
|
expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey);
|
||||||
expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey);
|
expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey);
|
||||||
expect(accountApiService.registerFinish).toHaveBeenCalledWith(
|
expect(accountApiService.registerFinish).toHaveBeenCalledWith(
|
||||||
@@ -342,13 +330,13 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("registers the user and returns a captcha bypass token when given an emergency access invite token", async () => {
|
it("registers the user when given an emergency access invite token", async () => {
|
||||||
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
||||||
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
||||||
accountApiService.registerFinish.mockResolvedValue(capchaBypassToken);
|
accountApiService.registerFinish.mockResolvedValue();
|
||||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||||
|
|
||||||
const result = await service.finishRegistration(
|
await service.finishRegistration(
|
||||||
email,
|
email,
|
||||||
passwordInputResult,
|
passwordInputResult,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -357,8 +345,6 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
emergencyAccessId,
|
emergencyAccessId,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result).toEqual(capchaBypassToken);
|
|
||||||
|
|
||||||
expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey);
|
expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey);
|
||||||
expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey);
|
expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey);
|
||||||
expect(accountApiService.registerFinish).toHaveBeenCalledWith(
|
expect(accountApiService.registerFinish).toHaveBeenCalledWith(
|
||||||
@@ -387,13 +373,13 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("registers the user and returns a captcha bypass token when given a provider invite token", async () => {
|
it("registers the user when given a provider invite token", async () => {
|
||||||
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
||||||
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
||||||
accountApiService.registerFinish.mockResolvedValue(capchaBypassToken);
|
accountApiService.registerFinish.mockResolvedValue();
|
||||||
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
|
||||||
|
|
||||||
const result = await service.finishRegistration(
|
await service.finishRegistration(
|
||||||
email,
|
email,
|
||||||
passwordInputResult,
|
passwordInputResult,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -404,8 +390,6 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
providerUserId,
|
providerUserId,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result).toEqual(capchaBypassToken);
|
|
||||||
|
|
||||||
expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey);
|
expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey);
|
||||||
expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey);
|
expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey);
|
||||||
expect(accountApiService.registerFinish).toHaveBeenCalledWith(
|
expect(accountApiService.registerFinish).toHaveBeenCalledWith(
|
||||||
|
|||||||
@@ -80,12 +80,7 @@ export class RecoverTwoFactorComponent implements OnInit {
|
|||||||
remember: false,
|
remember: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const credentials = new PasswordLoginCredentials(
|
const credentials = new PasswordLoginCredentials(email, this.masterPassword, twoFactorRequest);
|
||||||
email,
|
|
||||||
this.masterPassword,
|
|
||||||
"",
|
|
||||||
twoFactorRequest,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authResult = await this.loginStrategyService.logIn(credentials);
|
const authResult = await this.loginStrategyService.logIn(credentials);
|
||||||
|
|||||||
@@ -366,14 +366,9 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const captchaToken = await this.finishRegistration(passwordInputResult);
|
await this.finishRegistration(passwordInputResult);
|
||||||
|
|
||||||
if (captchaToken == null) {
|
await this.logIn(passwordInputResult.newPassword);
|
||||||
this.submitting = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.logIn(passwordInputResult.newPassword, captchaToken);
|
|
||||||
|
|
||||||
this.submitting = false;
|
this.submitting = false;
|
||||||
|
|
||||||
@@ -389,14 +384,9 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Logs the user in based using the token received by the `finishRegistration` method */
|
/** Logs the user in */
|
||||||
private async logIn(masterPassword: string, captchaBypassToken: string): Promise<void> {
|
private async logIn(masterPassword: string): Promise<void> {
|
||||||
const credentials = new PasswordLoginCredentials(
|
const credentials = new PasswordLoginCredentials(this.email, masterPassword);
|
||||||
this.email,
|
|
||||||
masterPassword,
|
|
||||||
captchaBypassToken,
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.loginStrategyService.logIn(credentials);
|
await this.loginStrategyService.logIn(credentials);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html class="theme_light">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"
|
|
||||||
/>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
|
||||||
<meta name="HandheldFriendly" content="true" />
|
|
||||||
<title>Bitwarden Captcha Connector</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="layout_frontend">
|
|
||||||
<div class="row justify-content-md-center mt-5">
|
|
||||||
<div>
|
|
||||||
<img src="..//images/logo-dark@2x.png" class="logo mb-2" alt="Bitwarden" />
|
|
||||||
<p id="captchaRequired" class="lead text-center mx-4 mb-4">Captcha Required</p>
|
|
||||||
<div id="captcha"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
@import "../scss/styles.scss";
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"
|
|
||||||
/>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
|
||||||
<meta name="HandheldFriendly" content="true" />
|
|
||||||
<title>Bitwarden Captcha Connector</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="captcha"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
body {
|
|
||||||
min-width: 0px !important;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
background: transparent;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
|
||||||
// @ts-strict-ignore
|
|
||||||
import { b64Decode, getQsParam } from "./common";
|
|
||||||
|
|
||||||
declare let hcaptcha: any;
|
|
||||||
|
|
||||||
if (window.location.pathname.includes("mobile")) {
|
|
||||||
// FIXME: Remove when updating file. Eslint update
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
||||||
require("./captcha-mobile.scss");
|
|
||||||
} else {
|
|
||||||
// FIXME: Remove when updating file. Eslint update
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
||||||
require("./captcha.scss");
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
|
||||||
// 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
|
|
||||||
init();
|
|
||||||
});
|
|
||||||
|
|
||||||
(window as any).captchaSuccess = captchaSuccess;
|
|
||||||
(window as any).captchaError = captchaError;
|
|
||||||
|
|
||||||
let parentUrl: string = null;
|
|
||||||
let parentOrigin: string = null;
|
|
||||||
let mobileResponse: boolean = null;
|
|
||||||
let sentSuccess = false;
|
|
||||||
|
|
||||||
async function init() {
|
|
||||||
await start();
|
|
||||||
onMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function start() {
|
|
||||||
sentSuccess = false;
|
|
||||||
|
|
||||||
const data = getQsParam("data");
|
|
||||||
if (!data) {
|
|
||||||
error("No data.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
parentUrl = getQsParam("parent");
|
|
||||||
if (!parentUrl) {
|
|
||||||
error("No parent.");
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
parentUrl = decodeURIComponent(parentUrl);
|
|
||||||
parentOrigin = new URL(parentUrl).origin;
|
|
||||||
}
|
|
||||||
|
|
||||||
let decodedData: any;
|
|
||||||
try {
|
|
||||||
decodedData = JSON.parse(b64Decode(data, true));
|
|
||||||
// FIXME: Remove when updating file. Eslint update
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
} catch (e) {
|
|
||||||
error("Cannot parse data.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mobileResponse = decodedData.callbackUri != null || decodedData.mobile === true;
|
|
||||||
|
|
||||||
let src = "https://hcaptcha.com/1/api.js?render=explicit";
|
|
||||||
|
|
||||||
// Set language code
|
|
||||||
if (decodedData.locale) {
|
|
||||||
src += `&hl=${encodeURIComponent(decodedData.locale) ?? "en"}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set captchaRequired subtitle for mobile
|
|
||||||
const subtitleEl = document.getElementById("captchaRequired");
|
|
||||||
if (decodedData.captchaRequiredText && subtitleEl) {
|
|
||||||
subtitleEl.textContent = decodedData.captchaRequiredText;
|
|
||||||
}
|
|
||||||
|
|
||||||
const script = document.createElement("script");
|
|
||||||
script.src = src;
|
|
||||||
script.async = true;
|
|
||||||
script.defer = true;
|
|
||||||
script.addEventListener("load", () => {
|
|
||||||
hcaptcha.render("captcha", {
|
|
||||||
sitekey: encodeURIComponent(decodedData.siteKey),
|
|
||||||
callback: "captchaSuccess",
|
|
||||||
"error-callback": "captchaError",
|
|
||||||
});
|
|
||||||
// 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
|
|
||||||
watchHeight();
|
|
||||||
});
|
|
||||||
document.head.appendChild(script);
|
|
||||||
}
|
|
||||||
|
|
||||||
function captchaSuccess(response: string) {
|
|
||||||
if (mobileResponse) {
|
|
||||||
document.location.replace("bitwarden://captcha-callback?token=" + encodeURIComponent(response));
|
|
||||||
} else {
|
|
||||||
success(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function captchaError() {
|
|
||||||
error("An error occurred with the captcha. Try again.");
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMessage() {
|
|
||||||
window.addEventListener(
|
|
||||||
"message",
|
|
||||||
(event) => {
|
|
||||||
if (!event.origin || event.origin === "" || event.origin !== parentOrigin) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.data === "start") {
|
|
||||||
// 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
|
|
||||||
start();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function error(message: string) {
|
|
||||||
parent.postMessage("error|" + message, parentUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
function success(data: string) {
|
|
||||||
if (sentSuccess) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
parent.postMessage("success|" + data, parentUrl);
|
|
||||||
sentSuccess = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function info(message: string | object) {
|
|
||||||
parent.postMessage("info|" + JSON.stringify(message), parentUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function watchHeight() {
|
|
||||||
const imagesDiv = document.body.lastChild as HTMLElement;
|
|
||||||
// eslint-disable-next-line
|
|
||||||
while (true) {
|
|
||||||
info({
|
|
||||||
height:
|
|
||||||
imagesDiv.style.visibility === "hidden"
|
|
||||||
? document.documentElement.offsetHeight
|
|
||||||
: document.documentElement.scrollHeight,
|
|
||||||
width: document.documentElement.scrollWidth,
|
|
||||||
});
|
|
||||||
await sleep(100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sleep(ms: number) {
|
|
||||||
await new Promise((r) => setTimeout(r, ms));
|
|
||||||
}
|
|
||||||
@@ -24,12 +24,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#hcaptcha_iframe {
|
|
||||||
border: none;
|
|
||||||
transition: height 0.25s linear;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@each $mfaType in $mfaTypes {
|
@each $mfaType in $mfaTypes {
|
||||||
.mfaType#{$mfaType} {
|
.mfaType#{$mfaType} {
|
||||||
content: url("../images/two-factor/" + $mfaType + ".png");
|
content: url("../images/two-factor/" + $mfaType + ".png");
|
||||||
|
|||||||
@@ -129,16 +129,6 @@ const plugins = [
|
|||||||
filename: "redirect-connector.html",
|
filename: "redirect-connector.html",
|
||||||
chunks: ["connectors/redirect", "styles"],
|
chunks: ["connectors/redirect", "styles"],
|
||||||
}),
|
}),
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
template: "./src/connectors/captcha.html",
|
|
||||||
filename: "captcha-connector.html",
|
|
||||||
chunks: ["connectors/captcha"],
|
|
||||||
}),
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
template: "./src/connectors/captcha-mobile.html",
|
|
||||||
filename: "captcha-mobile-connector.html",
|
|
||||||
chunks: ["connectors/captcha"],
|
|
||||||
}),
|
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: "./src/connectors/duo-redirect.html",
|
template: "./src/connectors/duo-redirect.html",
|
||||||
filename: "duo-redirect-connector.html",
|
filename: "duo-redirect-connector.html",
|
||||||
@@ -344,7 +334,6 @@ const webpackConfig = {
|
|||||||
"connectors/webauthn": "./src/connectors/webauthn.ts",
|
"connectors/webauthn": "./src/connectors/webauthn.ts",
|
||||||
"connectors/webauthn-fallback": "./src/connectors/webauthn-fallback.ts",
|
"connectors/webauthn-fallback": "./src/connectors/webauthn-fallback.ts",
|
||||||
"connectors/sso": "./src/connectors/sso.ts",
|
"connectors/sso": "./src/connectors/sso.ts",
|
||||||
"connectors/captcha": "./src/connectors/captcha.ts",
|
|
||||||
"connectors/duo-redirect": "./src/connectors/duo-redirect.ts",
|
"connectors/duo-redirect": "./src/connectors/duo-redirect.ts",
|
||||||
"connectors/redirect": "./src/connectors/redirect.ts",
|
"connectors/redirect": "./src/connectors/redirect.ts",
|
||||||
styles: ["./src/scss/styles.scss", "./src/scss/tailwind.css"],
|
styles: ["./src/scss/styles.scss", "./src/scss/tailwind.css"],
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
|
||||||
// @ts-strict-ignore
|
|
||||||
import { Directive, Input } from "@angular/core";
|
|
||||||
import { firstValueFrom } from "rxjs";
|
|
||||||
|
|
||||||
import { CaptchaIFrame } from "@bitwarden/common/auth/captcha-iframe";
|
|
||||||
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";
|
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
|
||||||
import { ToastService } from "@bitwarden/components";
|
|
||||||
|
|
||||||
@Directive()
|
|
||||||
export abstract class CaptchaProtectedComponent {
|
|
||||||
@Input() captchaSiteKey: string = null;
|
|
||||||
captchaToken: string = null;
|
|
||||||
captcha: CaptchaIFrame;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected environmentService: EnvironmentService,
|
|
||||||
protected i18nService: I18nService,
|
|
||||||
protected platformUtilsService: PlatformUtilsService,
|
|
||||||
protected toastService: ToastService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async setupCaptcha() {
|
|
||||||
const env = await firstValueFrom(this.environmentService.environment$);
|
|
||||||
const webVaultUrl = env.getWebVaultUrl();
|
|
||||||
|
|
||||||
this.captcha = new CaptchaIFrame(
|
|
||||||
window,
|
|
||||||
webVaultUrl,
|
|
||||||
this.i18nService,
|
|
||||||
(token: string) => {
|
|
||||||
this.captchaToken = token;
|
|
||||||
},
|
|
||||||
(error: string) => {
|
|
||||||
this.toastService.showToast({
|
|
||||||
variant: "error",
|
|
||||||
title: this.i18nService.t("errorOccurred"),
|
|
||||||
message: error,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
(info: string) => {
|
|
||||||
this.toastService.showToast({
|
|
||||||
variant: "info",
|
|
||||||
title: this.i18nService.t("info"),
|
|
||||||
message: info,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
showCaptcha() {
|
|
||||||
return !Utils.isNullOrWhitespace(this.captchaSiteKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected handleCaptchaRequired(response: { captchaSiteKey: string }): boolean {
|
|
||||||
if (Utils.isNullOrWhitespace(response.captchaSiteKey)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.captchaSiteKey = response.captchaSiteKey;
|
|
||||||
this.captcha.init(response.captchaSiteKey);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Directive, ElementRef, Input, OnChanges } from "@angular/core";
|
import { Directive, ElementRef, Input, OnChanges } from "@angular/core";
|
||||||
|
|
||||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
|
|
||||||
@@ -39,11 +38,6 @@ export class ApiActionDirective implements OnChanges {
|
|||||||
},
|
},
|
||||||
(e: any) => {
|
(e: any) => {
|
||||||
this.el.nativeElement.loading = false;
|
this.el.nativeElement.loading = false;
|
||||||
|
|
||||||
if ((e as ErrorResponse).captchaRequired) {
|
|
||||||
this.logService.error("Captcha required error response: " + e.getSingleMessage());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.logService?.error(`Received API exception:`, e);
|
this.logService?.error(`Received API exception:`, e);
|
||||||
this.validationService.showError(e);
|
this.validationService.showError(e);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ describe("DefaultRegistrationFinishService", () => {
|
|||||||
let userKey: UserKey;
|
let userKey: UserKey;
|
||||||
let userKeyEncString: EncString;
|
let userKeyEncString: EncString;
|
||||||
let userKeyPair: [string, EncString];
|
let userKeyPair: [string, EncString];
|
||||||
let capchaBypassToken: string;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
email = "test@email.com";
|
email = "test@email.com";
|
||||||
@@ -71,7 +70,6 @@ describe("DefaultRegistrationFinishService", () => {
|
|||||||
userKeyEncString = new EncString("userKeyEncrypted");
|
userKeyEncString = new EncString("userKeyEncrypted");
|
||||||
|
|
||||||
userKeyPair = ["publicKey", new EncString("privateKey")];
|
userKeyPair = ["publicKey", new EncString("privateKey")];
|
||||||
capchaBypassToken = "capchaBypassToken";
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws an error if the user key cannot be created", async () => {
|
it("throws an error if the user key cannot be created", async () => {
|
||||||
@@ -82,18 +80,12 @@ describe("DefaultRegistrationFinishService", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("registers the user and returns a captcha bypass token when given valid email verification input", async () => {
|
it("registers the user when given valid email verification input", async () => {
|
||||||
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]);
|
||||||
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
keyService.makeKeyPair.mockResolvedValue(userKeyPair);
|
||||||
accountApiService.registerFinish.mockResolvedValue(capchaBypassToken);
|
accountApiService.registerFinish.mockResolvedValue();
|
||||||
|
|
||||||
const result = await service.finishRegistration(
|
await service.finishRegistration(email, passwordInputResult, emailVerificationToken);
|
||||||
email,
|
|
||||||
passwordInputResult,
|
|
||||||
emailVerificationToken,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result).toEqual(capchaBypassToken);
|
|
||||||
|
|
||||||
expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey);
|
expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey);
|
||||||
expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey);
|
expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi
|
|||||||
emergencyAccessId?: string,
|
emergencyAccessId?: string,
|
||||||
providerInviteToken?: string,
|
providerInviteToken?: string,
|
||||||
providerUserId?: string,
|
providerUserId?: string,
|
||||||
): Promise<string> {
|
): Promise<void> {
|
||||||
const [newUserKey, newEncUserKey] = await this.keyService.makeUserKey(
|
const [newUserKey, newEncUserKey] = await this.keyService.makeUserKey(
|
||||||
passwordInputResult.masterKey,
|
passwordInputResult.masterKey,
|
||||||
);
|
);
|
||||||
@@ -57,9 +57,7 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi
|
|||||||
providerUserId,
|
providerUserId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const capchaBypassToken = await this.accountApiService.registerFinish(registerRequest);
|
return await this.accountApiService.registerFinish(registerRequest);
|
||||||
|
|
||||||
return capchaBypassToken;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async buildRegisterRequest(
|
protected async buildRegisterRequest(
|
||||||
|
|||||||
@@ -152,9 +152,8 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) {
|
async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) {
|
||||||
this.submitting = true;
|
this.submitting = true;
|
||||||
let captchaBypassToken: string = null;
|
|
||||||
try {
|
try {
|
||||||
captchaBypassToken = await this.registrationFinishService.finishRegistration(
|
await this.registrationFinishService.finishRegistration(
|
||||||
this.email,
|
this.email,
|
||||||
passwordInputResult,
|
passwordInputResult,
|
||||||
this.emailVerificationToken,
|
this.emailVerificationToken,
|
||||||
@@ -179,12 +178,7 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
// login with the new account
|
// login with the new account
|
||||||
try {
|
try {
|
||||||
const credentials = new PasswordLoginCredentials(
|
const credentials = new PasswordLoginCredentials(this.email, passwordInputResult.newPassword);
|
||||||
this.email,
|
|
||||||
passwordInputResult.newPassword,
|
|
||||||
captchaBypassToken,
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
const authenticationResult = await this.loginStrategyService.logIn(credentials);
|
const authenticationResult = await this.loginStrategyService.logIn(credentials);
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export abstract class RegistrationFinishService {
|
|||||||
* @param emergencyAccessId The optional emergency access id which is required to validate the emergency access invite token.
|
* @param emergencyAccessId The optional emergency access id which is required to validate the emergency access invite token.
|
||||||
* @param providerInviteToken The optional provider invite token.
|
* @param providerInviteToken The optional provider invite token.
|
||||||
* @param providerUserId The optional provider user id which is required to validate the provider invite token.
|
* @param providerUserId The optional provider user id which is required to validate the provider invite token.
|
||||||
* @returns a promise which resolves to the captcha bypass token string upon a successful account creation.
|
* @returns a promise which resolves upon a successful account creation.
|
||||||
*/
|
*/
|
||||||
abstract finishRegistration(
|
abstract finishRegistration(
|
||||||
email: string,
|
email: string,
|
||||||
@@ -38,5 +38,5 @@ export abstract class RegistrationFinishService {
|
|||||||
emergencyAccessId?: string,
|
emergencyAccessId?: string,
|
||||||
providerInviteToken?: string,
|
providerInviteToken?: string,
|
||||||
providerUserId?: string,
|
providerUserId?: string,
|
||||||
): Promise<string>;
|
): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,7 +260,6 @@ describe("TwoFactorAuthComponent", () => {
|
|||||||
// Assert
|
// Assert
|
||||||
expect(mockLoginStrategyService.logInTwoFactor).toHaveBeenCalledWith(
|
expect(mockLoginStrategyService.logInTwoFactor).toHaveBeenCalledWith(
|
||||||
new TokenTwoFactorRequest(component.selectedProviderType, token, remember),
|
new TokenTwoFactorRequest(component.selectedProviderType, token, remember),
|
||||||
"",
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -335,7 +335,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
|
|||||||
try {
|
try {
|
||||||
this.formPromise = this.loginStrategyService.logInTwoFactor(
|
this.formPromise = this.loginStrategyService.logInTwoFactor(
|
||||||
new TokenTwoFactorRequest(this.selectedProviderType, tokenValue, rememberValue),
|
new TokenTwoFactorRequest(this.selectedProviderType, tokenValue, rememberValue),
|
||||||
"", // TODO: PM-15162 - deprecate captchaResponse
|
|
||||||
);
|
);
|
||||||
const authResult: AuthResult = await this.formPromise;
|
const authResult: AuthResult = await this.formPromise;
|
||||||
this.logService.info("Successfully submitted two factor token");
|
this.logService.info("Successfully submitted two factor token");
|
||||||
|
|||||||
@@ -59,16 +59,11 @@ export abstract class LoginStrategyServiceAbstraction {
|
|||||||
| WebAuthnLoginCredentials,
|
| WebAuthnLoginCredentials,
|
||||||
) => Promise<AuthResult>;
|
) => Promise<AuthResult>;
|
||||||
/**
|
/**
|
||||||
* Sends a token request to the server with the provided two factor token
|
* Sends a token request to the server with the provided two factor token.
|
||||||
* and captcha response. This uses data stored from {@link LoginStrategyServiceAbstraction.logIn},
|
* This uses data stored from {@link LoginStrategyServiceAbstraction.logIn}, so that must be called first.
|
||||||
* so that must be called first.
|
|
||||||
* Returns an error if no session data is found.
|
* Returns an error if no session data is found.
|
||||||
*/
|
*/
|
||||||
logInTwoFactor: (
|
logInTwoFactor: (twoFactor: TokenTwoFactorRequest) => Promise<AuthResult>;
|
||||||
twoFactor: TokenTwoFactorRequest,
|
|
||||||
// TODO: PM-15162 - deprecate captchaResponse
|
|
||||||
captchaResponse: string,
|
|
||||||
) => Promise<AuthResult>;
|
|
||||||
/**
|
/**
|
||||||
* Creates a master key from the provided master password and email.
|
* Creates a master key from the provided master password and email.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import { LoginStrategy, LoginStrategyData } from "./login.strategy";
|
|||||||
|
|
||||||
export class AuthRequestLoginStrategyData implements LoginStrategyData {
|
export class AuthRequestLoginStrategyData implements LoginStrategyData {
|
||||||
tokenRequest: PasswordTokenRequest;
|
tokenRequest: PasswordTokenRequest;
|
||||||
captchaBypassToken: string;
|
|
||||||
authRequestCredentials: AuthRequestLoginCredentials;
|
authRequestCredentials: AuthRequestLoginCredentials;
|
||||||
|
|
||||||
static fromJSON(obj: Jsonify<AuthRequestLoginStrategyData>): AuthRequestLoginStrategyData {
|
static fromJSON(obj: Jsonify<AuthRequestLoginStrategyData>): AuthRequestLoginStrategyData {
|
||||||
@@ -54,7 +53,6 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
|
|||||||
data.tokenRequest = new PasswordTokenRequest(
|
data.tokenRequest = new PasswordTokenRequest(
|
||||||
credentials.email,
|
credentials.email,
|
||||||
credentials.accessCode,
|
credentials.accessCode,
|
||||||
null,
|
|
||||||
await this.buildTwoFactor(credentials.twoFactor, credentials.email),
|
await this.buildTwoFactor(credentials.twoFactor, credentials.email),
|
||||||
await this.buildDeviceRequest(),
|
await this.buildDeviceRequest(),
|
||||||
);
|
);
|
||||||
@@ -66,12 +64,8 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
|
|||||||
return authResult;
|
return authResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async logInTwoFactor(
|
override async logInTwoFactor(twoFactor: TokenTwoFactorRequest): Promise<AuthResult> {
|
||||||
twoFactor: TokenTwoFactorRequest,
|
|
||||||
captchaResponse: string,
|
|
||||||
): Promise<AuthResult> {
|
|
||||||
const data = this.cache.value;
|
const data = this.cache.value;
|
||||||
data.tokenRequest.captchaResponse = captchaResponse ?? data.captchaBypassToken;
|
|
||||||
this.cache.next(data);
|
this.cache.next(data);
|
||||||
|
|
||||||
return super.logInTwoFactor(twoFactor);
|
return super.logInTwoFactor(twoFactor);
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
|||||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||||
import { PasswordTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/password-token.request";
|
import { PasswordTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/password-token.request";
|
||||||
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
|
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
|
||||||
import { IdentityCaptchaResponse } from "@bitwarden/common/auth/models/response/identity-captcha.response";
|
|
||||||
import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response";
|
import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response";
|
||||||
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
|
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
|
||||||
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
|
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
|
||||||
@@ -59,7 +58,6 @@ const accessToken = "ACCESS_TOKEN";
|
|||||||
const refreshToken = "REFRESH_TOKEN";
|
const refreshToken = "REFRESH_TOKEN";
|
||||||
const userKey = "USER_KEY";
|
const userKey = "USER_KEY";
|
||||||
const privateKey = "PRIVATE_KEY";
|
const privateKey = "PRIVATE_KEY";
|
||||||
const captchaSiteKey = "CAPTCHA_SITE_KEY";
|
|
||||||
const kdf = 0;
|
const kdf = 0;
|
||||||
const kdfIterations = 10000;
|
const kdfIterations = 10000;
|
||||||
const userId = Utils.newGuid() as UserId;
|
const userId = Utils.newGuid() as UserId;
|
||||||
@@ -298,7 +296,6 @@ describe("LoginStrategy", () => {
|
|||||||
expected.userId = userId;
|
expected.userId = userId;
|
||||||
expected.resetMasterPassword = true;
|
expected.resetMasterPassword = true;
|
||||||
expected.twoFactorProviders = null;
|
expected.twoFactorProviders = null;
|
||||||
expected.captchaSiteKey = "";
|
|
||||||
expect(result).toEqual(expected);
|
expect(result).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -314,7 +311,6 @@ describe("LoginStrategy", () => {
|
|||||||
expected.userId = userId;
|
expected.userId = userId;
|
||||||
expected.resetMasterPassword = false;
|
expected.resetMasterPassword = false;
|
||||||
expected.twoFactorProviders = null;
|
expected.twoFactorProviders = null;
|
||||||
expected.captchaSiteKey = "";
|
|
||||||
expect(result).toEqual(expected);
|
expect(result).toEqual(expected);
|
||||||
|
|
||||||
expect(masterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith(
|
expect(masterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith(
|
||||||
@@ -323,28 +319,6 @@ describe("LoginStrategy", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects login if CAPTCHA is required", async () => {
|
|
||||||
// Sample CAPTCHA response
|
|
||||||
const tokenResponse = new IdentityCaptchaResponse({
|
|
||||||
error: "invalid_grant",
|
|
||||||
error_description: "Captcha required.",
|
|
||||||
HCaptcha_SiteKey: captchaSiteKey,
|
|
||||||
});
|
|
||||||
|
|
||||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
|
||||||
masterPasswordService.masterKeySubject.next(masterKey);
|
|
||||||
masterPasswordService.mock.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
|
|
||||||
|
|
||||||
const result = await passwordLoginStrategy.logIn(credentials);
|
|
||||||
|
|
||||||
expect(stateService.addAccount).not.toHaveBeenCalled();
|
|
||||||
expect(messagingService.send).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
const expected = new AuthResult();
|
|
||||||
expected.captchaSiteKey = captchaSiteKey;
|
|
||||||
expect(result).toEqual(expected);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("makes a new public and private key for an old account", async () => {
|
it("makes a new public and private key for an old account", async () => {
|
||||||
const tokenResponse = identityTokenResponseFactory();
|
const tokenResponse = identityTokenResponseFactory();
|
||||||
tokenResponse.privateKey = null;
|
tokenResponse.privateKey = null;
|
||||||
@@ -492,7 +466,6 @@ describe("LoginStrategy", () => {
|
|||||||
cache.tokenRequest = new PasswordTokenRequest(
|
cache.tokenRequest = new PasswordTokenRequest(
|
||||||
email,
|
email,
|
||||||
masterPasswordHash,
|
masterPasswordHash,
|
||||||
"",
|
|
||||||
new TokenTwoFactorRequest(),
|
new TokenTwoFactorRequest(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -524,7 +497,6 @@ describe("LoginStrategy", () => {
|
|||||||
|
|
||||||
await passwordLoginStrategy.logInTwoFactor(
|
await passwordLoginStrategy.logInTwoFactor(
|
||||||
new TokenTwoFactorRequest(twoFactorProviderType, twoFactorToken, twoFactorRemember),
|
new TokenTwoFactorRequest(twoFactorProviderType, twoFactorToken, twoFactorRemember),
|
||||||
"",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(apiService.postIdentityToken).toHaveBeenCalledWith(
|
expect(apiService.postIdentityToken).toHaveBeenCalledWith(
|
||||||
@@ -541,13 +513,11 @@ describe("LoginStrategy", () => {
|
|||||||
|
|
||||||
describe("Device verification", () => {
|
describe("Device verification", () => {
|
||||||
it("processes device verification response", async () => {
|
it("processes device verification response", async () => {
|
||||||
const captchaToken = "test-captcha-token";
|
|
||||||
const deviceVerificationResponse = new IdentityDeviceVerificationResponse({
|
const deviceVerificationResponse = new IdentityDeviceVerificationResponse({
|
||||||
error: "invalid_grant",
|
error: "invalid_grant",
|
||||||
error_description: "Device verification required.",
|
error_description: "Device verification required.",
|
||||||
email: "test@bitwarden.com",
|
email: "test@bitwarden.com",
|
||||||
deviceVerificationRequest: true,
|
deviceVerificationRequest: true,
|
||||||
captchaToken: captchaToken,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
apiService.postIdentityToken.mockResolvedValue(deviceVerificationResponse);
|
apiService.postIdentityToken.mockResolvedValue(deviceVerificationResponse);
|
||||||
@@ -556,7 +526,6 @@ describe("LoginStrategy", () => {
|
|||||||
cache.tokenRequest = new PasswordTokenRequest(
|
cache.tokenRequest = new PasswordTokenRequest(
|
||||||
email,
|
email,
|
||||||
masterPasswordHash,
|
masterPasswordHash,
|
||||||
"",
|
|
||||||
new TokenTwoFactorRequest(),
|
new TokenTwoFactorRequest(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { SsoTokenRequest } from "@bitwarden/common/auth/models/request/identity-
|
|||||||
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
|
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
|
||||||
import { UserApiTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/user-api-token.request";
|
import { UserApiTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/user-api-token.request";
|
||||||
import { WebAuthnLoginTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/webauthn-login-token.request";
|
import { WebAuthnLoginTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/webauthn-login-token.request";
|
||||||
import { IdentityCaptchaResponse } from "@bitwarden/common/auth/models/response/identity-captcha.response";
|
|
||||||
import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response";
|
import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response";
|
||||||
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
|
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
|
||||||
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
|
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
|
||||||
@@ -56,7 +55,6 @@ import { CacheData } from "../services/login-strategies/login-strategy.state";
|
|||||||
type IdentityResponse =
|
type IdentityResponse =
|
||||||
| IdentityTokenResponse
|
| IdentityTokenResponse
|
||||||
| IdentityTwoFactorResponse
|
| IdentityTwoFactorResponse
|
||||||
| IdentityCaptchaResponse
|
|
||||||
| IdentityDeviceVerificationResponse;
|
| IdentityDeviceVerificationResponse;
|
||||||
|
|
||||||
export abstract class LoginStrategyData {
|
export abstract class LoginStrategyData {
|
||||||
@@ -66,7 +64,6 @@ export abstract class LoginStrategyData {
|
|||||||
| SsoTokenRequest
|
| SsoTokenRequest
|
||||||
| WebAuthnLoginTokenRequest
|
| WebAuthnLoginTokenRequest
|
||||||
| undefined;
|
| undefined;
|
||||||
captchaBypassToken?: string;
|
|
||||||
|
|
||||||
/** User's entered email obtained pre-login. */
|
/** User's entered email obtained pre-login. */
|
||||||
abstract userEnteredEmail?: string;
|
abstract userEnteredEmail?: string;
|
||||||
@@ -108,10 +105,7 @@ export abstract class LoginStrategy {
|
|||||||
| WebAuthnLoginCredentials,
|
| WebAuthnLoginCredentials,
|
||||||
): Promise<AuthResult>;
|
): Promise<AuthResult>;
|
||||||
|
|
||||||
async logInTwoFactor(
|
async logInTwoFactor(twoFactor: TokenTwoFactorRequest): Promise<AuthResult> {
|
||||||
twoFactor: TokenTwoFactorRequest,
|
|
||||||
captchaResponse: string | null = null,
|
|
||||||
): Promise<AuthResult> {
|
|
||||||
const data = this.cache.value;
|
const data = this.cache.value;
|
||||||
if (!data.tokenRequest) {
|
if (!data.tokenRequest) {
|
||||||
throw new Error("Token request is undefined");
|
throw new Error("Token request is undefined");
|
||||||
@@ -133,8 +127,6 @@ export abstract class LoginStrategy {
|
|||||||
|
|
||||||
if (response instanceof IdentityTwoFactorResponse) {
|
if (response instanceof IdentityTwoFactorResponse) {
|
||||||
return [await this.processTwoFactorResponse(response), response];
|
return [await this.processTwoFactorResponse(response), response];
|
||||||
} else if (response instanceof IdentityCaptchaResponse) {
|
|
||||||
return [await this.processCaptchaResponse(response), response];
|
|
||||||
} else if (response instanceof IdentityTokenResponse) {
|
} else if (response instanceof IdentityTokenResponse) {
|
||||||
return [await this.processTokenResponse(response), response];
|
return [await this.processTokenResponse(response), response];
|
||||||
} else if (response instanceof IdentityDeviceVerificationResponse) {
|
} else if (response instanceof IdentityDeviceVerificationResponse) {
|
||||||
@@ -362,7 +354,6 @@ export abstract class LoginStrategy {
|
|||||||
result.twoFactorProviders = response.twoFactorProviders2;
|
result.twoFactorProviders = response.twoFactorProviders2;
|
||||||
|
|
||||||
await this.twoFactorService.setProviders(response);
|
await this.twoFactorService.setProviders(response);
|
||||||
this.cache.next({ ...this.cache.value, captchaBypassToken: response.captchaToken ?? null });
|
|
||||||
result.ssoEmail2FaSessionToken = response.ssoEmail2faSessionToken;
|
result.ssoEmail2FaSessionToken = response.ssoEmail2faSessionToken;
|
||||||
|
|
||||||
result.email = response.email ?? "";
|
result.email = response.email ?? "";
|
||||||
@@ -379,12 +370,6 @@ export abstract class LoginStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async processCaptchaResponse(response: IdentityCaptchaResponse): Promise<AuthResult> {
|
|
||||||
const result = new AuthResult();
|
|
||||||
result.captchaSiteKey = response.siteKey;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies that the active account is set after initialization.
|
* Verifies that the active account is set after initialization.
|
||||||
* Note: In browser there is a slight delay between when active account emits in background,
|
* Note: In browser there is a slight delay between when active account emits in background,
|
||||||
@@ -407,7 +392,7 @@ export abstract class LoginStrategy {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the response from the server when a device verification is required.
|
* Handles the response from the server when a device verification is required.
|
||||||
* It sets the requiresDeviceVerification flag to true and caches the captcha token if it came back.
|
* It sets the requiresDeviceVerification flag to true.
|
||||||
*
|
*
|
||||||
* @param {IdentityDeviceVerificationResponse} response - The response from the server indicating that device verification is required.
|
* @param {IdentityDeviceVerificationResponse} response - The response from the server indicating that device verification is required.
|
||||||
* @returns {Promise<AuthResult>} - A promise that resolves to an AuthResult object
|
* @returns {Promise<AuthResult>} - A promise that resolves to an AuthResult object
|
||||||
@@ -417,9 +402,6 @@ export abstract class LoginStrategy {
|
|||||||
): Promise<AuthResult> {
|
): Promise<AuthResult> {
|
||||||
const result = new AuthResult();
|
const result = new AuthResult();
|
||||||
result.requiresDeviceVerification = true;
|
result.requiresDeviceVerification = true;
|
||||||
|
|
||||||
// Extend cached data with captcha bypass token if it came back.
|
|
||||||
this.cache.next({ ...this.cache.value, captchaBypassToken: response.captchaToken ?? null });
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,7 +184,6 @@ describe("PasswordLoginStrategy", () => {
|
|||||||
provider: null,
|
provider: null,
|
||||||
token: null,
|
token: null,
|
||||||
}),
|
}),
|
||||||
captchaResponse: undefined,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -260,14 +259,11 @@ describe("PasswordLoginStrategy", () => {
|
|||||||
apiService.postIdentityToken.mockResolvedValueOnce(
|
apiService.postIdentityToken.mockResolvedValueOnce(
|
||||||
identityTokenResponseFactory(masterPasswordPolicy),
|
identityTokenResponseFactory(masterPasswordPolicy),
|
||||||
);
|
);
|
||||||
await passwordLoginStrategy.logInTwoFactor(
|
await passwordLoginStrategy.logInTwoFactor({
|
||||||
{
|
provider: TwoFactorProviderType.Authenticator,
|
||||||
provider: TwoFactorProviderType.Authenticator,
|
token: "123456",
|
||||||
token: "123456",
|
remember: false,
|
||||||
remember: false,
|
});
|
||||||
},
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Second login attempt should save the force password reset options
|
// Second login attempt should save the force password reset options
|
||||||
expect(masterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith(
|
expect(masterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith(
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
|||||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||||
import { PasswordTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/password-token.request";
|
import { PasswordTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/password-token.request";
|
||||||
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
|
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
|
||||||
import { IdentityCaptchaResponse } from "@bitwarden/common/auth/models/response/identity-captcha.response";
|
|
||||||
import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response";
|
import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response";
|
||||||
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
|
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
|
||||||
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
|
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
|
||||||
@@ -30,8 +29,6 @@ export class PasswordLoginStrategyData implements LoginStrategyData {
|
|||||||
|
|
||||||
/** User's entered email obtained pre-login. Always present in MP login. */
|
/** User's entered email obtained pre-login. Always present in MP login. */
|
||||||
userEnteredEmail: string;
|
userEnteredEmail: string;
|
||||||
/** If 2fa is required, token is returned to bypass captcha */
|
|
||||||
captchaBypassToken?: string;
|
|
||||||
/** The local version of the user's master key hash */
|
/** The local version of the user's master key hash */
|
||||||
localMasterKeyHash: string;
|
localMasterKeyHash: string;
|
||||||
/** The user's master key */
|
/** The user's master key */
|
||||||
@@ -79,7 +76,7 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async logIn(credentials: PasswordLoginCredentials) {
|
override async logIn(credentials: PasswordLoginCredentials) {
|
||||||
const { email, masterPassword, captchaToken, twoFactor } = credentials;
|
const { email, masterPassword, twoFactor } = credentials;
|
||||||
|
|
||||||
const data = new PasswordLoginStrategyData();
|
const data = new PasswordLoginStrategyData();
|
||||||
data.masterKey = await this.loginStrategyService.makePreloginKey(masterPassword, email);
|
data.masterKey = await this.loginStrategyService.makePreloginKey(masterPassword, email);
|
||||||
@@ -96,7 +93,6 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
|||||||
data.tokenRequest = new PasswordTokenRequest(
|
data.tokenRequest = new PasswordTokenRequest(
|
||||||
email,
|
email,
|
||||||
serverMasterKeyHash,
|
serverMasterKeyHash,
|
||||||
captchaToken,
|
|
||||||
await this.buildTwoFactor(twoFactor, email),
|
await this.buildTwoFactor(twoFactor, email),
|
||||||
await this.buildDeviceRequest(),
|
await this.buildDeviceRequest(),
|
||||||
);
|
);
|
||||||
@@ -105,23 +101,12 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
|||||||
|
|
||||||
const [authResult, identityResponse] = await this.startLogIn();
|
const [authResult, identityResponse] = await this.startLogIn();
|
||||||
|
|
||||||
if (identityResponse instanceof IdentityCaptchaResponse) {
|
|
||||||
return authResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.evaluateMasterPasswordIfRequired(identityResponse, credentials, authResult);
|
await this.evaluateMasterPasswordIfRequired(identityResponse, credentials, authResult);
|
||||||
|
|
||||||
return authResult;
|
return authResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async logInTwoFactor(
|
override async logInTwoFactor(twoFactor: TokenTwoFactorRequest): Promise<AuthResult> {
|
||||||
twoFactor: TokenTwoFactorRequest,
|
|
||||||
captchaResponse: string,
|
|
||||||
): Promise<AuthResult> {
|
|
||||||
const data = this.cache.value;
|
|
||||||
data.tokenRequest.captchaResponse = captchaResponse ?? data.captchaBypassToken;
|
|
||||||
this.cache.next(data);
|
|
||||||
|
|
||||||
const result = await super.logInTwoFactor(twoFactor);
|
const result = await super.logInTwoFactor(twoFactor);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import { CacheData } from "../services/login-strategies/login-strategy.state";
|
|||||||
import { LoginStrategyData, LoginStrategy } from "./login.strategy";
|
import { LoginStrategyData, LoginStrategy } from "./login.strategy";
|
||||||
|
|
||||||
export class SsoLoginStrategyData implements LoginStrategyData {
|
export class SsoLoginStrategyData implements LoginStrategyData {
|
||||||
captchaBypassToken: string;
|
|
||||||
tokenRequest: SsoTokenRequest;
|
tokenRequest: SsoTokenRequest;
|
||||||
/**
|
/**
|
||||||
* User's entered email obtained pre-login. Present in most SSO flows, but not CLI + SSO Flow.
|
* User's entered email obtained pre-login. Present in most SSO flows, but not CLI + SSO Flow.
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import { LoginStrategy, LoginStrategyData } from "./login.strategy";
|
|||||||
|
|
||||||
export class UserApiLoginStrategyData implements LoginStrategyData {
|
export class UserApiLoginStrategyData implements LoginStrategyData {
|
||||||
tokenRequest: UserApiTokenRequest;
|
tokenRequest: UserApiTokenRequest;
|
||||||
captchaBypassToken: string;
|
|
||||||
|
|
||||||
static fromJSON(obj: Jsonify<UserApiLoginStrategyData>): UserApiLoginStrategyData {
|
static fromJSON(obj: Jsonify<UserApiLoginStrategyData>): UserApiLoginStrategyData {
|
||||||
return Object.assign(new UserApiLoginStrategyData(), obj, {
|
return Object.assign(new UserApiLoginStrategyData(), obj, {
|
||||||
|
|||||||
@@ -208,11 +208,9 @@ describe("WebAuthnLoginStrategy", () => {
|
|||||||
|
|
||||||
expect(authResult).toBeInstanceOf(AuthResult);
|
expect(authResult).toBeInstanceOf(AuthResult);
|
||||||
expect(authResult).toMatchObject({
|
expect(authResult).toMatchObject({
|
||||||
captchaSiteKey: "",
|
|
||||||
resetMasterPassword: false,
|
resetMasterPassword: false,
|
||||||
twoFactorProviders: null,
|
twoFactorProviders: null,
|
||||||
requiresTwoFactor: false,
|
requiresTwoFactor: false,
|
||||||
requiresCaptcha: false,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import { LoginStrategy, LoginStrategyData } from "./login.strategy";
|
|||||||
|
|
||||||
export class WebAuthnLoginStrategyData implements LoginStrategyData {
|
export class WebAuthnLoginStrategyData implements LoginStrategyData {
|
||||||
tokenRequest: WebAuthnLoginTokenRequest;
|
tokenRequest: WebAuthnLoginTokenRequest;
|
||||||
captchaBypassToken?: string;
|
|
||||||
credentials: WebAuthnLoginCredentials;
|
credentials: WebAuthnLoginCredentials;
|
||||||
|
|
||||||
static fromJSON(obj: Jsonify<WebAuthnLoginStrategyData>): WebAuthnLoginStrategyData {
|
static fromJSON(obj: Jsonify<WebAuthnLoginStrategyData>): WebAuthnLoginStrategyData {
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ export class PasswordLoginCredentials {
|
|||||||
constructor(
|
constructor(
|
||||||
public email: string,
|
public email: string,
|
||||||
public masterPassword: string,
|
public masterPassword: string,
|
||||||
// TODO: PM-15162 - captcha is deprecated as part of UI refresh work
|
|
||||||
public captchaToken?: string,
|
|
||||||
public twoFactor?: TokenTwoFactorRequest,
|
public twoFactor?: TokenTwoFactorRequest,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ describe("LoginStrategyService", () => {
|
|||||||
premium: false,
|
premium: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await sut.logInTwoFactor(twoFactorToken, "CAPTCHA");
|
const result = await sut.logInTwoFactor(twoFactorToken);
|
||||||
|
|
||||||
expect(result).toBeInstanceOf(AuthResult);
|
expect(result).toBeInstanceOf(AuthResult);
|
||||||
});
|
});
|
||||||
@@ -285,7 +285,7 @@ describe("LoginStrategyService", () => {
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(sut.logInTwoFactor(twoFactorToken, "CAPTCHA")).rejects.toThrow();
|
await expect(sut.logInTwoFactor(twoFactorToken)).rejects.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throw error on too low kdf config", async () => {
|
it("throw error on too low kdf config", async () => {
|
||||||
|
|||||||
@@ -242,10 +242,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async logInTwoFactor(
|
async logInTwoFactor(twoFactor: TokenTwoFactorRequest): Promise<AuthResult> {
|
||||||
twoFactor: TokenTwoFactorRequest,
|
|
||||||
captchaResponse: string,
|
|
||||||
): Promise<AuthResult> {
|
|
||||||
if (!(await this.isSessionValid())) {
|
if (!(await this.isSessionValid())) {
|
||||||
throw new Error(this.i18nService.t("sessionTimeout"));
|
throw new Error(this.i18nService.t("sessionTimeout"));
|
||||||
}
|
}
|
||||||
@@ -256,10 +253,10 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await strategy.logInTwoFactor(twoFactor, captchaResponse);
|
const result = await strategy.logInTwoFactor(twoFactor);
|
||||||
|
|
||||||
// Only clear cache if 2FA token has been accepted, otherwise we need to be able to try again
|
// Only clear cache if 2FA token has been accepted, otherwise we need to be able to try again
|
||||||
if (result != null && !result.requiresTwoFactor && !result.requiresCaptcha) {
|
if (result != null && !result.requiresTwoFactor) {
|
||||||
await this.clearCache();
|
await this.clearCache();
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ describe("LOGIN_STRATEGY_CACHE_KEY", () => {
|
|||||||
actual.password.tokenRequest = new PasswordTokenRequest(
|
actual.password.tokenRequest = new PasswordTokenRequest(
|
||||||
"EMAIL",
|
"EMAIL",
|
||||||
"LOCAL_PASSWORD_HASH",
|
"LOCAL_PASSWORD_HASH",
|
||||||
"CAPTCHA_TOKEN",
|
|
||||||
twoFactorRequest,
|
twoFactorRequest,
|
||||||
deviceRequest,
|
deviceRequest,
|
||||||
);
|
);
|
||||||
@@ -116,7 +115,7 @@ describe("LOGIN_STRATEGY_CACHE_KEY", () => {
|
|||||||
deviceResponse,
|
deviceResponse,
|
||||||
deviceRequest,
|
deviceRequest,
|
||||||
);
|
);
|
||||||
actual.webAuthn.captchaBypassToken = "CAPTCHA_BYPASS_TOKEN";
|
|
||||||
actual.webAuthn.tokenRequest.setTwoFactor(
|
actual.webAuthn.tokenRequest.setTwoFactor(
|
||||||
new TokenTwoFactorRequest(TwoFactorProviderType.Email, "TOKEN", false),
|
new TokenTwoFactorRequest(TwoFactorProviderType.Email, "TOKEN", false),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -61,13 +61,11 @@ import { UpdateTwoFactorYubikeyOtpRequest } from "../auth/models/request/update-
|
|||||||
import { ApiKeyResponse } from "../auth/models/response/api-key.response";
|
import { ApiKeyResponse } from "../auth/models/response/api-key.response";
|
||||||
import { AuthRequestResponse } from "../auth/models/response/auth-request.response";
|
import { AuthRequestResponse } from "../auth/models/response/auth-request.response";
|
||||||
import { DeviceVerificationResponse } from "../auth/models/response/device-verification.response";
|
import { DeviceVerificationResponse } from "../auth/models/response/device-verification.response";
|
||||||
import { IdentityCaptchaResponse } from "../auth/models/response/identity-captcha.response";
|
|
||||||
import { IdentityDeviceVerificationResponse } from "../auth/models/response/identity-device-verification.response";
|
import { IdentityDeviceVerificationResponse } from "../auth/models/response/identity-device-verification.response";
|
||||||
import { IdentityTokenResponse } from "../auth/models/response/identity-token.response";
|
import { IdentityTokenResponse } from "../auth/models/response/identity-token.response";
|
||||||
import { IdentityTwoFactorResponse } from "../auth/models/response/identity-two-factor.response";
|
import { IdentityTwoFactorResponse } from "../auth/models/response/identity-two-factor.response";
|
||||||
import { KeyConnectorUserKeyResponse } from "../auth/models/response/key-connector-user-key.response";
|
import { KeyConnectorUserKeyResponse } from "../auth/models/response/key-connector-user-key.response";
|
||||||
import { PreloginResponse } from "../auth/models/response/prelogin.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";
|
import { SsoPreValidateResponse } from "../auth/models/response/sso-pre-validate.response";
|
||||||
import { TwoFactorAuthenticatorResponse } from "../auth/models/response/two-factor-authenticator.response";
|
import { TwoFactorAuthenticatorResponse } from "../auth/models/response/two-factor-authenticator.response";
|
||||||
import { TwoFactorDuoResponse } from "../auth/models/response/two-factor-duo.response";
|
import { TwoFactorDuoResponse } from "../auth/models/response/two-factor-duo.response";
|
||||||
@@ -95,7 +93,6 @@ import { EventRequest } from "../models/request/event.request";
|
|||||||
import { KdfRequest } from "../models/request/kdf.request";
|
import { KdfRequest } from "../models/request/kdf.request";
|
||||||
import { KeysRequest } from "../models/request/keys.request";
|
import { KeysRequest } from "../models/request/keys.request";
|
||||||
import { PreloginRequest } from "../models/request/prelogin.request";
|
import { PreloginRequest } from "../models/request/prelogin.request";
|
||||||
import { RegisterRequest } from "../models/request/register.request";
|
|
||||||
import { StorageRequest } from "../models/request/storage.request";
|
import { StorageRequest } from "../models/request/storage.request";
|
||||||
import { UpdateAvatarRequest } from "../models/request/update-avatar.request";
|
import { UpdateAvatarRequest } from "../models/request/update-avatar.request";
|
||||||
import { UpdateDomainsRequest } from "../models/request/update-domains.request";
|
import { UpdateDomainsRequest } from "../models/request/update-domains.request";
|
||||||
@@ -147,10 +144,7 @@ export abstract class ApiService {
|
|||||||
| UserApiTokenRequest
|
| UserApiTokenRequest
|
||||||
| WebAuthnLoginTokenRequest,
|
| WebAuthnLoginTokenRequest,
|
||||||
) => Promise<
|
) => Promise<
|
||||||
| IdentityTokenResponse
|
IdentityTokenResponse | IdentityTwoFactorResponse | IdentityDeviceVerificationResponse
|
||||||
| IdentityTwoFactorResponse
|
|
||||||
| IdentityCaptchaResponse
|
|
||||||
| IdentityDeviceVerificationResponse
|
|
||||||
>;
|
>;
|
||||||
refreshIdentityToken: () => Promise<any>;
|
refreshIdentityToken: () => Promise<any>;
|
||||||
|
|
||||||
@@ -167,7 +161,6 @@ export abstract class ApiService {
|
|||||||
postSecurityStamp: (request: SecretVerificationRequest) => Promise<any>;
|
postSecurityStamp: (request: SecretVerificationRequest) => Promise<any>;
|
||||||
getAccountRevisionDate: () => Promise<number>;
|
getAccountRevisionDate: () => Promise<number>;
|
||||||
postPasswordHint: (request: PasswordHintRequest) => Promise<any>;
|
postPasswordHint: (request: PasswordHintRequest) => Promise<any>;
|
||||||
postRegister: (request: RegisterRequest) => Promise<RegisterResponse>;
|
|
||||||
postPremium: (data: FormData) => Promise<PaymentResponse>;
|
postPremium: (data: FormData) => Promise<PaymentResponse>;
|
||||||
postReinstatePremium: () => Promise<any>;
|
postReinstatePremium: () => Promise<any>;
|
||||||
postAccountStorage: (request: StorageRequest) => Promise<PaymentResponse>;
|
postAccountStorage: (request: StorageRequest) => Promise<PaymentResponse>;
|
||||||
|
|||||||
@@ -47,10 +47,9 @@ export abstract class AccountApiService {
|
|||||||
* @param request - The request object containing the user's email verification token,
|
* @param request - The request object containing the user's email verification token,
|
||||||
* the email, hashed MP, newly created user key, and new asymmetric user key pair along
|
* the email, hashed MP, newly created user key, and new asymmetric user key pair along
|
||||||
* with the KDF information used during the process.
|
* with the KDF information used during the process.
|
||||||
* @returns A promise that resolves to a string captcha bypass token when the
|
* @returns A promise that resolves when the registration process is successfully completed.
|
||||||
* registration process is successfully completed.
|
|
||||||
*/
|
*/
|
||||||
abstract registerFinish(request: RegisterFinishRequest): Promise<string>;
|
abstract registerFinish(request: RegisterFinishRequest): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the [dbo].[User].[VerifyDevices] flag to true or false.
|
* Sets the [dbo].[User].[VerifyDevices] flag to true or false.
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import { I18nService } from "../platform/abstractions/i18n.service";
|
|
||||||
|
|
||||||
import { IFrameComponent } from "./iframe-component";
|
|
||||||
|
|
||||||
// TODO: PM-15162 - captcha is deprecated as part of UI refresh work
|
|
||||||
export class CaptchaIFrame extends IFrameComponent {
|
|
||||||
constructor(
|
|
||||||
win: Window,
|
|
||||||
webVaultUrl: string,
|
|
||||||
private i18nService: I18nService,
|
|
||||||
successCallback: (message: string) => any,
|
|
||||||
errorCallback: (message: string) => any,
|
|
||||||
infoCallback: (message: string) => any,
|
|
||||||
) {
|
|
||||||
super(
|
|
||||||
win,
|
|
||||||
webVaultUrl,
|
|
||||||
"captcha-connector.html",
|
|
||||||
"hcaptcha_iframe",
|
|
||||||
successCallback,
|
|
||||||
errorCallback,
|
|
||||||
(message: string) => {
|
|
||||||
const parsedMessage = JSON.parse(message);
|
|
||||||
if (typeof parsedMessage !== "string") {
|
|
||||||
this.iframe.height = parsedMessage.height.toString();
|
|
||||||
this.iframe.width = parsedMessage.width.toString();
|
|
||||||
} else {
|
|
||||||
infoCallback(parsedMessage);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
init(siteKey: string): void {
|
|
||||||
super.initComponent(
|
|
||||||
this.createParams({ siteKey: siteKey, locale: this.i18nService.translationLocale }, 1),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
|
||||||
// @ts-strict-ignore
|
|
||||||
export abstract class IFrameComponent {
|
|
||||||
iframe: HTMLIFrameElement;
|
|
||||||
private connectorLink: HTMLAnchorElement;
|
|
||||||
private parseFunction = this.parseMessage.bind(this);
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private win: Window,
|
|
||||||
protected webVaultUrl: string,
|
|
||||||
private path: string,
|
|
||||||
private iframeId: string,
|
|
||||||
public successCallback?: (message: string) => any,
|
|
||||||
public errorCallback?: (message: string) => any,
|
|
||||||
public infoCallback?: (message: string) => any,
|
|
||||||
) {
|
|
||||||
this.connectorLink = win.document.createElement("a");
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
this.sendMessage("stop");
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
this.sendMessage("start");
|
|
||||||
}
|
|
||||||
|
|
||||||
sendMessage(message: any) {
|
|
||||||
if (!this.iframe || !this.iframe.src || !this.iframe.contentWindow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.iframe.contentWindow.postMessage(message, this.iframe.src);
|
|
||||||
}
|
|
||||||
|
|
||||||
base64Encode(str: string): string {
|
|
||||||
return btoa(
|
|
||||||
encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) => {
|
|
||||||
return String.fromCharCode(("0x" + p1) as any);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
this.win.removeEventListener("message", this.parseFunction, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected createParams(data: any, version: number) {
|
|
||||||
return new URLSearchParams({
|
|
||||||
data: this.base64Encode(JSON.stringify(data)),
|
|
||||||
parent: encodeURIComponent(this.win.document.location.href),
|
|
||||||
v: version.toString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected initComponent(params: URLSearchParams): void {
|
|
||||||
this.connectorLink.href = `${this.webVaultUrl}/${this.path}?${params}`;
|
|
||||||
this.iframe = this.win.document.getElementById(this.iframeId) as HTMLIFrameElement;
|
|
||||||
this.iframe.src = this.connectorLink.href;
|
|
||||||
|
|
||||||
this.win.addEventListener("message", this.parseFunction, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseMessage(event: MessageEvent) {
|
|
||||||
if (!this.validMessage(event)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts: string[] = event.data.split("|");
|
|
||||||
if (parts[0] === "success" && this.successCallback) {
|
|
||||||
this.successCallback(parts[1]);
|
|
||||||
} else if (parts[0] === "error" && this.errorCallback) {
|
|
||||||
this.errorCallback(parts[1]);
|
|
||||||
} else if (parts[0] === "info" && this.infoCallback) {
|
|
||||||
this.infoCallback(parts[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private validMessage(event: MessageEvent) {
|
|
||||||
if (
|
|
||||||
event.origin == null ||
|
|
||||||
event.origin === "" ||
|
|
||||||
event.origin !== (this.connectorLink as any).origin ||
|
|
||||||
event.data == null ||
|
|
||||||
typeof event.data !== "string"
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
event.data.indexOf("success|") === 0 ||
|
|
||||||
event.data.indexOf("error|") === 0 ||
|
|
||||||
event.data.indexOf("info|") === 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Utils } from "../../../platform/misc/utils";
|
|
||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
import { TwoFactorProviderType } from "../../enums/two-factor-provider-type";
|
import { TwoFactorProviderType } from "../../enums/two-factor-provider-type";
|
||||||
|
|
||||||
export class AuthResult {
|
export class AuthResult {
|
||||||
userId: UserId;
|
userId: UserId;
|
||||||
captchaSiteKey = "";
|
|
||||||
// TODO: PM-3287 - Remove this after 3 releases of backwards compatibility. - Target release 2023.12 for removal
|
// TODO: PM-3287 - Remove this after 3 releases of backwards compatibility. - Target release 2023.12 for removal
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
@@ -21,10 +19,6 @@ export class AuthResult {
|
|||||||
requiresEncryptionKeyMigration: boolean;
|
requiresEncryptionKeyMigration: boolean;
|
||||||
requiresDeviceVerification: boolean;
|
requiresDeviceVerification: boolean;
|
||||||
|
|
||||||
get requiresCaptcha() {
|
|
||||||
return !Utils.isNullOrWhitespace(this.captchaSiteKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
get requiresTwoFactor() {
|
get requiresTwoFactor() {
|
||||||
return this.twoFactorProviders != null;
|
return this.twoFactorProviders != null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
|
||||||
// @ts-strict-ignore
|
|
||||||
export abstract class CaptchaProtectedRequest {
|
|
||||||
captchaResponse: string = null;
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,14 @@
|
|||||||
import { ClientType } from "../../../../enums";
|
import { ClientType } from "../../../../enums";
|
||||||
import { Utils } from "../../../../platform/misc/utils";
|
import { Utils } from "../../../../platform/misc/utils";
|
||||||
import { CaptchaProtectedRequest } from "../captcha-protected.request";
|
|
||||||
|
|
||||||
import { DeviceRequest } from "./device.request";
|
import { DeviceRequest } from "./device.request";
|
||||||
import { TokenTwoFactorRequest } from "./token-two-factor.request";
|
import { TokenTwoFactorRequest } from "./token-two-factor.request";
|
||||||
import { TokenRequest } from "./token.request";
|
import { TokenRequest } from "./token.request";
|
||||||
|
|
||||||
export class PasswordTokenRequest extends TokenRequest implements CaptchaProtectedRequest {
|
export class PasswordTokenRequest extends TokenRequest {
|
||||||
constructor(
|
constructor(
|
||||||
public email: string,
|
public email: string,
|
||||||
public masterPasswordHash: string,
|
public masterPasswordHash: string,
|
||||||
public captchaResponse: string,
|
|
||||||
protected twoFactor: TokenTwoFactorRequest,
|
protected twoFactor: TokenTwoFactorRequest,
|
||||||
device?: DeviceRequest,
|
device?: DeviceRequest,
|
||||||
public newDeviceOtp?: string,
|
public newDeviceOtp?: string,
|
||||||
@@ -25,10 +23,6 @@ export class PasswordTokenRequest extends TokenRequest implements CaptchaProtect
|
|||||||
obj.username = this.email;
|
obj.username = this.email;
|
||||||
obj.password = this.masterPasswordHash;
|
obj.password = this.masterPasswordHash;
|
||||||
|
|
||||||
if (this.captchaResponse != null) {
|
|
||||||
obj.captchaResponse = this.captchaResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.newDeviceOtp) {
|
if (this.newDeviceOtp) {
|
||||||
obj.newDeviceOtp = this.newDeviceOtp;
|
obj.newDeviceOtp = this.newDeviceOtp;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export interface ICaptchaProtectedResponse {
|
|
||||||
captchaBypassToken: string;
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { BaseResponse } from "../../../models/response/base.response";
|
|
||||||
|
|
||||||
export class IdentityCaptchaResponse extends BaseResponse {
|
|
||||||
siteKey: string;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.siteKey = this.getResponseProperty("HCaptcha_SiteKey");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,12 +2,9 @@ import { BaseResponse } from "@bitwarden/common/models/response/base.response";
|
|||||||
|
|
||||||
export class IdentityDeviceVerificationResponse extends BaseResponse {
|
export class IdentityDeviceVerificationResponse extends BaseResponse {
|
||||||
deviceVerified: boolean;
|
deviceVerified: boolean;
|
||||||
captchaToken: string;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
super(response);
|
super(response);
|
||||||
this.deviceVerified = this.getResponseProperty("DeviceVerified") ?? false;
|
this.deviceVerified = this.getResponseProperty("DeviceVerified") ?? false;
|
||||||
|
|
||||||
this.captchaToken = this.getResponseProperty("CaptchaBypassToken");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,12 @@ export class IdentityTwoFactorResponse extends BaseResponse {
|
|||||||
twoFactorProviders: TwoFactorProviderType[];
|
twoFactorProviders: TwoFactorProviderType[];
|
||||||
// a map of two-factor providers to necessary data for completion
|
// a map of two-factor providers to necessary data for completion
|
||||||
twoFactorProviders2: Record<TwoFactorProviderType, Record<string, string>>;
|
twoFactorProviders2: Record<TwoFactorProviderType, Record<string, string>>;
|
||||||
captchaToken: string;
|
|
||||||
ssoEmail2faSessionToken: string;
|
ssoEmail2faSessionToken: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
masterPasswordPolicy?: MasterPasswordPolicyResponse;
|
masterPasswordPolicy?: MasterPasswordPolicyResponse;
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
super(response);
|
super(response);
|
||||||
this.captchaToken = this.getResponseProperty("CaptchaBypassToken");
|
|
||||||
this.twoFactorProviders = this.getResponseProperty("TwoFactorProviders");
|
this.twoFactorProviders = this.getResponseProperty("TwoFactorProviders");
|
||||||
this.twoFactorProviders2 = this.getResponseProperty("TwoFactorProviders2");
|
this.twoFactorProviders2 = this.getResponseProperty("TwoFactorProviders2");
|
||||||
this.masterPasswordPolicy = new MasterPasswordPolicyResponse(
|
this.masterPasswordPolicy = new MasterPasswordPolicyResponse(
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import { BaseResponse } from "../../../models/response/base.response";
|
|
||||||
|
|
||||||
import { ICaptchaProtectedResponse } from "./captcha-protected.response";
|
|
||||||
|
|
||||||
export class RegisterResponse extends BaseResponse implements ICaptchaProtectedResponse {
|
|
||||||
captchaBypassToken: string;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
|
||||||
super(response);
|
|
||||||
this.captchaBypassToken = this.getResponseProperty("CaptchaBypassToken");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -84,7 +84,7 @@ export class AccountApiServiceImplementation implements AccountApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async registerFinish(request: RegisterFinishRequest): Promise<string> {
|
async registerFinish(request: RegisterFinishRequest): Promise<void> {
|
||||||
const env = await firstValueFrom(this.environmentService.environment$);
|
const env = await firstValueFrom(this.environmentService.environment$);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
|
||||||
// @ts-strict-ignore
|
|
||||||
import { KdfType } from "@bitwarden/key-management";
|
|
||||||
|
|
||||||
import { CaptchaProtectedRequest } from "../../auth/models/request/captcha-protected.request";
|
|
||||||
|
|
||||||
import { KeysRequest } from "./keys.request";
|
|
||||||
import { ReferenceEventRequest } from "./reference-event.request";
|
|
||||||
|
|
||||||
export class RegisterRequest implements CaptchaProtectedRequest {
|
|
||||||
masterPasswordHint: string;
|
|
||||||
keys: KeysRequest;
|
|
||||||
token: string;
|
|
||||||
organizationUserId: string;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
public email: string,
|
|
||||||
public name: string,
|
|
||||||
public masterPasswordHash: string,
|
|
||||||
masterPasswordHint: string,
|
|
||||||
public key: string,
|
|
||||||
public referenceData: ReferenceEventRequest,
|
|
||||||
public captchaResponse: string,
|
|
||||||
public kdf: KdfType,
|
|
||||||
public kdfIterations: number,
|
|
||||||
public kdfMemory?: number,
|
|
||||||
public kdfParallelism?: number,
|
|
||||||
) {
|
|
||||||
this.masterPasswordHint = masterPasswordHint ? masterPasswordHint : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,11 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Utils } from "../../platform/misc/utils";
|
|
||||||
|
|
||||||
import { BaseResponse } from "./base.response";
|
import { BaseResponse } from "./base.response";
|
||||||
|
|
||||||
export class ErrorResponse extends BaseResponse {
|
export class ErrorResponse extends BaseResponse {
|
||||||
message: string;
|
message: string;
|
||||||
validationErrors: { [key: string]: string[] };
|
validationErrors: { [key: string]: string[] };
|
||||||
statusCode: number;
|
statusCode: number;
|
||||||
captchaRequired: boolean;
|
|
||||||
captchaSiteKey: string;
|
|
||||||
|
|
||||||
constructor(response: any, status: number, identityResponse?: boolean) {
|
constructor(response: any, status: number, identityResponse?: boolean) {
|
||||||
super(response);
|
super(response);
|
||||||
@@ -28,8 +24,6 @@ export class ErrorResponse extends BaseResponse {
|
|||||||
} else if (errorModel) {
|
} else if (errorModel) {
|
||||||
this.message = this.getResponseProperty("Message", errorModel);
|
this.message = this.getResponseProperty("Message", errorModel);
|
||||||
this.validationErrors = this.getResponseProperty("ValidationErrors", errorModel);
|
this.validationErrors = this.getResponseProperty("ValidationErrors", errorModel);
|
||||||
this.captchaSiteKey = this.validationErrors?.HCaptcha_SiteKey?.[0];
|
|
||||||
this.captchaRequired = !Utils.isNullOrWhitespace(this.captchaSiteKey);
|
|
||||||
}
|
}
|
||||||
this.statusCode = status;
|
this.statusCode = status;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,13 +69,11 @@ import { UpdateTwoFactorYubikeyOtpRequest } from "../auth/models/request/update-
|
|||||||
import { ApiKeyResponse } from "../auth/models/response/api-key.response";
|
import { ApiKeyResponse } from "../auth/models/response/api-key.response";
|
||||||
import { AuthRequestResponse } from "../auth/models/response/auth-request.response";
|
import { AuthRequestResponse } from "../auth/models/response/auth-request.response";
|
||||||
import { DeviceVerificationResponse } from "../auth/models/response/device-verification.response";
|
import { DeviceVerificationResponse } from "../auth/models/response/device-verification.response";
|
||||||
import { IdentityCaptchaResponse } from "../auth/models/response/identity-captcha.response";
|
|
||||||
import { IdentityDeviceVerificationResponse } from "../auth/models/response/identity-device-verification.response";
|
import { IdentityDeviceVerificationResponse } from "../auth/models/response/identity-device-verification.response";
|
||||||
import { IdentityTokenResponse } from "../auth/models/response/identity-token.response";
|
import { IdentityTokenResponse } from "../auth/models/response/identity-token.response";
|
||||||
import { IdentityTwoFactorResponse } from "../auth/models/response/identity-two-factor.response";
|
import { IdentityTwoFactorResponse } from "../auth/models/response/identity-two-factor.response";
|
||||||
import { KeyConnectorUserKeyResponse } from "../auth/models/response/key-connector-user-key.response";
|
import { KeyConnectorUserKeyResponse } from "../auth/models/response/key-connector-user-key.response";
|
||||||
import { PreloginResponse } from "../auth/models/response/prelogin.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";
|
import { SsoPreValidateResponse } from "../auth/models/response/sso-pre-validate.response";
|
||||||
import { TwoFactorAuthenticatorResponse } from "../auth/models/response/two-factor-authenticator.response";
|
import { TwoFactorAuthenticatorResponse } from "../auth/models/response/two-factor-authenticator.response";
|
||||||
import { TwoFactorDuoResponse } from "../auth/models/response/two-factor-duo.response";
|
import { TwoFactorDuoResponse } from "../auth/models/response/two-factor-duo.response";
|
||||||
@@ -106,7 +104,6 @@ import { EventRequest } from "../models/request/event.request";
|
|||||||
import { KdfRequest } from "../models/request/kdf.request";
|
import { KdfRequest } from "../models/request/kdf.request";
|
||||||
import { KeysRequest } from "../models/request/keys.request";
|
import { KeysRequest } from "../models/request/keys.request";
|
||||||
import { PreloginRequest } from "../models/request/prelogin.request";
|
import { PreloginRequest } from "../models/request/prelogin.request";
|
||||||
import { RegisterRequest } from "../models/request/register.request";
|
|
||||||
import { StorageRequest } from "../models/request/storage.request";
|
import { StorageRequest } from "../models/request/storage.request";
|
||||||
import { UpdateAvatarRequest } from "../models/request/update-avatar.request";
|
import { UpdateAvatarRequest } from "../models/request/update-avatar.request";
|
||||||
import { UpdateDomainsRequest } from "../models/request/update-domains.request";
|
import { UpdateDomainsRequest } from "../models/request/update-domains.request";
|
||||||
@@ -200,10 +197,7 @@ export class ApiService implements ApiServiceAbstraction {
|
|||||||
| SsoTokenRequest
|
| SsoTokenRequest
|
||||||
| WebAuthnLoginTokenRequest,
|
| WebAuthnLoginTokenRequest,
|
||||||
): Promise<
|
): Promise<
|
||||||
| IdentityTokenResponse
|
IdentityTokenResponse | IdentityTwoFactorResponse | IdentityDeviceVerificationResponse
|
||||||
| IdentityTwoFactorResponse
|
|
||||||
| IdentityCaptchaResponse
|
|
||||||
| IdentityDeviceVerificationResponse
|
|
||||||
> {
|
> {
|
||||||
const headers = new Headers({
|
const headers = new Headers({
|
||||||
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
||||||
@@ -246,12 +240,6 @@ export class ApiService implements ApiServiceAbstraction {
|
|||||||
Object.keys(responseJson.TwoFactorProviders2).length
|
Object.keys(responseJson.TwoFactorProviders2).length
|
||||||
) {
|
) {
|
||||||
return new IdentityTwoFactorResponse(responseJson);
|
return new IdentityTwoFactorResponse(responseJson);
|
||||||
} else if (
|
|
||||||
response.status === 400 &&
|
|
||||||
responseJson.HCaptcha_SiteKey &&
|
|
||||||
Object.keys(responseJson.HCaptcha_SiteKey).length
|
|
||||||
) {
|
|
||||||
return new IdentityCaptchaResponse(responseJson);
|
|
||||||
} else if (
|
} else if (
|
||||||
response.status === 400 &&
|
response.status === 400 &&
|
||||||
responseJson?.ErrorModel?.Message === ApiService.NEW_DEVICE_VERIFICATION_REQUIRED_MESSAGE
|
responseJson?.ErrorModel?.Message === ApiService.NEW_DEVICE_VERIFICATION_REQUIRED_MESSAGE
|
||||||
@@ -369,19 +357,6 @@ export class ApiService implements ApiServiceAbstraction {
|
|||||||
return this.send("POST", "/accounts/password-hint", request, false, false);
|
return this.send("POST", "/accounts/password-hint", request, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async postRegister(request: RegisterRequest): Promise<RegisterResponse> {
|
|
||||||
const env = await firstValueFrom(this.environmentService.environment$);
|
|
||||||
const r = await this.send(
|
|
||||||
"POST",
|
|
||||||
"/accounts/register",
|
|
||||||
request,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
env.getIdentityUrl(),
|
|
||||||
);
|
|
||||||
return new RegisterResponse(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
async postPremium(data: FormData): Promise<PaymentResponse> {
|
async postPremium(data: FormData): Promise<PaymentResponse> {
|
||||||
const r = await this.send("POST", "/accounts/premium", data, true, true);
|
const r = await this.send("POST", "/accounts/premium", data, true, true);
|
||||||
return new PaymentResponse(r);
|
return new PaymentResponse(r);
|
||||||
|
|||||||
Reference in New Issue
Block a user