610 lines
23 KiB
Vue
610 lines
23 KiB
Vue
<script>
|
|
/**
|
|
* 第三方首次登录绑定:微信扫码和通用 OAuth 使用各自的待绑定会话;先验证授权状态,再以手机/邮箱验证码绑定并校验服务端 JWT。
|
|
*/
|
|
import { definePageMeta } from "#imports";
|
|
import "~/assets/styles/PhoneCodeField.CwyHXfML.css";
|
|
import "~/assets/styles/GeetestField.DiieTibK.css";
|
|
import "~/assets/styles/CaptchaField.BSN3ZbsD.css";
|
|
import "~/assets/styles/bind.CFZQE2UH.css";
|
|
definePageMeta({
|
|
layout: `blank`,
|
|
});
|
|
import { useRuntimeConfig } from "#app";
|
|
import { toDisplayString } from "vue";
|
|
import { onMounted } from "vue";
|
|
import { normalizeClass } 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 { createElementVNode } from "vue";
|
|
import { createTextVNode } from "vue";
|
|
import { unref } from "vue";
|
|
import { defineComponent } from "vue";
|
|
import { createVNode } from "vue";
|
|
import { openBlock } from "vue";
|
|
import { withModifiers } from "vue";
|
|
import { useRoute } from "#app";
|
|
import { navigateTo } from "#app";
|
|
import { useHead } from "#app";
|
|
import { NuxtLink } from "#components";
|
|
import { useApi } from "~/lib/auth.js";
|
|
import { isValidJwt } from "~/lib/auth.js";
|
|
import { useAuth } from "~/lib/auth.js";
|
|
import { t as useSiteConfig_t } from "~/lib/useSiteConfig.js";
|
|
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 { n as wechat_binding_n } from "~/lib/wechat-binding.js";
|
|
import { t as wechat_binding_t } from "~/lib/wechat-binding.js";
|
|
import { r as auth_redirect_r } from "~/lib/auth-redirect.js";
|
|
import inputtext from "primevue/inputtext";
|
|
import CaptchaField from "~/components/CaptchaField.vue";
|
|
import checkbox from "primevue/checkbox";
|
|
import { n as oauth_n } from "~/lib/oauth.js";
|
|
import PhoneCodeField from "~/components/PhoneCodeField.vue";
|
|
import { t as useCaptcha_t } from "~/lib/useCaptcha.js";
|
|
import { t as composables_t } from "~/lib/composables-2.js";
|
|
import { t as useVerifyCode_t } from "~/lib/useVerifyCode.js";
|
|
var ve = {
|
|
class: `auth-card bind-card`,
|
|
},
|
|
ye = {
|
|
key: 1,
|
|
class: `session-status`,
|
|
},
|
|
be = [`disabled`],
|
|
xe = {
|
|
class: `seg`,
|
|
},
|
|
Se = [`aria-pressed`],
|
|
Ce = [`aria-pressed`],
|
|
we = {
|
|
class: `field`,
|
|
},
|
|
w = {
|
|
for: `bind-account`,
|
|
},
|
|
T = {
|
|
class: `input-row`,
|
|
},
|
|
E = {
|
|
class: `field`,
|
|
},
|
|
D = {
|
|
class: `input-row`,
|
|
},
|
|
O = {
|
|
class: `agreement`,
|
|
},
|
|
k = {
|
|
for: `bind-agreement`,
|
|
},
|
|
Te = [`href`],
|
|
Ee = {
|
|
key: 1,
|
|
},
|
|
De = [`href`],
|
|
Oe = {
|
|
key: 3,
|
|
},
|
|
A = _plugin_vue_export_helper_t(
|
|
defineComponent({
|
|
__name: `bind`,
|
|
setup(g) {
|
|
(composables_t({
|
|
layout: `blank`,
|
|
}),
|
|
useHead({
|
|
title: `绑定账号 - 破碎工坊网络`,
|
|
}));
|
|
let S = useApi(),
|
|
A = useAuth(),
|
|
j = useSiteConfig_t(),
|
|
M = useRoute(),
|
|
N = M.query.provider === `wechat`,
|
|
P = N ? wechat_binding_n(sessionStorage) : null,
|
|
F = auth_redirect_r(M.query.redirect),
|
|
I = ref(`phone`),
|
|
L = ref(``),
|
|
z = ref(``),
|
|
B = ref(!1),
|
|
V = ref(!1),
|
|
H = ref(``),
|
|
U = ref(!1),
|
|
W = ref(!1),
|
|
loginAttempt = null,
|
|
G = !0;
|
|
onBeforeUnmount(() => {
|
|
G = !1;
|
|
});
|
|
// 先确认授权仍有效;微信支持已完成登录或 WaitBind,通用 OAuth 解析登录/绑定结果,失效状态不开放绑定。
|
|
async function K() {
|
|
if (!W.value && G) {
|
|
((W.value = !0), (U.value = !1), (H.value = ``));
|
|
const attempt = A.prepareLogin();
|
|
loginAttempt = attempt;
|
|
try {
|
|
if (N) {
|
|
if (!P) throw Error(`扫码会话已过期,请返回登录重新扫码`);
|
|
let e = await S.get(`/mp_weixin_notice/qrcode/status`, {
|
|
ticket: P.ticket,
|
|
token: P.token,
|
|
});
|
|
if (!G || !attempt.isCurrent()) return;
|
|
if (e.status === `Success` && isValidJwt(e.jwt)) {
|
|
(wechat_binding_t(sessionStorage),
|
|
attempt.commit(e.jwt),
|
|
await navigateTo(F));
|
|
return;
|
|
}
|
|
if (e.status !== `WaitBind`)
|
|
throw Error(`扫码会话已失效,请返回登录重新扫码`);
|
|
U.value = !0;
|
|
return;
|
|
}
|
|
let t = await S.get(`/oauth/token`);
|
|
if (!G || !attempt.isCurrent()) return;
|
|
let n = oauth_n(
|
|
t,
|
|
window.location.origin,
|
|
new URL(
|
|
String(
|
|
useRuntimeConfig().public.apiBase || window.location.origin,
|
|
),
|
|
window.location.origin,
|
|
).origin,
|
|
);
|
|
if (n.kind === `login`) {
|
|
(attempt.commit(n.jwt), await navigateTo(`/`));
|
|
return;
|
|
}
|
|
if (n.kind !== `bind`)
|
|
throw Error(`授权会话已过期,请返回登录并重新授权`);
|
|
U.value = !0;
|
|
} catch (e) {
|
|
G &&
|
|
attempt.isCurrent() &&
|
|
(H.value = e instanceof Error ? e.message : `验证授权会话失败`);
|
|
} finally {
|
|
G && (W.value = !1);
|
|
}
|
|
}
|
|
}
|
|
let q = useCaptcha_t(`oauth`),
|
|
{
|
|
sending: J,
|
|
countdown: Y,
|
|
send: ke,
|
|
} = useVerifyCode_t({
|
|
action: `oauth`,
|
|
captcha: q,
|
|
error: H,
|
|
}),
|
|
{
|
|
needed: Ae,
|
|
provider: je,
|
|
captchaId: Me,
|
|
complete: Ne,
|
|
invalidate: X,
|
|
value: Z,
|
|
img: Pe,
|
|
loading: Fe,
|
|
error: Q,
|
|
load: Ie,
|
|
} = q,
|
|
$ = computed(() => {
|
|
let e = j.config.value,
|
|
t = (e) =>
|
|
typeof e == `string` && /^https?:\/\//i.test(e) ? e : ``;
|
|
return {
|
|
service: t(e?.terms_service_url),
|
|
privacy: t(e?.terms_privacy_url),
|
|
};
|
|
});
|
|
watch(I, () => {
|
|
((L.value = ``), (z.value = ``), (H.value = ``));
|
|
});
|
|
// 必须先验证授权会话并同意协议;提交验证码绑定,微信额外携带扫码 ticket/token,成功响应的 JWT 合法后才建立会话。
|
|
async function Le() {
|
|
if (!V.value && G) {
|
|
if (((H.value = ``), !U.value || !loginAttempt.isCurrent())) {
|
|
U.value = !1;
|
|
H.value = `请先验证授权会话;会话失效时请返回登录重新授权`;
|
|
return;
|
|
}
|
|
if (!L.value.trim() || !z.value.trim()) {
|
|
H.value = `请填写账号和验证码`;
|
|
return;
|
|
}
|
|
if (!B.value) {
|
|
H.value = `请先阅读并同意服务协议和隐私协议`;
|
|
return;
|
|
}
|
|
V.value = !0;
|
|
const attempt = loginAttempt;
|
|
try {
|
|
let e = q.validationError?.();
|
|
if (e) throw Error(e);
|
|
let t = await S.post(
|
|
N
|
|
? `/mp_weixin_notice/qrcode/bind_client`
|
|
: `/oauth/client/bind`,
|
|
{
|
|
type: I.value,
|
|
account: L.value.trim(),
|
|
phone_code: `86`,
|
|
code: z.value.trim(),
|
|
...(N && P
|
|
? {
|
|
ticket: P.ticket,
|
|
token: P.token,
|
|
captcha: q.value.value,
|
|
}
|
|
: {}),
|
|
},
|
|
);
|
|
if (!G || !attempt.isCurrent()) return;
|
|
if (!isValidJwt(t?.jwt))
|
|
throw Error(`绑定会话已失效,请返回登录后重新发起第三方授权`);
|
|
(N && wechat_binding_t(sessionStorage),
|
|
attempt.commit(t.jwt),
|
|
await navigateTo(F));
|
|
} catch (e) {
|
|
G &&
|
|
attempt.isCurrent() &&
|
|
((H.value =
|
|
e instanceof Error ? e.message : `绑定失败,请重试`),
|
|
await q.handleError?.(e));
|
|
} finally {
|
|
G && (V.value = !1);
|
|
}
|
|
}
|
|
}
|
|
return (
|
|
onMounted(() => {
|
|
(j.load(), K());
|
|
}),
|
|
// 授权状态未验证时锁定绑定流程;协议勾选、验证码和当前渠道共同决定是否可以提交。
|
|
(e, n) => {
|
|
let i = message,
|
|
a = button,
|
|
s = NuxtLink,
|
|
u = PhoneCodeField,
|
|
g = inputtext,
|
|
y = CaptchaField,
|
|
b = checkbox;
|
|
return (
|
|
openBlock(),
|
|
createElementBlock(`section`, ve, [
|
|
(n[13] ||= createElementVNode(`h1`, null, `绑定账号`, -1)),
|
|
(n[14] ||= createElementVNode(
|
|
`p`,
|
|
{
|
|
class: `bind-intro`,
|
|
},
|
|
`请使用已注册的手机号或邮箱接收验证码,完成与第三方账号的绑定。`,
|
|
-1,
|
|
)),
|
|
unref(H)
|
|
? (openBlock(),
|
|
createBlock(
|
|
i,
|
|
{
|
|
key: 0,
|
|
severity: `error`,
|
|
closable: !1,
|
|
},
|
|
{
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(H)), 1),
|
|
]),
|
|
_: 1,
|
|
},
|
|
))
|
|
: createCommentVNode(``, !0),
|
|
unref(U)
|
|
? createCommentVNode(``, !0)
|
|
: (openBlock(),
|
|
createElementBlock(`div`, ye, [
|
|
createVNode(
|
|
a,
|
|
{
|
|
label: `验证授权会话`,
|
|
loading: unref(W),
|
|
onClick: K,
|
|
},
|
|
null,
|
|
8,
|
|
[`loading`],
|
|
),
|
|
createVNode(
|
|
s,
|
|
{
|
|
to: `/login`,
|
|
},
|
|
{
|
|
default: withCtx(() => [
|
|
...(n[8] ||= [createTextVNode(`返回登录`, -1)]),
|
|
]),
|
|
_: 1,
|
|
},
|
|
),
|
|
])),
|
|
unref(U)
|
|
? (openBlock(),
|
|
createElementBlock(
|
|
`form`,
|
|
{
|
|
key: 2,
|
|
class: `bind-form`,
|
|
onSubmit: withModifiers(Le, [`prevent`]),
|
|
},
|
|
[
|
|
createElementVNode(
|
|
`fieldset`,
|
|
{
|
|
disabled: unref(V),
|
|
},
|
|
[
|
|
(n[9] ||= createElementVNode(
|
|
`legend`,
|
|
null,
|
|
`验证方式`,
|
|
-1,
|
|
)),
|
|
createElementVNode(`div`, xe, [
|
|
createElementVNode(
|
|
`button`,
|
|
{
|
|
type: `button`,
|
|
class: normalizeClass({
|
|
active: unref(I) === `email`,
|
|
}),
|
|
"aria-pressed": unref(I) === `email`,
|
|
onClick: (n[0] ||= (e) =>
|
|
(I.value = `email`)),
|
|
},
|
|
` 邮箱 `,
|
|
10,
|
|
Se,
|
|
),
|
|
createElementVNode(
|
|
`button`,
|
|
{
|
|
type: `button`,
|
|
class: normalizeClass({
|
|
active: unref(I) === `phone`,
|
|
}),
|
|
"aria-pressed": unref(I) === `phone`,
|
|
onClick: (n[1] ||= (e) =>
|
|
(I.value = `phone`)),
|
|
},
|
|
` 手机号 `,
|
|
10,
|
|
Ce,
|
|
),
|
|
]),
|
|
],
|
|
8,
|
|
be,
|
|
),
|
|
createElementVNode(`div`, we, [
|
|
createElementVNode(
|
|
`label`,
|
|
w,
|
|
toDisplayString(
|
|
unref(I) === `phone` ? `手机号` : `邮箱`,
|
|
),
|
|
1,
|
|
),
|
|
createElementVNode(`div`, T, [
|
|
unref(I) === `phone`
|
|
? (openBlock(), createBlock(u, { key: 0 }))
|
|
: createCommentVNode(``, !0),
|
|
createVNode(
|
|
g,
|
|
{
|
|
id: `bind-account`,
|
|
modelValue: unref(L),
|
|
"onUpdate:modelValue": (n[3] ||= (e) =>
|
|
isRef(L) ? (L.value = e) : null),
|
|
type: unref(I) === `email` ? `email` : `tel`,
|
|
placeholder:
|
|
unref(I) === `phone`
|
|
? `输入手机号`
|
|
: `输入邮箱`,
|
|
autocomplete: `username`,
|
|
disabled: unref(V),
|
|
required: ``,
|
|
},
|
|
null,
|
|
8,
|
|
[`modelValue`, `type`, `placeholder`, `disabled`],
|
|
),
|
|
]),
|
|
]),
|
|
createElementVNode(`div`, E, [
|
|
(n[10] ||= createElementVNode(
|
|
`label`,
|
|
{
|
|
for: `bind-code`,
|
|
},
|
|
`验证码`,
|
|
-1,
|
|
)),
|
|
createElementVNode(`div`, D, [
|
|
createVNode(
|
|
g,
|
|
{
|
|
id: `bind-code`,
|
|
modelValue: unref(z),
|
|
"onUpdate:modelValue": (n[4] ||= (e) =>
|
|
isRef(z) ? (z.value = e) : null),
|
|
placeholder: `输入验证码`,
|
|
autocomplete: `one-time-code`,
|
|
disabled: unref(V),
|
|
required: ``,
|
|
},
|
|
null,
|
|
8,
|
|
[`modelValue`, `disabled`],
|
|
),
|
|
createVNode(
|
|
a,
|
|
{
|
|
label: unref(Y) ? `${unref(Y)}s` : `获取验证码`,
|
|
loading: unref(J),
|
|
disabled:
|
|
unref(V) || !unref(L).trim() || unref(Y) > 0,
|
|
onClick: (n[5] ||= (e) =>
|
|
unref(ke)(
|
|
unref(I),
|
|
unref(L).trim(),
|
|
`请输入账号`,
|
|
)),
|
|
},
|
|
null,
|
|
8,
|
|
[`label`, `loading`, `disabled`],
|
|
),
|
|
]),
|
|
]),
|
|
unref(Ae)
|
|
? (openBlock(),
|
|
createBlock(
|
|
y,
|
|
{
|
|
key: 0,
|
|
modelValue: unref(Z),
|
|
"onUpdate:modelValue": (n[6] ||= (e) =>
|
|
isRef(Z) ? (Z.value = e) : null),
|
|
img: unref(Pe),
|
|
provider: unref(je),
|
|
"captcha-id": unref(Me),
|
|
disabled: unref(V) || unref(J),
|
|
onVerified: unref(Ne),
|
|
onInvalid: unref(X),
|
|
loading: unref(Fe),
|
|
onRefresh: unref(Ie),
|
|
},
|
|
null,
|
|
8,
|
|
[
|
|
`modelValue`,
|
|
`img`,
|
|
`provider`,
|
|
`captcha-id`,
|
|
`disabled`,
|
|
`onVerified`,
|
|
`onInvalid`,
|
|
`loading`,
|
|
`onRefresh`,
|
|
],
|
|
))
|
|
: createCommentVNode(``, !0),
|
|
unref(Q)
|
|
? (openBlock(),
|
|
createBlock(
|
|
i,
|
|
{
|
|
key: 1,
|
|
severity: `error`,
|
|
closable: !1,
|
|
},
|
|
{
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(Q)), 1),
|
|
]),
|
|
_: 1,
|
|
},
|
|
))
|
|
: createCommentVNode(``, !0),
|
|
createElementVNode(`div`, O, [
|
|
createVNode(
|
|
b,
|
|
{
|
|
modelValue: unref(B),
|
|
"onUpdate:modelValue": (n[7] ||= (e) =>
|
|
isRef(B) ? (B.value = e) : null),
|
|
"input-id": `bind-agreement`,
|
|
binary: ``,
|
|
disabled: unref(V),
|
|
},
|
|
null,
|
|
8,
|
|
[`modelValue`, `disabled`],
|
|
),
|
|
createElementVNode(`label`, k, [
|
|
(n[11] ||= createTextVNode(`阅读并同意 `, -1)),
|
|
unref($).service
|
|
? (openBlock(),
|
|
createElementBlock(
|
|
`a`,
|
|
{
|
|
key: 0,
|
|
href: unref($).service,
|
|
target: `_blank`,
|
|
rel: `noopener noreferrer`,
|
|
},
|
|
`《服务协议》`,
|
|
8,
|
|
Te,
|
|
))
|
|
: (openBlock(),
|
|
createElementBlock(`span`, Ee, `《服务协议》`)),
|
|
(n[12] ||= createTextVNode(` 和 `, -1)),
|
|
unref($).privacy
|
|
? (openBlock(),
|
|
createElementBlock(
|
|
`a`,
|
|
{
|
|
key: 2,
|
|
href: unref($).privacy,
|
|
target: `_blank`,
|
|
rel: `noopener noreferrer`,
|
|
},
|
|
`《隐私协议》`,
|
|
8,
|
|
De,
|
|
))
|
|
: (openBlock(),
|
|
createElementBlock(`span`, Oe, `《隐私协议》`)),
|
|
]),
|
|
]),
|
|
createVNode(
|
|
a,
|
|
{
|
|
type: `submit`,
|
|
label: `确认绑定`,
|
|
loading: unref(V),
|
|
disabled:
|
|
!unref(B) || !unref(L).trim() || !unref(z).trim(),
|
|
fluid: ``,
|
|
},
|
|
null,
|
|
8,
|
|
[`loading`, `disabled`],
|
|
),
|
|
],
|
|
32,
|
|
))
|
|
: createCommentVNode(``, !0),
|
|
])
|
|
);
|
|
}
|
|
);
|
|
},
|
|
}),
|
|
[[`__scopeId`, `data-v-146faed1`]],
|
|
);
|
|
export {};
|
|
export default A;
|
|
</script>
|