Files
jiapuapp/static/tac/js/jiapu-tac-adapter.js
T
2026-07-27 06:50:23 +08:00

162 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function attachJiapuTacAdapter(global) {
"use strict";
var SUPPORTED_TYPES = ["SLIDER", "ROTATE", "CONCAT", "WORD_IMAGE_CLICK"];
function fail(message, code) {
var error = new Error(message);
error.code = code || "TAC_PROTOCOL_INVALID";
throw error;
}
function text(value, label) {
if (typeof value !== "string" || !value.trim()) fail(label + "不能为空");
return value.trim();
}
function positiveInteger(value, label) {
var number = Number(value);
if (!Number.isInteger(number) || number < 1) fail(label + "必须是正整数");
return number;
}
function assertContext(context) {
if (!context || typeof context !== "object") fail("TAC 请求上下文无效");
return {
tenantId: text(context.tenantId, "租户标识"),
operationCode: text(context.operationCode, "认证动作"),
subject: text(context.subject, "验证主体"),
providerCode: text(context.providerCode, "验证服务商").toUpperCase(),
captchaType: text(context.captchaType, "验证码类型").toUpperCase(),
};
}
function assertChallengePayload(type, payload) {
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
fail("行为验证挑战缺少渲染载荷");
}
if (type === "CONCAT") {
text(payload.backgroundImage, "拼图背景图片");
positiveInteger(payload.backgroundImageHeight, "拼图背景高度");
if (!payload.data || !Number.isFinite(Number(payload.data.randomY))) {
fail("拼图挑战缺少 randomY");
}
return;
}
text(payload.backgroundImage, "行为验证背景图片");
text(payload.templateImage, "行为验证模板图片");
}
function normalizeChallengeResponse(response, expectedContext) {
var context = assertContext(expectedContext);
if (!response || typeof response !== "object" || Number(response.code) !== 200) {
fail((response && response.msg) || "行为验证挑战请求失败", "TAC_CHALLENGE_FAILED");
}
var data = response.data;
if (!data || typeof data !== "object" || data.required !== true) {
fail("行为验证挑战未处于强制验证状态", "TAC_CHALLENGE_NOT_REQUIRED");
}
var providerCode = text(data.providerCode, "挑战服务商").toUpperCase();
var captchaType = text(data.captchaType, "挑战验证码类型").toUpperCase();
if (providerCode !== "TIANAI" || providerCode !== context.providerCode) {
fail("行为验证挑战服务商不匹配", "TAC_PROVIDER_MISMATCH");
}
if (SUPPORTED_TYPES.indexOf(captchaType) < 0 || captchaType !== context.captchaType) {
fail("行为验证挑战类型不匹配", "TAC_TYPE_MISMATCH");
}
var challengeId = text(data.challengeId, "行为验证挑战标识");
var expireSeconds = positiveInteger(data.expireSeconds, "行为验证挑战有效期");
assertChallengePayload(captchaType, data.payload);
return {
challenge: {
required: true,
providerCode: providerCode,
captchaType: captchaType,
challengeId: challengeId,
expireSeconds: expireSeconds,
},
sdkResponse: {
code: 200,
msg: typeof response.msg === "string" ? response.msg : "操作成功",
data: Object.assign({}, data.payload, {
type: captchaType,
id: challengeId,
}),
},
};
}
function assertTrack(track) {
if (!track || typeof track !== "object" || Array.isArray(track)) fail("行为验证轨迹无效");
positiveInteger(track.bgImageWidth, "轨迹背景宽度");
positiveInteger(track.bgImageHeight, "轨迹背景高度");
var startTime = Number(track.startTime);
var stopTime = Number(track.stopTime);
if (!Number.isFinite(startTime) || !Number.isFinite(stopTime) || stopTime < startTime) {
fail("行为验证轨迹时间无效");
}
if (!Array.isArray(track.trackList) || track.trackList.length < 1) {
fail("行为验证轨迹不能为空");
}
track.trackList.forEach(function validatePoint(point) {
if (!point || !Number.isFinite(Number(point.x)) || !Number.isFinite(Number(point.y)) ||
!Number.isFinite(Number(point.t)) || typeof point.type !== "string" || !point.type) {
fail("行为验证轨迹点无效");
}
});
return track;
}
function buildVerifyBody(libraryRequest, expectedContext, challenge) {
var context = assertContext(expectedContext);
if (!challenge || typeof challenge !== "object") fail("行为验证挑战上下文缺失");
var challengeId = text(challenge.challengeId, "行为验证挑战标识");
if (!libraryRequest || libraryRequest.id !== challengeId) {
fail("行为验证 challenge 标识与当前挑战不匹配", "TAC_CHALLENGE_MISMATCH");
}
if (challenge.providerCode !== context.providerCode || challenge.captchaType !== context.captchaType) {
fail("行为验证挑战上下文已变化", "TAC_CONTEXT_MISMATCH");
}
return {
tenantId: context.tenantId,
subject: context.subject,
challengeId: challengeId,
providerCode: challenge.providerCode,
captchaType: challenge.captchaType,
// 受保护 OpenAPI 的唯一合同是 payload.trackSDK 回调 id 已提升为 challengeId
// 不得再次混入 payload,也不得退回“直接传 track”的兼容分支。
payload: { track: assertTrack(libraryRequest.data) },
};
}
function normalizeVerifyResponse(response) {
if (!response || typeof response !== "object" || Number(response.code) !== 200) {
fail((response && response.msg) || "行为验证请求失败", "TAC_VERIFY_REQUEST_FAILED");
}
var data = response.data;
if (!data || data.passed !== true) {
fail((data && data.message) || "行为验证未通过", "TAC_VERIFY_REJECTED");
}
var validToken = text(data.validToken, "行为验证票据");
var expireSeconds = positiveInteger(data.expireSeconds, "行为验证票据有效期");
return {
code: 200,
data: { passed: true, validToken: validToken, expireSeconds: expireSeconds },
};
}
function toSdkFailure(error) {
return {
code: 4001,
msg: error && error.message ? error.message : "行为验证失败,请重试",
};
}
global.JiapuTacAdapter = Object.freeze({
normalizeChallengeResponse: normalizeChallengeResponse,
buildVerifyBody: buildVerifyBody,
normalizeVerifyResponse: normalizeVerifyResponse,
toSdkFailure: toSdkFailure,
});
})(typeof window !== "undefined" ? window : globalThis);