1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +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:
Todd Martin
2025-05-09 10:44:11 -04:00
committed by GitHub
parent 625256b08e
commit 4191bb9533
59 changed files with 56 additions and 977 deletions

View File

@@ -172,7 +172,6 @@ describe("WebRegistrationFinishService", () => {
let userKey: UserKey;
let userKeyEncString: EncString;
let userKeyPair: [string, EncString];
let capchaBypassToken: string;
let orgInvite: OrganizationInvite;
let orgSponsoredFreeFamilyPlanToken: string;
@@ -198,7 +197,6 @@ describe("WebRegistrationFinishService", () => {
userKeyEncString = new EncString("userKeyEncrypted");
userKeyPair = ["publicKey", new EncString("privateKey")];
capchaBypassToken = "capchaBypassToken";
orgInvite = new OrganizationInvite();
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.makeKeyPair.mockResolvedValue(userKeyPair);
accountApiService.registerFinish.mockResolvedValue(capchaBypassToken);
accountApiService.registerFinish.mockResolvedValue();
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
const result = await service.finishRegistration(
email,
passwordInputResult,
emailVerificationToken,
);
expect(result).toEqual(capchaBypassToken);
await service.finishRegistration(email, passwordInputResult, emailVerificationToken);
expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey);
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.makeKeyPair.mockResolvedValue(userKeyPair);
accountApiService.registerFinish.mockResolvedValue(capchaBypassToken);
accountApiService.registerFinish.mockResolvedValue();
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(orgInvite);
const result = await service.finishRegistration(email, passwordInputResult);
expect(result).toEqual(capchaBypassToken);
await service.finishRegistration(email, passwordInputResult);
expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey);
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.makeKeyPair.mockResolvedValue(userKeyPair);
accountApiService.registerFinish.mockResolvedValue(capchaBypassToken);
accountApiService.registerFinish.mockResolvedValue();
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
const result = await service.finishRegistration(
await service.finishRegistration(
email,
passwordInputResult,
undefined,
orgSponsoredFreeFamilyPlanToken,
);
expect(result).toEqual(capchaBypassToken);
expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey);
expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey);
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.makeKeyPair.mockResolvedValue(userKeyPair);
accountApiService.registerFinish.mockResolvedValue(capchaBypassToken);
accountApiService.registerFinish.mockResolvedValue();
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
const result = await service.finishRegistration(
await service.finishRegistration(
email,
passwordInputResult,
undefined,
@@ -357,8 +345,6 @@ describe("WebRegistrationFinishService", () => {
emergencyAccessId,
);
expect(result).toEqual(capchaBypassToken);
expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey);
expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey);
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.makeKeyPair.mockResolvedValue(userKeyPair);
accountApiService.registerFinish.mockResolvedValue(capchaBypassToken);
accountApiService.registerFinish.mockResolvedValue();
acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null);
const result = await service.finishRegistration(
await service.finishRegistration(
email,
passwordInputResult,
undefined,
@@ -404,8 +390,6 @@ describe("WebRegistrationFinishService", () => {
providerUserId,
);
expect(result).toEqual(capchaBypassToken);
expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey);
expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey);
expect(accountApiService.registerFinish).toHaveBeenCalledWith(

View File

@@ -80,12 +80,7 @@ export class RecoverTwoFactorComponent implements OnInit {
remember: false,
};
const credentials = new PasswordLoginCredentials(
email,
this.masterPassword,
"",
twoFactorRequest,
);
const credentials = new PasswordLoginCredentials(email, this.masterPassword, twoFactorRequest);
try {
const authResult = await this.loginStrategyService.logIn(credentials);

View File

@@ -366,14 +366,9 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy {
return;
}
const captchaToken = await this.finishRegistration(passwordInputResult);
await this.finishRegistration(passwordInputResult);
if (captchaToken == null) {
this.submitting = false;
return;
}
await this.logIn(passwordInputResult.newPassword, captchaToken);
await this.logIn(passwordInputResult.newPassword);
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 */
private async logIn(masterPassword: string, captchaBypassToken: string): Promise<void> {
const credentials = new PasswordLoginCredentials(
this.email,
masterPassword,
captchaBypassToken,
null,
);
/** Logs the user in */
private async logIn(masterPassword: string): Promise<void> {
const credentials = new PasswordLoginCredentials(this.email, masterPassword);
await this.loginStrategyService.logIn(credentials);
}

View File

@@ -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>

View File

@@ -1 +0,0 @@
@import "../scss/styles.scss";

View File

@@ -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>

View File

@@ -1,8 +0,0 @@
body {
min-width: 0px !important;
padding: 0;
margin: 0;
background: transparent;
display: flex;
justify-content: center;
}

View File

@@ -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));
}

View File

@@ -24,12 +24,6 @@
}
}
#hcaptcha_iframe {
border: none;
transition: height 0.25s linear;
width: 100%;
}
@each $mfaType in $mfaTypes {
.mfaType#{$mfaType} {
content: url("../images/two-factor/" + $mfaType + ".png");

View File

@@ -129,16 +129,6 @@ const plugins = [
filename: "redirect-connector.html",
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({
template: "./src/connectors/duo-redirect.html",
filename: "duo-redirect-connector.html",
@@ -344,7 +334,6 @@ const webpackConfig = {
"connectors/webauthn": "./src/connectors/webauthn.ts",
"connectors/webauthn-fallback": "./src/connectors/webauthn-fallback.ts",
"connectors/sso": "./src/connectors/sso.ts",
"connectors/captcha": "./src/connectors/captcha.ts",
"connectors/duo-redirect": "./src/connectors/duo-redirect.ts",
"connectors/redirect": "./src/connectors/redirect.ts",
styles: ["./src/scss/styles.scss", "./src/scss/tailwind.css"],