645 lines
24 KiB
Vue
645 lines
24 KiB
Vue
<script>
|
|
/**
|
|
* 通用安全挑战弹窗:challenge 提供允许的方式,context 提供操作场景与验证令牌。
|
|
* verified 仅回传验证字段供父级重试原操作;非登录场景暂不开放 certification 认证。
|
|
*/
|
|
import "~/assets/styles/GeetestField.DiieTibK.css";
|
|
import "~/assets/styles/CaptchaField.BSN3ZbsD.css";
|
|
import "~/assets/styles/SecurityVerificationDialog.skYFo4v7.css";
|
|
import { i as rolldown_runtime_i } from "~/lib/rolldown-runtime.js";
|
|
import { toDisplayString } from "vue";
|
|
import { onBeforeUnmount } from "vue";
|
|
import { watch } from "vue";
|
|
import { createBlock } from "vue";
|
|
import { ref } from "vue";
|
|
import { withCtx } from "vue";
|
|
import { createCommentVNode } from "vue";
|
|
import { computed } from "vue";
|
|
import { isRef } from "vue";
|
|
import { createElementBlock } from "vue";
|
|
import { Fragment } from "vue";
|
|
import { createElementVNode } from "vue";
|
|
import { createTextVNode } from "vue";
|
|
import { unref } from "vue";
|
|
import { defineComponent } from "vue";
|
|
import { createVNode } from "vue";
|
|
import { openBlock } from "vue";
|
|
import { loadModule } from "~/utils/load-module.js";
|
|
import { securityMethods } from "~/lib/auth.js";
|
|
import { useApi } from "~/lib/auth.js";
|
|
import dialog from "primevue/dialog";
|
|
import button from "primevue/button";
|
|
import { t as _plugin_vue_export_helper_t } from "~/lib/_plugin-vue_export-helper.js";
|
|
import message from "primevue/message";
|
|
import inputtext from "primevue/inputtext";
|
|
import CaptchaField from "~/components/CaptchaField.vue";
|
|
import select from "primevue/select";
|
|
import { t as interval_t } from "~/lib/interval.js";
|
|
import { t as useCaptcha_t } from "~/lib/useCaptcha.js";
|
|
var w = {
|
|
class: `security-verify`,
|
|
},
|
|
oe = {
|
|
key: 1,
|
|
class: `tip`,
|
|
},
|
|
T = [`src`],
|
|
se = {
|
|
for: `security-value`,
|
|
},
|
|
ce = {
|
|
class: `verify-input`,
|
|
},
|
|
le = {
|
|
key: 1,
|
|
class: `error`,
|
|
},
|
|
ue = {
|
|
key: 4,
|
|
role: `alert`,
|
|
class: `error`,
|
|
},
|
|
de = {
|
|
key: 5,
|
|
role: `alert`,
|
|
class: `error`,
|
|
},
|
|
fe = {
|
|
class: `actions`,
|
|
},
|
|
E = Object.assign(
|
|
_plugin_vue_export_helper_t(
|
|
defineComponent({
|
|
__name: `SecurityVerificationDialog`,
|
|
props: {
|
|
visible: {
|
|
type: Boolean,
|
|
},
|
|
challenge: {},
|
|
busy: {
|
|
type: Boolean,
|
|
default: !1,
|
|
},
|
|
requestError: {
|
|
default: ``,
|
|
},
|
|
context: {
|
|
default: void 0,
|
|
},
|
|
},
|
|
emits: [`update:visible`, `verified`, `cancel`],
|
|
setup(m, { emit: b }) {
|
|
let E = m,
|
|
D = b,
|
|
O = useApi(),
|
|
k = ref(``),
|
|
A = ref(``),
|
|
j = ref(``),
|
|
M = ref(``),
|
|
N = ref(!1),
|
|
P = ref(0),
|
|
F = ref(!1),
|
|
I = ref(``),
|
|
L = 0,
|
|
R = !1,
|
|
z,
|
|
B,
|
|
V = useCaptcha_t(`security_verify`),
|
|
{
|
|
needed: pe,
|
|
provider: me,
|
|
captchaId: he,
|
|
img: ge,
|
|
value: H,
|
|
loading: _e,
|
|
error: U,
|
|
} = V,
|
|
W = computed(() =>
|
|
securityMethods(E.challenge).map((e) => ({
|
|
...e,
|
|
disabled:
|
|
e.value === `certification` && E.context?.scene !== `login`,
|
|
displayLabel:
|
|
e.label +
|
|
(e.value === `certification` && E.context?.scene !== `login`
|
|
? `(暂未开放)`
|
|
: ``),
|
|
})),
|
|
),
|
|
G = computed(() => W.value.find((e) => e.value === k.value)),
|
|
K = computed(() => E.busy || N.value || F.value);
|
|
function ve() {
|
|
(clearInterval(z), clearTimeout(B), (z = void 0), (B = void 0));
|
|
}
|
|
function q() {
|
|
// 切换挑战/验证方式时统一清空密码、验证码、认证二维码及定时器,并废弃旧请求代次。
|
|
(L++,
|
|
ve(),
|
|
(A.value = ``),
|
|
(M.value = ``),
|
|
(P.value = 0),
|
|
(N.value = !1),
|
|
(F.value = !1),
|
|
(j.value = ``),
|
|
(I.value = ``),
|
|
V.invalidate());
|
|
}
|
|
(watch(
|
|
() => [E.visible, E.challenge],
|
|
() => {
|
|
(q(),
|
|
E.visible &&
|
|
((k.value = W.value.find((e) => !e.disabled)?.value || ``),
|
|
(k.value === `phone_code` || k.value === `email_code`) &&
|
|
V.needed.value &&
|
|
V.load()));
|
|
},
|
|
{
|
|
immediate: !0,
|
|
},
|
|
),
|
|
watch(k, () => {
|
|
(q(),
|
|
E.visible &&
|
|
(k.value === `phone_code` || k.value === `email_code`) &&
|
|
V.needed.value &&
|
|
V.load());
|
|
}),
|
|
onBeforeUnmount(() => {
|
|
((R = !0), q());
|
|
}));
|
|
let J = (e) => !R && E.visible && e === L,
|
|
Y = computed(
|
|
() => k.value === `email_code` || k.value === `phone_code`,
|
|
);
|
|
async function ye() {
|
|
// 登录异常使用 exception_login,其余操作必须传 action;图形挑战通过后才能发送短信/邮件。
|
|
if (K.value || P.value || !Y.value || !G.value || !E.visible)
|
|
return;
|
|
let e =
|
|
E.context?.scene === `login`
|
|
? `exception_login`
|
|
: E.context?.action;
|
|
if (!e) {
|
|
M.value = `验证场景缺失,请重新发起操作`;
|
|
return;
|
|
}
|
|
if (((M.value = V.validationError()), M.value)) return;
|
|
let t = L,
|
|
n = G.value,
|
|
r = n.account || E.context?.account || ``;
|
|
if (E.context?.scene === `login` && !r) {
|
|
M.value = `验证账户信息缺失,请重新登录`;
|
|
return;
|
|
}
|
|
N.value = !0;
|
|
try {
|
|
if (
|
|
(await O.post(
|
|
n.value === `email_code` ? `/email/code` : `/phone/code`,
|
|
{
|
|
action: e,
|
|
...(n.value === `email_code`
|
|
? {
|
|
email: r,
|
|
}
|
|
: {
|
|
phone: r,
|
|
phone_code:
|
|
n.phone_code || E.context?.phoneCode || `86`,
|
|
}),
|
|
captcha: V.value.value.trim(),
|
|
token: V.token.value,
|
|
security_verify_token:
|
|
n.security_verify_token || E.context?.token || ``,
|
|
},
|
|
),
|
|
!J(t))
|
|
)
|
|
return;
|
|
((P.value = 60),
|
|
(z = interval_t(() => {
|
|
--P.value <= 0 && (clearInterval(z), (z = void 0));
|
|
}, 1e3)),
|
|
V.needed.value && V.load());
|
|
} catch (e) {
|
|
J(t) &&
|
|
((M.value = e instanceof Error ? e.message : `验证码发送失败`),
|
|
await V.handleError(e));
|
|
} finally {
|
|
J(t) && (N.value = !1);
|
|
}
|
|
}
|
|
function X() {
|
|
// 操作密码不能 trim,其他验证码去除边缘空白;验证令牌必须和所选挑战方式一同提交。
|
|
return {
|
|
security_verify_method: k.value,
|
|
security_verify_value:
|
|
k.value === `operate_password` ? A.value : A.value.trim(),
|
|
...(j.value
|
|
? {
|
|
certify_id: j.value,
|
|
}
|
|
: {}),
|
|
security_verify_token:
|
|
G.value?.security_verify_token || E.context?.token || ``,
|
|
};
|
|
}
|
|
function Z() {
|
|
return {
|
|
account: G.value?.account || E.context?.account || ``,
|
|
phone_code: G.value?.phone_code || E.context?.phoneCode || `86`,
|
|
security_verify_token:
|
|
G.value?.security_verify_token || E.context?.token || ``,
|
|
};
|
|
}
|
|
async function Q(e) {
|
|
// 实名认证结果串行查询,弹窗关闭或挑战改变后停止续排,不把旧认证结果交给新操作。
|
|
if (J(e) && j.value) {
|
|
try {
|
|
let t = await O.get(
|
|
`/login/exception/certification/status`,
|
|
{
|
|
...Z(),
|
|
certify_id: j.value,
|
|
},
|
|
!1,
|
|
);
|
|
if (!J(e)) return;
|
|
if (Number(t.verify_status) === 1) {
|
|
(clearTimeout(B), D(`verified`, X()));
|
|
return;
|
|
}
|
|
M.value = ``;
|
|
} catch (t) {
|
|
J(e) &&
|
|
(M.value =
|
|
t instanceof Error
|
|
? t.message
|
|
: `暂时无法查询验证结果,正在重试`);
|
|
}
|
|
J(e) && (B = setTimeout(() => void Q(e), 2e3));
|
|
}
|
|
}
|
|
async function be() {
|
|
// 认证链接要求 HTTPS;二维码在浏览器按需生成,认证完成仍以状态接口为准。
|
|
if (
|
|
K.value ||
|
|
G.value?.disabled ||
|
|
k.value !== `certification` ||
|
|
E.context?.scene !== `login`
|
|
)
|
|
return;
|
|
let t = ++L;
|
|
(clearTimeout(B), (F.value = !0), (M.value = ``));
|
|
try {
|
|
let n = await O.post(
|
|
`/login/exception/certification/create`,
|
|
Z(),
|
|
!1,
|
|
);
|
|
if (!J(t)) return;
|
|
if (
|
|
!n.certify_id ||
|
|
!n.certify_url ||
|
|
!/^https:\/\//i.test(n.certify_url)
|
|
)
|
|
throw Error(`认证二维码响应无效,请重试`);
|
|
let { toDataURL: r } = await loadModule(
|
|
async () => {
|
|
let { toDataURL: t } =
|
|
await import("qrcode/lib/browser.js").then((t) =>
|
|
rolldown_runtime_i(t.default, 1),
|
|
);
|
|
return {
|
|
toDataURL: t,
|
|
};
|
|
},
|
|
[],
|
|
import.meta.url,
|
|
),
|
|
i = await r(n.certify_url, {
|
|
width: 240,
|
|
margin: 2,
|
|
});
|
|
if (!J(t)) return;
|
|
((j.value = String(n.certify_id)), (I.value = i), Q(t));
|
|
} catch (e) {
|
|
J(t) &&
|
|
(M.value =
|
|
e instanceof Error ? e.message : `认证二维码加载失败`);
|
|
} finally {
|
|
J(t) && (F.value = !1);
|
|
}
|
|
}
|
|
function xe() {
|
|
if (
|
|
!K.value &&
|
|
E.visible &&
|
|
G.value &&
|
|
!G.value.disabled &&
|
|
k.value !== `certification`
|
|
) {
|
|
if (((M.value = ``), !A.value.trim())) {
|
|
M.value = `请输入验证信息`;
|
|
return;
|
|
}
|
|
D(`verified`, X());
|
|
}
|
|
}
|
|
function $() {
|
|
E.busy || (q(), D(`cancel`), D(`update:visible`, !1));
|
|
}
|
|
return (e, n) => {
|
|
let r = select,
|
|
a = message,
|
|
c = button,
|
|
_ = inputtext,
|
|
v = CaptchaField,
|
|
y = dialog;
|
|
return (
|
|
openBlock(),
|
|
createBlock(
|
|
y,
|
|
{
|
|
visible: m.visible,
|
|
header: `安全验证`,
|
|
modal: ``,
|
|
closable: !m.busy,
|
|
"close-on-escape": !m.busy,
|
|
style: {
|
|
width: `min(430px, calc(100vw - 28px))`,
|
|
},
|
|
"onUpdate:visible": (n[3] ||= (e) => !e && $()),
|
|
},
|
|
{
|
|
default: withCtx(() => [
|
|
createElementVNode(`div`, w, [
|
|
(n[5] ||= createElementVNode(
|
|
`label`,
|
|
{
|
|
for: `security-method`,
|
|
},
|
|
`验证方式`,
|
|
-1,
|
|
)),
|
|
createVNode(
|
|
r,
|
|
{
|
|
"input-id": `security-method`,
|
|
modelValue: unref(k),
|
|
"onUpdate:modelValue": (n[0] ||= (e) =>
|
|
isRef(k) ? (k.value = e) : null),
|
|
options: unref(W),
|
|
"option-label": `displayLabel`,
|
|
"option-value": `value`,
|
|
"option-disabled": `disabled`,
|
|
disabled: unref(K),
|
|
},
|
|
null,
|
|
8,
|
|
[`modelValue`, `options`, `disabled`],
|
|
),
|
|
unref(k)
|
|
? createCommentVNode(``, !0)
|
|
: (openBlock(),
|
|
createBlock(
|
|
a,
|
|
{
|
|
key: 0,
|
|
severity: `warn`,
|
|
closable: !1,
|
|
},
|
|
{
|
|
default: withCtx(() => [
|
|
...(n[4] ||= [
|
|
createTextVNode(
|
|
`当前没有已开放的验证方式,请联系客服。`,
|
|
-1,
|
|
),
|
|
]),
|
|
]),
|
|
_: 1,
|
|
},
|
|
)),
|
|
unref(G)?.tip
|
|
? (openBlock(),
|
|
createElementBlock(
|
|
`p`,
|
|
oe,
|
|
toDisplayString(unref(G).tip),
|
|
1,
|
|
))
|
|
: createCommentVNode(``, !0),
|
|
unref(k) === `certification`
|
|
? (openBlock(),
|
|
createElementBlock(
|
|
Fragment,
|
|
{
|
|
key: 2,
|
|
},
|
|
[
|
|
unref(I)
|
|
? (openBlock(),
|
|
createElementBlock(
|
|
`img`,
|
|
{
|
|
key: 0,
|
|
src: unref(I),
|
|
alt: `安全验证二维码`,
|
|
class: `security-qr`,
|
|
},
|
|
null,
|
|
8,
|
|
T,
|
|
))
|
|
: createCommentVNode(``, !0),
|
|
createVNode(
|
|
c,
|
|
{
|
|
label: unref(I) ? `刷新二维码` : `获取二维码`,
|
|
loading: unref(F),
|
|
disabled: unref(K),
|
|
onClick: be,
|
|
},
|
|
null,
|
|
8,
|
|
[`label`, `loading`, `disabled`],
|
|
),
|
|
],
|
|
64,
|
|
))
|
|
: unref(k)
|
|
? (openBlock(),
|
|
createElementBlock(
|
|
Fragment,
|
|
{
|
|
key: 3,
|
|
},
|
|
[
|
|
createElementVNode(
|
|
`label`,
|
|
se,
|
|
toDisplayString(
|
|
unref(Y) ? `验证码` : `操作密码`,
|
|
),
|
|
1,
|
|
),
|
|
createElementVNode(`div`, ce, [
|
|
createVNode(
|
|
_,
|
|
{
|
|
id: `security-value`,
|
|
modelValue: unref(A),
|
|
"onUpdate:modelValue": (n[1] ||= (e) =>
|
|
isRef(A) ? (A.value = e) : null),
|
|
type: unref(Y) ? `text` : `password`,
|
|
autocomplete: unref(Y)
|
|
? `one-time-code`
|
|
: `off`,
|
|
disabled: unref(K),
|
|
},
|
|
null,
|
|
8,
|
|
[
|
|
`modelValue`,
|
|
`type`,
|
|
`autocomplete`,
|
|
`disabled`,
|
|
],
|
|
),
|
|
unref(Y)
|
|
? (openBlock(),
|
|
createBlock(
|
|
c,
|
|
{
|
|
key: 0,
|
|
label: unref(P)
|
|
? `${unref(P)}s`
|
|
: `发送验证码`,
|
|
severity: `secondary`,
|
|
outlined: ``,
|
|
disabled: unref(K) || unref(P) > 0,
|
|
onClick: ye,
|
|
},
|
|
null,
|
|
8,
|
|
[`label`, `disabled`],
|
|
))
|
|
: createCommentVNode(``, !0),
|
|
]),
|
|
unref(Y) && unref(pe)
|
|
? (openBlock(),
|
|
createBlock(
|
|
v,
|
|
{
|
|
key: 0,
|
|
modelValue: unref(H),
|
|
"onUpdate:modelValue": (n[2] ||= (e) =>
|
|
isRef(H) ? (H.value = e) : null),
|
|
img: unref(ge),
|
|
provider: unref(me),
|
|
"captcha-id": unref(he),
|
|
loading: unref(_e),
|
|
disabled: unref(K),
|
|
onVerified: unref(V).complete,
|
|
onInvalid: unref(V).invalidate,
|
|
onRefresh: unref(V).load,
|
|
},
|
|
null,
|
|
8,
|
|
[
|
|
`modelValue`,
|
|
`img`,
|
|
`provider`,
|
|
`captcha-id`,
|
|
`loading`,
|
|
`disabled`,
|
|
`onVerified`,
|
|
`onInvalid`,
|
|
`onRefresh`,
|
|
],
|
|
))
|
|
: createCommentVNode(``, !0),
|
|
unref(Y) && unref(U)
|
|
? (openBlock(),
|
|
createElementBlock(
|
|
`small`,
|
|
le,
|
|
toDisplayString(unref(U)),
|
|
1,
|
|
))
|
|
: createCommentVNode(``, !0),
|
|
],
|
|
64,
|
|
))
|
|
: createCommentVNode(``, !0),
|
|
unref(M)
|
|
? (openBlock(),
|
|
createElementBlock(
|
|
`small`,
|
|
ue,
|
|
toDisplayString(unref(M)),
|
|
1,
|
|
))
|
|
: m.requestError
|
|
? (openBlock(),
|
|
createElementBlock(
|
|
`small`,
|
|
de,
|
|
toDisplayString(m.requestError),
|
|
1,
|
|
))
|
|
: createCommentVNode(``, !0),
|
|
createElementVNode(`div`, fe, [
|
|
createVNode(
|
|
c,
|
|
{
|
|
label: `取消`,
|
|
severity: `secondary`,
|
|
text: ``,
|
|
disabled: m.busy,
|
|
onClick: $,
|
|
},
|
|
null,
|
|
8,
|
|
[`disabled`],
|
|
),
|
|
unref(k) && unref(k) !== `certification`
|
|
? (openBlock(),
|
|
createBlock(
|
|
c,
|
|
{
|
|
key: 0,
|
|
label: `验证`,
|
|
loading: m.busy,
|
|
disabled: unref(K),
|
|
onClick: xe,
|
|
},
|
|
null,
|
|
8,
|
|
[`loading`, `disabled`],
|
|
))
|
|
: createCommentVNode(``, !0),
|
|
]),
|
|
]),
|
|
]),
|
|
_: 1,
|
|
},
|
|
8,
|
|
[`visible`, `closable`, `close-on-escape`],
|
|
)
|
|
);
|
|
};
|
|
},
|
|
}),
|
|
[[`__scopeId`, `data-v-ff4061ec`]],
|
|
),
|
|
{
|
|
__name: `SecurityVerificationDialog`,
|
|
},
|
|
);
|
|
export {};
|
|
export default E;
|
|
</script>
|