Files
console-dev/app/components/SecurityVerificationDialog.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>