修复认证:隔离实名认证内容并处理二维码图片失败
This commit is contained in:
1 parent
9b8124a4ea
commit
b3a66d97c8
3 files changed
+264
-5
No files matched your search
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* Failure modes recorded before the fixes:
|
||||
* - Certification HTML must keep its QR-refresh script, without reading or writing the console parent.
|
||||
* - Receiving a QR URL is not evidence that an image loaded; pending/broken images must not start login polling.
|
||||
* - A broken image needs an accessible error and a retry, and a fresh loaded image must still permit normal login.
|
||||
* All scenarios enter the actual built pages and replace only the external HTTP service or browser clock.
|
||||
*/
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import {
|
||||
appOrigin,
|
||||
backendOrigin,
|
||||
eventually,
|
||||
login,
|
||||
scenario,
|
||||
} from "./helpers/console-browser.mjs";
|
||||
|
||||
const qrImage = "R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==";
|
||||
|
||||
function reply(route, data) {
|
||||
return route.fulfill({
|
||||
json: { status: 200, msg: "", data },
|
||||
headers: { "Access-Control-Allow-Origin": "*" },
|
||||
});
|
||||
}
|
||||
|
||||
test(
|
||||
"certification QR refresh scripts run without console-parent privileges",
|
||||
{ timeout: 30000 },
|
||||
(t) =>
|
||||
scenario(t, {}, async (page) => {
|
||||
await page.addInitScript(() => {
|
||||
document.addEventListener(
|
||||
"DOMContentLoaded",
|
||||
() => {
|
||||
document.documentElement.dataset.auditIsolationSeed =
|
||||
"synthetic-local-marker";
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
});
|
||||
await page
|
||||
.context()
|
||||
.route(`${backendOrigin}/console/v1/certification/**`, (route) => {
|
||||
const path = new URL(route.request().url()).pathname;
|
||||
if (path.endsWith("/info"))
|
||||
return reply(route, {
|
||||
certification_open: 1,
|
||||
certification_company_open: 0,
|
||||
certification_upload: 0,
|
||||
is_certification: 0,
|
||||
person: { status: 4 },
|
||||
});
|
||||
if (path.endsWith("/auth"))
|
||||
return reply(route, {
|
||||
html: `<!doctype html><html><body><div id="qrcode"><img src="data:image/gif;base64,${qrImage}"></div><script>
|
||||
document.documentElement.dataset.auditPluginScript = "ran";
|
||||
try {
|
||||
parent.document.documentElement.dataset.auditFromCertification =
|
||||
parent.document.documentElement.dataset.auditIsolationSeed;
|
||||
document.documentElement.dataset.auditParentAccess = "allowed";
|
||||
} catch {
|
||||
document.documentElement.dataset.auditParentAccess = "blocked";
|
||||
}
|
||||
setTimeout(() => {
|
||||
const replacement = document.createElement("img");
|
||||
replacement.src = "data:image/gif;base64,${qrImage}";
|
||||
replacement.dataset.auditDynamicQr = "updated";
|
||||
document.querySelector("#qrcode").replaceChildren(replacement);
|
||||
}, 30);
|
||||
</script></body></html>`,
|
||||
});
|
||||
if (path.endsWith("/status"))
|
||||
return reply(route, { code: 2, refresh: 0 });
|
||||
return route.fallback();
|
||||
});
|
||||
await login(page, "/account/realname");
|
||||
await page
|
||||
.getByRole("button", { name: "继续扫码核验", exact: true })
|
||||
.click();
|
||||
const element = await page.locator("iframe.auth-frame").elementHandle();
|
||||
const frame = await element.contentFrame();
|
||||
await frame
|
||||
.locator('[data-certification-qr][data-audit-dynamic-qr="updated"]')
|
||||
.waitFor();
|
||||
const observed = {
|
||||
refreshedQrVisible: await frame
|
||||
.locator("[data-certification-qr]")
|
||||
.isVisible(),
|
||||
pluginScriptRan: await frame.evaluate(
|
||||
() => document.documentElement.dataset.auditPluginScript === "ran",
|
||||
),
|
||||
parentAccessBlocked: await frame.evaluate(
|
||||
() =>
|
||||
document.documentElement.dataset.auditParentAccess === "blocked",
|
||||
),
|
||||
parentMarkerUntouched: await page.evaluate(
|
||||
() =>
|
||||
document.documentElement.dataset.auditFromCertification ===
|
||||
undefined,
|
||||
),
|
||||
};
|
||||
assert.equal(observed.refreshedQrVisible, true);
|
||||
assert.equal(observed.pluginScriptRan, true);
|
||||
assert.equal(observed.parentAccessBlocked, true);
|
||||
assert.equal(observed.parentMarkerUntouched, true);
|
||||
return observed;
|
||||
}),
|
||||
);
|
||||
|
||||
test(
|
||||
"a broken WeChat image stops its scan and a fresh image recovers login",
|
||||
{ timeout: 35000 },
|
||||
(t) =>
|
||||
scenario(t, {}, async (page) => {
|
||||
await page.clock.install();
|
||||
let acquisitions = 0;
|
||||
let firstScanPolls = 0;
|
||||
let secondScanPolls = 0;
|
||||
let releaseImage;
|
||||
const imageRelease = new Promise((resolve) => {
|
||||
releaseImage = resolve;
|
||||
});
|
||||
let imageRequested;
|
||||
const imageStarted = new Promise((resolve) => {
|
||||
imageRequested = resolve;
|
||||
});
|
||||
await page.context().route(`${backendOrigin}/**`, async (route) => {
|
||||
const url = new URL(route.request().url());
|
||||
if (url.pathname === "/audit-qr/1.gif") {
|
||||
imageRequested();
|
||||
await imageRelease;
|
||||
return route.fulfill({
|
||||
contentType: "image/gif",
|
||||
body: "invalid-image",
|
||||
});
|
||||
}
|
||||
if (url.pathname === "/audit-qr/2.gif")
|
||||
return route.fulfill({
|
||||
contentType: "image/gif",
|
||||
body: Buffer.from(qrImage, "base64"),
|
||||
});
|
||||
if (url.pathname === "/console/v1/common")
|
||||
return reply(route, {
|
||||
website_name: "本地扫码验收",
|
||||
login_phone_password: 1,
|
||||
login_email_password: 1,
|
||||
plugin_configuration: { mp_weixin_notice: { scan_login: 1 } },
|
||||
});
|
||||
if (url.pathname === "/console/v1/mp_weixin_notice/qrcode") {
|
||||
acquisitions++;
|
||||
return reply(route, {
|
||||
img_url: `${backendOrigin}/audit-qr/${acquisitions}.gif`,
|
||||
ticket: `local-qr-ticket-${acquisitions}`,
|
||||
token: "local-qr-token",
|
||||
});
|
||||
}
|
||||
if (url.pathname === "/console/v1/mp_weixin_notice/qrcode/status") {
|
||||
if (url.searchParams.get("ticket") === "local-qr-ticket-1") {
|
||||
firstScanPolls++;
|
||||
return reply(route, { status: "Wait" });
|
||||
}
|
||||
secondScanPolls++;
|
||||
return reply(route, { status: "Success", jwt: "e2e-session-token" });
|
||||
}
|
||||
return route.fallback();
|
||||
});
|
||||
try {
|
||||
await page.goto(`${appOrigin}/login?redirect=/security/ssh-keys`);
|
||||
await page
|
||||
.getByRole("button", { name: "获取二维码", exact: true })
|
||||
.click();
|
||||
await imageStarted;
|
||||
await page.clock.runFor(5000);
|
||||
const pollsBeforeImage = firstScanPolls;
|
||||
releaseImage();
|
||||
await eventually(
|
||||
() =>
|
||||
page.evaluate(() => {
|
||||
const image = document.querySelector(".qr-img");
|
||||
return !image || image.complete;
|
||||
}),
|
||||
Boolean,
|
||||
"The invalid image must finish decoding",
|
||||
);
|
||||
await page.clock.runFor(5000);
|
||||
const failureState = {
|
||||
accessibleError: await page
|
||||
.locator(".qr-status")
|
||||
.getByRole("alert")
|
||||
.isVisible(),
|
||||
brokenImageRemoved: (await page.locator(".qr-img").count()) === 0,
|
||||
pollsBeforeImage,
|
||||
pollsAfterImageFailure: firstScanPolls,
|
||||
};
|
||||
await page
|
||||
.getByRole("button", { name: "刷新二维码", exact: true })
|
||||
.click();
|
||||
await eventually(
|
||||
() =>
|
||||
page.evaluate(() => {
|
||||
const image = document.querySelector(".qr-img");
|
||||
return Boolean(image?.complete && image.naturalWidth > 0);
|
||||
}),
|
||||
Boolean,
|
||||
"Retry must display a real decoded QR image",
|
||||
);
|
||||
await page.clock.runFor(2100);
|
||||
await page.waitForURL((url) => url.pathname === "/security/ssh-keys");
|
||||
await page.getByText("audit-01", { exact: true }).waitFor();
|
||||
assert.equal(failureState.accessibleError, true);
|
||||
assert.equal(failureState.brokenImageRemoved, true);
|
||||
assert.equal(failureState.pollsBeforeImage, 0);
|
||||
assert.equal(failureState.pollsAfterImageFailure, 0);
|
||||
assert.equal(secondScanPolls, 1);
|
||||
return { ...failureState, retryCompletedNormalLogin: true };
|
||||
} finally {
|
||||
releaseImage();
|
||||
}
|
||||
}),
|
||||
);
|
||||
Reference in new issue
Block a user