@liujitcn/kratos-admin-core 0.0.28 → 0.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/build/auto-imports.json +73 -0
- package/build/plugins.ts +2 -58
- package/dist/package/declarations/src/api/base/mfa.d.ts +26 -0
- package/dist/package/declarations/src/components/Dialog/FormDialog.vue.d.ts +3 -3
- package/dist/package/declarations/src/components/Dialog/PasswordVerifyDialog.vue.d.ts +65 -0
- package/dist/package/declarations/src/components/Dialog/ProDialog.vue.d.ts +1 -1
- package/dist/package/declarations/src/components/Grid/components/GridItem.vue.d.ts +1 -1
- package/dist/package/declarations/src/components/Mfa/MfaRecoveryCodesDialog.vue.d.ts +19 -0
- package/dist/package/declarations/src/components/Mfa/MfaSetupPanel.vue.d.ts +8 -0
- package/dist/package/declarations/src/components/ProForm/components/ProFormItem.vue.d.ts +4 -4
- package/dist/package/declarations/src/components/ProForm/index.vue.d.ts +1 -1
- package/dist/package/declarations/src/components/ProForm/interface/index.d.ts +4 -1
- package/dist/package/declarations/src/components/RichTextPreview/index.vue.d.ts +8 -0
- package/dist/package/declarations/src/components/Upload/Img.vue.d.ts +1 -1
- package/dist/package/declarations/src/components/Upload/Imgs.vue.d.ts +1 -1
- package/dist/package/declarations/src/layouts/components/LockScreen.vue.d.ts +3 -0
- package/dist/package/declarations/src/locales/generated.d.ts +168 -0
- package/dist/package/declarations/src/locales/index.d.ts +2 -0
- package/dist/package/declarations/src/rpc/base/v1/login.d.ts +33 -3
- package/dist/package/declarations/src/rpc/base/v1/mfa.d.ts +152 -0
- package/dist/package/declarations/src/rpc/base/v1/oauth.d.ts +25 -0
- package/dist/package/declarations/src/rpc/base/v1/oauth_client.d.ts +23 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_dict.d.ts +4 -4
- package/dist/package/declarations/src/rpc/system/admin/v1/base_i18n.d.ts +27 -23
- package/dist/package/declarations/src/security.d.ts +2 -0
- package/dist/package/declarations/src/stores/interface/index.d.ts +14 -1
- package/dist/package/declarations/src/stores/modules/lockScreen.d.ts +20 -0
- package/dist/package/declarations/src/stores/modules/user.d.ts +5 -2
- package/dist/package/declarations/src/stores/runtime.d.ts +1 -0
- package/dist/package/declarations/src/utils/clipboard.d.ts +2 -0
- package/dist/package/declarations/src/utils/webauthn.d.ts +4 -0
- package/dist/package/declarations/vite.config.d.ts +2 -0
- package/dist/package/src/api/base/mfa.ts +114 -0
- package/dist/package/src/components/Dialog/PasswordVerifyDialog.vue +176 -0
- package/dist/package/src/components/Loading/fullScreen.ts +2 -1
- package/dist/package/src/components/MarkdownPreview/index.vue +21 -1
- package/dist/package/src/components/Mfa/MfaRecoveryCodesDialog.vue +91 -0
- package/dist/package/src/components/Mfa/MfaSetupPanel.vue +81 -0
- package/dist/package/src/components/ProForm/components/ProFormItem.vue +16 -1
- package/dist/package/src/components/ProForm/interface/index.ts +4 -0
- package/dist/package/src/components/ProTable/components/ColSetting.vue +1 -1
- package/dist/package/src/components/ProTable/index.vue +6 -2
- package/dist/package/src/components/RichTextPreview/index.vue +45 -0
- package/dist/package/src/config/index.ts +1 -1
- package/dist/package/src/layouts/LayoutClassic/index.vue +3 -1
- package/dist/package/src/layouts/LayoutColumns/index.vue +3 -1
- package/dist/package/src/layouts/LayoutTransverse/index.vue +3 -1
- package/dist/package/src/layouts/LayoutVertical/index.vue +9 -1
- package/dist/package/src/layouts/components/Header/components/Avatar.vue +23 -4
- package/dist/package/src/layouts/components/Header/components/InfoDialog.vue +1 -1
- package/dist/package/src/layouts/components/Header/components/PasswordDialog.vue +1 -1
- package/dist/package/src/layouts/components/LockScreen.vue +450 -0
- package/dist/package/src/layouts/index.vue +2 -0
- package/dist/package/src/layouts/indexAsync.vue +2 -0
- package/dist/package/src/locales/README.md +2 -1
- package/dist/package/src/locales/en-US.json +43 -1
- package/dist/package/src/locales/index.ts +16 -12
- package/dist/package/src/locales/ja-JP.json +43 -1
- package/dist/package/src/locales/zh-CN.json +43 -1
- package/dist/package/src/locales/zh-TW.json +43 -1
- package/dist/package/src/rpc/base/v1/login.ts +33 -2
- package/dist/package/src/rpc/base/v1/mfa.ts +180 -0
- package/dist/package/src/rpc/base/v1/oauth.ts +25 -0
- package/dist/package/src/rpc/base/v1/oauth_client.ts +33 -0
- package/dist/package/src/rpc/system/admin/v1/base_dict.ts +4 -4
- package/dist/package/src/rpc/system/admin/v1/base_i18n.ts +27 -23
- package/dist/package/src/security.ts +2 -0
- package/dist/package/src/stores/interface/index.ts +15 -1
- package/dist/package/src/stores/modules/config.ts +4 -4
- package/dist/package/src/stores/modules/lockScreen.ts +78 -0
- package/dist/package/src/stores/modules/user.ts +16 -3
- package/dist/package/src/stores/runtime.ts +1 -0
- package/dist/package/src/utils/clipboard.ts +22 -0
- package/dist/package/src/utils/request.ts +26 -10
- package/dist/package/src/utils/webauthn.ts +90 -0
- package/dist/package/src/views/login/components/LoginForm.vue +237 -16
- package/dist/package/src/views/login/index.vue +3 -1
- package/dist/package/types/generated/auto-imports.d.ts +132 -129
- package/dist/package/types/generated/components.d.ts +4 -72
- package/package.json +22 -1
- package/types/generated/auto-imports.d.ts +132 -129
- package/types/generated/components.d.ts +4 -72
- package/vite.config.ts +3 -1
|
@@ -59,15 +59,18 @@
|
|
|
59
59
|
</el-form-item>
|
|
60
60
|
</el-form>
|
|
61
61
|
<div class="login-btn">
|
|
62
|
-
<el-button
|
|
62
|
+
<el-button round size="large" @click="resetForm(loginFormRef)">
|
|
63
|
+
<el-icon><CircleClose /></el-icon>
|
|
64
|
+
{{ t("common.action.reset") }}
|
|
65
|
+
</el-button>
|
|
63
66
|
<el-button
|
|
64
|
-
:icon="UserFilled"
|
|
65
67
|
round
|
|
66
68
|
size="large"
|
|
67
69
|
type="primary"
|
|
68
70
|
:loading="loading || oauthTicketLoading"
|
|
69
71
|
@click="handleLogin(loginFormRef)"
|
|
70
72
|
>
|
|
73
|
+
<el-icon><UserFilled /></el-icon>
|
|
71
74
|
{{ t("common.action.login") }}
|
|
72
75
|
</el-button>
|
|
73
76
|
</div>
|
|
@@ -128,6 +131,48 @@
|
|
|
128
131
|
/>
|
|
129
132
|
</div>
|
|
130
133
|
</el-dialog>
|
|
134
|
+
<ProDialog v-model="mfaDialogVisible" :title="t('core.login.mfa_title')" width="360px" :close-on-click-modal="false">
|
|
135
|
+
<ProForm
|
|
136
|
+
ref="mfaLoginFormRef"
|
|
137
|
+
:model="mfaLoginForm"
|
|
138
|
+
:fields="mfaLoginFormFields"
|
|
139
|
+
:rules="mfaLoginFormRules"
|
|
140
|
+
size="default"
|
|
141
|
+
label-width="auto"
|
|
142
|
+
@keyup.enter.prevent="verifyMfaLogin"
|
|
143
|
+
/>
|
|
144
|
+
<template #footer>
|
|
145
|
+
<el-button @click="mfaDialogVisible = false">{{ t("common.action.cancel") }}</el-button>
|
|
146
|
+
<el-button type="primary" :loading="mfaLoading" @click="verifyMfaLogin">
|
|
147
|
+
{{ mfaMethod === "webauthn" ? t("core.login.mfa_webauthn_action") : t("common.action.confirm") }}
|
|
148
|
+
</el-button>
|
|
149
|
+
</template>
|
|
150
|
+
</ProDialog>
|
|
151
|
+
<ProDialog v-model="mfaSetupDialogVisible" :title="t('core.login.mfa_setup_title')" width="520px" :close-on-click-modal="false">
|
|
152
|
+
<template v-if="mfaSetupMethod !== 'webauthn'">
|
|
153
|
+
<MfaSetupPanel :uri="mfaSetupUri" />
|
|
154
|
+
<ProForm
|
|
155
|
+
ref="mfaSetupFormRef"
|
|
156
|
+
:model="mfaSetupForm"
|
|
157
|
+
:fields="mfaSetupFormFields"
|
|
158
|
+
:rules="mfaSetupFormRules"
|
|
159
|
+
size="default"
|
|
160
|
+
label-width="auto"
|
|
161
|
+
@keyup.enter.prevent="confirmMfaSetup"
|
|
162
|
+
/>
|
|
163
|
+
</template>
|
|
164
|
+
<template #footer>
|
|
165
|
+
<el-button :disabled="mfaLoading" @click="mfaSetupDialogVisible = false">{{ t("common.action.cancel") }}</el-button>
|
|
166
|
+
<el-button type="primary" :loading="mfaLoading" @click="confirmMfaSetup">
|
|
167
|
+
{{ mfaSetupMethod === "webauthn" ? t("core.login.mfa_webauthn_action") : t("common.action.confirm") }}
|
|
168
|
+
</el-button>
|
|
169
|
+
</template>
|
|
170
|
+
</ProDialog>
|
|
171
|
+
<MfaRecoveryCodesDialog
|
|
172
|
+
v-model="recoveryCodesDialogVisible"
|
|
173
|
+
:codes="recoveryCodes"
|
|
174
|
+
@confirm="finishMfaEnrollment"
|
|
175
|
+
/>
|
|
131
176
|
</template>
|
|
132
177
|
|
|
133
178
|
<script setup lang="ts">
|
|
@@ -136,9 +181,15 @@ import { useRoute, useRouter } from "vue-router";
|
|
|
136
181
|
import { HOME_URL } from "../../../config";
|
|
137
182
|
import { getTimeState } from "../../../utils";
|
|
138
183
|
import { defLoginService } from "../../../api/base/login";
|
|
184
|
+
import { defMfaService } from "../../../api/base/mfa";
|
|
139
185
|
import { defOauthService } from "../../../api/base/oauth";
|
|
140
|
-
import {
|
|
141
|
-
import
|
|
186
|
+
import { createWebAuthnCredential, getWebAuthnAssertion } from "../../../security";
|
|
187
|
+
import ProDialog from "../../../components/Dialog/ProDialog.vue";
|
|
188
|
+
import ProForm from "../../../components/ProForm/index.vue";
|
|
189
|
+
import MfaSetupPanel from "../../../components/Mfa/MfaSetupPanel.vue";
|
|
190
|
+
import MfaRecoveryCodesDialog from "../../../components/Mfa/MfaRecoveryCodesDialog.vue";
|
|
191
|
+
import type { ProFormField, ProFormInstance } from "../../../components/ProForm/interface";
|
|
192
|
+
import { LoginStatus, type LoginRequest, type LoginResponse } from "../../../rpc/base/v1/login";
|
|
142
193
|
import type { OauthProvider } from "../../../rpc/base/v1/oauth";
|
|
143
194
|
import { getOauthProviderIcon, withOauthProviderDisplay, type OauthProviderDisplay } from "../../../utils/oauthProvider";
|
|
144
195
|
import { useUserStore } from "../../../stores/modules/user";
|
|
@@ -147,8 +198,7 @@ import { useTabsStore } from "../../../stores/modules/tabs";
|
|
|
147
198
|
import { useKeepAliveStore } from "../../../stores/modules/keepAlive";
|
|
148
199
|
import { initDynamicRouter } from "../../../routers/modules/dynamicRouter";
|
|
149
200
|
import { isUnmatchedRoute, navigateTo, resolveFrontendRouteURL } from "../../../utils/router";
|
|
150
|
-
import {
|
|
151
|
-
import type { ElForm } from "element-plus";
|
|
201
|
+
import type { ElForm, FormRules } from "element-plus";
|
|
152
202
|
import { PASSWORD_CRYPTO_SCENE, encryptPassword } from "../../../utils/passwordCrypto";
|
|
153
203
|
import { useConfigStore } from "../../../stores/modules/config";
|
|
154
204
|
import { Click as GoCaptchaClick, Rotate as GoCaptchaRotate, Slide as GoCaptchaSlide } from "go-captcha-vue";
|
|
@@ -182,6 +232,84 @@ const loginRules = computed(() => ({
|
|
|
182
232
|
const loading = ref(false);
|
|
183
233
|
const oauthLoadingProvider = ref("");
|
|
184
234
|
const oauthTicketLoading = ref(false);
|
|
235
|
+
const mfaDialogVisible = ref(false);
|
|
236
|
+
const mfaLoading = ref(false);
|
|
237
|
+
const mfaChallengeId = ref("");
|
|
238
|
+
const mfaLoginFormRef = ref<ProFormInstance>();
|
|
239
|
+
const mfaLoginForm = reactive({ code: "", recoveryCode: "" });
|
|
240
|
+
const mfaMethod = ref("totp");
|
|
241
|
+
const mfaWebAuthnOptionsJson = ref("");
|
|
242
|
+
const mfaSetupDialogVisible = ref(false);
|
|
243
|
+
const mfaSetupTicket = ref("");
|
|
244
|
+
const mfaSetupUri = ref("");
|
|
245
|
+
const mfaSetupFormRef = ref<ProFormInstance>();
|
|
246
|
+
const mfaSetupForm = reactive({ code: "" });
|
|
247
|
+
const recoveryCodesDialogVisible = ref(false);
|
|
248
|
+
const recoveryCodes = ref<string[]>([]);
|
|
249
|
+
const mfaSetupMethod = ref("totp");
|
|
250
|
+
const mfaSetupWebAuthnOptionsJson = ref("");
|
|
251
|
+
|
|
252
|
+
/** 登录阶段 MFA 输入字段,验证码和恢复码至少填写其一。 */
|
|
253
|
+
const mfaLoginFormFields = computed<ProFormField[]>(() => {
|
|
254
|
+
const recoveryCodeField: ProFormField = {
|
|
255
|
+
prop: "recoveryCode",
|
|
256
|
+
label: t("core.login.mfa_recovery_code"),
|
|
257
|
+
component: "input",
|
|
258
|
+
props: {
|
|
259
|
+
autocomplete: "one-time-code",
|
|
260
|
+
placeholder: t("core.login.mfa_recovery_code")
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
if (mfaMethod.value === "webauthn") return [recoveryCodeField];
|
|
264
|
+
return [
|
|
265
|
+
{
|
|
266
|
+
prop: "code",
|
|
267
|
+
label: t("core.login.mfa_code"),
|
|
268
|
+
component: "input",
|
|
269
|
+
props: {
|
|
270
|
+
autocomplete: "one-time-code",
|
|
271
|
+
inputmode: "numeric",
|
|
272
|
+
maxlength: 8,
|
|
273
|
+
placeholder: t("core.login.mfa_code")
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
recoveryCodeField
|
|
277
|
+
];
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const mfaLoginFormRules = computed<FormRules>(() => {
|
|
281
|
+
if (mfaMethod.value === "webauthn") return {};
|
|
282
|
+
const factorRule = {
|
|
283
|
+
validator: (_rule: unknown, value: string, callback: (error?: Error) => void) => {
|
|
284
|
+
if (value || mfaLoginForm.code || mfaLoginForm.recoveryCode) {
|
|
285
|
+
callback();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
callback(new Error(t("core.login.mfa_code")));
|
|
289
|
+
},
|
|
290
|
+
trigger: "blur"
|
|
291
|
+
};
|
|
292
|
+
return { code: [factorRule], recoveryCode: [factorRule] };
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
/** 强制绑定 MFA 的 TOTP 输入字段。 */
|
|
296
|
+
const mfaSetupFormFields = computed<ProFormField[]>(() => [
|
|
297
|
+
{
|
|
298
|
+
prop: "code",
|
|
299
|
+
label: t("core.login.mfa_code"),
|
|
300
|
+
component: "input",
|
|
301
|
+
props: {
|
|
302
|
+
autocomplete: "one-time-code",
|
|
303
|
+
inputmode: "numeric",
|
|
304
|
+
maxlength: 8,
|
|
305
|
+
placeholder: t("core.login.mfa_code")
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
]);
|
|
309
|
+
|
|
310
|
+
const mfaSetupFormRules = computed<FormRules>(() => ({
|
|
311
|
+
code: [{ required: true, message: t("core.login.mfa_code"), trigger: "blur" }]
|
|
312
|
+
}));
|
|
185
313
|
|
|
186
314
|
/** 登录页三方登录展示项。 */
|
|
187
315
|
type LoginOauthProvider = OauthProvider & OauthProviderDisplay;
|
|
@@ -412,12 +540,14 @@ const handleOauthLogin = async (provider: LoginOauthProvider) => {
|
|
|
412
540
|
};
|
|
413
541
|
|
|
414
542
|
/** 完成登录后的用户信息、字典与动态路由初始化。 */
|
|
415
|
-
const finishLogin = async () => {
|
|
543
|
+
const finishLogin = async (mustChangePassword = false) => {
|
|
416
544
|
// 1.获取用户信息
|
|
417
545
|
await userStore.getUserInfo();
|
|
418
546
|
|
|
419
|
-
|
|
420
|
-
|
|
547
|
+
if (!mustChangePassword) {
|
|
548
|
+
// 2.预加载字典缓存,避免页面首次渲染时字典值为空
|
|
549
|
+
await dictStore.loadDictionaries();
|
|
550
|
+
}
|
|
421
551
|
|
|
422
552
|
// 3.添加动态路由
|
|
423
553
|
await initDynamicRouter();
|
|
@@ -428,7 +558,11 @@ const finishLogin = async () => {
|
|
|
428
558
|
|
|
429
559
|
// 5.优先跳回登录失效前页面,没有记录时再进入首个可访问页面。
|
|
430
560
|
// 统一走动态路由感知跳转,避免首次登录后目标页面尚未完成挂载时直接进入 404。
|
|
431
|
-
|
|
561
|
+
if (mustChangePassword) {
|
|
562
|
+
await navigateTo(router, "/profile", { tab: "password" });
|
|
563
|
+
} else {
|
|
564
|
+
await navigateTo(router, getLoginRedirectPath());
|
|
565
|
+
}
|
|
432
566
|
ElNotification({
|
|
433
567
|
title: getTimeState(),
|
|
434
568
|
message: t("core.login.welcome"),
|
|
@@ -437,6 +571,95 @@ const finishLogin = async () => {
|
|
|
437
571
|
});
|
|
438
572
|
};
|
|
439
573
|
|
|
574
|
+
/** 处理密码或 OAuth 返回的 MFA 登录状态。 */
|
|
575
|
+
const handleLoginResponse = async (result: LoginResponse) => {
|
|
576
|
+
if (result.status === LoginStatus.LOGIN_STATUS_MFA_REQUIRED) {
|
|
577
|
+
mfaChallengeId.value = result.mfa_challenge_id;
|
|
578
|
+
mfaMethod.value = result.mfa_method || "totp";
|
|
579
|
+
mfaWebAuthnOptionsJson.value = result.mfa_webauthn_options_json || "";
|
|
580
|
+
mfaLoginForm.code = "";
|
|
581
|
+
mfaLoginForm.recoveryCode = "";
|
|
582
|
+
mfaDialogVisible.value = true;
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
if (result.status === LoginStatus.LOGIN_STATUS_MFA_ENROLLMENT_REQUIRED) {
|
|
586
|
+
mfaSetupTicket.value = result.mfa_setup_ticket;
|
|
587
|
+
mfaSetupMethod.value = result.mfa_method || "totp";
|
|
588
|
+
await beginMfaSetup(result.mfa_setup_ticket);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
userStore.updateTokenAuth(result.access_token, result.refresh_token ?? "", result.token_type ?? "", result.expires_in);
|
|
592
|
+
await finishLogin(result.status === LoginStatus.LOGIN_STATUS_PASSWORD_CHANGE_REQUIRED);
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
/** 校验登录阶段 MFA 并完成登录。 */
|
|
596
|
+
const verifyMfaLogin = async () => {
|
|
597
|
+
if (mfaLoading.value || !mfaChallengeId.value) return;
|
|
598
|
+
if (mfaMethod.value !== "webauthn" && !(await mfaLoginFormRef.value?.validate())) return;
|
|
599
|
+
mfaLoading.value = true;
|
|
600
|
+
try {
|
|
601
|
+
const useRecoveryCode = Boolean(mfaLoginForm.recoveryCode);
|
|
602
|
+
const webauthnResponseJson =
|
|
603
|
+
mfaMethod.value === "webauthn" && !useRecoveryCode ? await getWebAuthnAssertion(mfaWebAuthnOptionsJson.value) : "";
|
|
604
|
+
if (mfaMethod.value !== "webauthn" && !mfaLoginForm.code && !mfaLoginForm.recoveryCode) return;
|
|
605
|
+
const result = await defMfaService.VerifyMfa({
|
|
606
|
+
challenge_id: mfaChallengeId.value,
|
|
607
|
+
code: useRecoveryCode ? "" : mfaLoginForm.code,
|
|
608
|
+
recovery_code: mfaLoginForm.recoveryCode,
|
|
609
|
+
webauthn_response_json: webauthnResponseJson
|
|
610
|
+
});
|
|
611
|
+
mfaDialogVisible.value = false;
|
|
612
|
+
await handleLoginResponse(result);
|
|
613
|
+
} finally {
|
|
614
|
+
mfaLoading.value = false;
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
/** 开始强制绑定 MFA。 */
|
|
619
|
+
const beginMfaSetup = async (setupTicket: string) => {
|
|
620
|
+
mfaLoading.value = true;
|
|
621
|
+
try {
|
|
622
|
+
const result = await defMfaService.BeginMfaEnrollment({ setup_ticket: setupTicket });
|
|
623
|
+
mfaSetupTicket.value = result.setup_ticket;
|
|
624
|
+
mfaSetupUri.value = result.otpauth_uri;
|
|
625
|
+
mfaSetupMethod.value = result.method || "totp";
|
|
626
|
+
mfaSetupWebAuthnOptionsJson.value = result.webauthn_options_json || "";
|
|
627
|
+
mfaSetupForm.code = "";
|
|
628
|
+
mfaSetupDialogVisible.value = true;
|
|
629
|
+
} finally {
|
|
630
|
+
mfaLoading.value = false;
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
/** 关闭恢复码弹窗并结束强制绑定登录流程。 */
|
|
635
|
+
const finishMfaEnrollment = async () => {
|
|
636
|
+
if (!recoveryCodes.value.length) return;
|
|
637
|
+
recoveryCodes.value = [];
|
|
638
|
+
ElMessage.success(t("core.login.mfa_setup_success"));
|
|
639
|
+
await loadPageCaptcha();
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
/** 确认强制绑定 MFA 并要求用户重新登录。 */
|
|
643
|
+
const confirmMfaSetup = async () => {
|
|
644
|
+
if (mfaLoading.value || !mfaSetupTicket.value) return;
|
|
645
|
+
if (mfaSetupMethod.value !== "webauthn" && !(await mfaSetupFormRef.value?.validate())) return;
|
|
646
|
+
mfaLoading.value = true;
|
|
647
|
+
try {
|
|
648
|
+
const webauthnResponseJson =
|
|
649
|
+
mfaSetupMethod.value === "webauthn" ? await createWebAuthnCredential(mfaSetupWebAuthnOptionsJson.value) : "";
|
|
650
|
+
const result = await defMfaService.ConfirmMfaEnrollment({
|
|
651
|
+
setup_ticket: mfaSetupTicket.value,
|
|
652
|
+
code: mfaSetupForm.code,
|
|
653
|
+
webauthn_response_json: webauthnResponseJson
|
|
654
|
+
});
|
|
655
|
+
mfaSetupDialogVisible.value = false;
|
|
656
|
+
recoveryCodes.value = result.recovery_codes;
|
|
657
|
+
recoveryCodesDialogVisible.value = true;
|
|
658
|
+
} finally {
|
|
659
|
+
mfaLoading.value = false;
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
|
|
440
663
|
/** 消费 OAuth 回调携带的一次性票据。 */
|
|
441
664
|
const consumeOauthTicket = async () => {
|
|
442
665
|
const oauthError = route.query.oauth_error;
|
|
@@ -452,8 +675,7 @@ const consumeOauthTicket = async () => {
|
|
|
452
675
|
oauthTicketLoading.value = true;
|
|
453
676
|
try {
|
|
454
677
|
const result = await defOauthService.ExchangeOauthTicket({ ticket });
|
|
455
|
-
|
|
456
|
-
await finishLogin();
|
|
678
|
+
await handleLoginResponse(result);
|
|
457
679
|
} finally {
|
|
458
680
|
oauthTicketLoading.value = false;
|
|
459
681
|
}
|
|
@@ -537,10 +759,9 @@ const submitLogin = async (captchaToken: string) => {
|
|
|
537
759
|
captcha_code: captchaToken,
|
|
538
760
|
captcha_id: loginForm.captcha_id
|
|
539
761
|
};
|
|
540
|
-
// 1
|
|
541
|
-
await userStore.login(loginRequest);
|
|
542
|
-
|
|
543
|
-
await finishLogin();
|
|
762
|
+
// 1.执行登录接口,挑战态不保存令牌,等待 MFA 完成。
|
|
763
|
+
const result = await userStore.login(loginRequest);
|
|
764
|
+
await handleLoginResponse(result);
|
|
544
765
|
} catch (_error) {
|
|
545
766
|
await loadPageCaptcha();
|
|
546
767
|
} finally {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<SwitchDark class="dark" />
|
|
5
5
|
<LocaleSwitch class="locale" />
|
|
6
6
|
<div class="login-left">
|
|
7
|
-
<img class="login-left-img" :src="backgroundUrl" alt="login" />
|
|
7
|
+
<img class="login-left-img" :src="backgroundUrl" :alt="t('core.login.background_alt')" />
|
|
8
8
|
</div>
|
|
9
9
|
<div class="login-form">
|
|
10
10
|
<div ref="loginLogoRef" class="login-logo">
|
|
@@ -23,8 +23,10 @@ import LoginForm from "./components/LoginForm.vue";
|
|
|
23
23
|
import SwitchDark from "../../components/SwitchDark/index.vue";
|
|
24
24
|
import LocaleSwitch from "../../components/LocaleSwitch/index.vue";
|
|
25
25
|
import { useConfigStore } from "../../stores/modules/config";
|
|
26
|
+
import { useLocaleStore } from "../../locales";
|
|
26
27
|
|
|
27
28
|
const configStore = useConfigStore();
|
|
29
|
+
const { t } = useLocaleStore();
|
|
28
30
|
const projectName = computed(() => configStore.display.sysName || import.meta.env.VITE_GLOB_APP_TITLE);
|
|
29
31
|
const logoUrl = computed(() => configStore.display.adminLogo);
|
|
30
32
|
const backgroundUrl = computed(() => configStore.display.background);
|
|
@@ -6,137 +6,140 @@
|
|
|
6
6
|
// biome-ignore lint: disable
|
|
7
7
|
export {}
|
|
8
8
|
declare global {
|
|
9
|
-
const ArrowDown: typeof import(
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
const
|
|
9
|
+
const ArrowDown: typeof import('@element-plus/icons-vue').ArrowDown
|
|
10
|
+
const ArrowLeft: typeof import('@element-plus/icons-vue').ArrowLeft
|
|
11
|
+
const ArrowRight: typeof import('@element-plus/icons-vue').ArrowRight
|
|
12
|
+
const ArrowUp: typeof import('@element-plus/icons-vue').ArrowUp
|
|
13
|
+
const Bell: typeof import('@element-plus/icons-vue').Bell
|
|
14
|
+
const Calendar: typeof import('@element-plus/icons-vue').Calendar
|
|
15
|
+
const Camera: typeof import('@element-plus/icons-vue').Camera
|
|
16
|
+
const ChatDotRound: typeof import('@element-plus/icons-vue').ChatDotRound
|
|
17
|
+
const CircleCheck: typeof import('@element-plus/icons-vue').CircleCheck
|
|
18
|
+
const CircleCheckFilled: typeof import('@element-plus/icons-vue').CircleCheckFilled
|
|
19
|
+
const CircleClose: typeof import('@element-plus/icons-vue').CircleClose
|
|
20
|
+
const CirclePlus: typeof import('@element-plus/icons-vue').CirclePlus
|
|
21
|
+
const Close: typeof import('@element-plus/icons-vue').Close
|
|
22
|
+
const CollectionTag: typeof import('@element-plus/icons-vue').CollectionTag
|
|
23
|
+
const CopyDocument: typeof import('@element-plus/icons-vue').CopyDocument
|
|
24
|
+
const DataAnalysis: typeof import('@element-plus/icons-vue').DataAnalysis
|
|
25
|
+
const Delete: typeof import('@element-plus/icons-vue').Delete
|
|
26
|
+
const Document: typeof import('@element-plus/icons-vue').Document
|
|
27
|
+
const Download: typeof import('@element-plus/icons-vue').Download
|
|
28
|
+
const Edit: typeof import('@element-plus/icons-vue').Edit
|
|
29
|
+
const EditPen: typeof import('@element-plus/icons-vue').EditPen
|
|
30
|
+
const EffectScope: typeof import('vue').EffectScope
|
|
31
|
+
const ElConfigProvider: typeof import('element-plus').ElConfigProvider
|
|
32
|
+
const ElImage: typeof import('element-plus').ElImage
|
|
33
|
+
const ElLoading: typeof import('element-plus').ElLoading
|
|
34
|
+
const ElMessage: typeof import('element-plus').ElMessage
|
|
35
|
+
const ElMessageBox: typeof import('element-plus').ElMessageBox
|
|
36
|
+
const ElNotification: typeof import('element-plus').ElNotification
|
|
37
|
+
const ElTable: typeof import('element-plus').ElTable
|
|
38
|
+
const ElTag: typeof import('element-plus').ElTag
|
|
39
|
+
const ElTooltip: typeof import('element-plus').ElTooltip
|
|
40
|
+
const ElTree: typeof import('element-plus').ElTree
|
|
41
|
+
const Expand: typeof import('@element-plus/icons-vue').Expand
|
|
42
|
+
const Female: typeof import('@element-plus/icons-vue').Female
|
|
43
|
+
const Finished: typeof import('@element-plus/icons-vue').Finished
|
|
44
|
+
const Fold: typeof import('@element-plus/icons-vue').Fold
|
|
45
|
+
const Folder: typeof import('@element-plus/icons-vue').Folder
|
|
46
|
+
const Goods: typeof import('@element-plus/icons-vue').Goods
|
|
47
|
+
const InfoFilled: typeof import('@element-plus/icons-vue').InfoFilled
|
|
48
|
+
const List: typeof import('@element-plus/icons-vue').List
|
|
49
|
+
const Lock: typeof import('@element-plus/icons-vue').Lock
|
|
50
|
+
const Male: typeof import('@element-plus/icons-vue').Male
|
|
51
|
+
const Message: typeof import('@element-plus/icons-vue').Message
|
|
52
|
+
const Moon: typeof import('@element-plus/icons-vue').Moon
|
|
53
|
+
const Operation: typeof import('@element-plus/icons-vue').Operation
|
|
54
|
+
const Phone: typeof import('@element-plus/icons-vue').Phone
|
|
55
|
+
const Plus: typeof import('@element-plus/icons-vue').Plus
|
|
56
|
+
const Position: typeof import('@element-plus/icons-vue').Position
|
|
57
|
+
const Promotion: typeof import('@element-plus/icons-vue').Promotion
|
|
58
|
+
const QuestionFilled: typeof import('@element-plus/icons-vue').QuestionFilled
|
|
59
|
+
const Refresh: typeof import('@element-plus/icons-vue').Refresh
|
|
60
|
+
const RefreshLeft: typeof import('@element-plus/icons-vue').RefreshLeft
|
|
61
|
+
const RefreshRight: typeof import('@element-plus/icons-vue').RefreshRight
|
|
62
|
+
const Search: typeof import('@element-plus/icons-vue').Search
|
|
63
|
+
const Setting: typeof import('@element-plus/icons-vue').Setting
|
|
64
|
+
const Sunny: typeof import('@element-plus/icons-vue').Sunny
|
|
65
|
+
const Switch: typeof import('@element-plus/icons-vue').Switch
|
|
66
|
+
const SwitchButton: typeof import('@element-plus/icons-vue').SwitchButton
|
|
67
|
+
const Tickets: typeof import('@element-plus/icons-vue').Tickets
|
|
68
|
+
const Timer: typeof import('@element-plus/icons-vue').Timer
|
|
69
|
+
const User: typeof import('@element-plus/icons-vue').User
|
|
70
|
+
const UserFilled: typeof import('@element-plus/icons-vue').UserFilled
|
|
71
|
+
const Van: typeof import('@element-plus/icons-vue').Van
|
|
72
|
+
const View: typeof import('@element-plus/icons-vue').View
|
|
73
|
+
const Wallet: typeof import('@element-plus/icons-vue').Wallet
|
|
74
|
+
const WarningFilled: typeof import('@element-plus/icons-vue').WarningFilled
|
|
75
|
+
const computed: typeof import('vue').computed
|
|
76
|
+
const createApp: typeof import('vue').createApp
|
|
77
|
+
const customRef: typeof import('vue').customRef
|
|
78
|
+
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
|
|
79
|
+
const defineComponent: typeof import('vue').defineComponent
|
|
80
|
+
const effectScope: typeof import('vue').effectScope
|
|
81
|
+
const formContextKey: typeof import('element-plus').formContextKey
|
|
82
|
+
const formItemContextKey: typeof import('element-plus').formItemContextKey
|
|
83
|
+
const getCurrentInstance: typeof import('vue').getCurrentInstance
|
|
84
|
+
const getCurrentScope: typeof import('vue').getCurrentScope
|
|
85
|
+
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
|
|
86
|
+
const h: typeof import('vue').h
|
|
87
|
+
const inject: typeof import('vue').inject
|
|
88
|
+
const isProxy: typeof import('vue').isProxy
|
|
89
|
+
const isReactive: typeof import('vue').isReactive
|
|
90
|
+
const isReadonly: typeof import('vue').isReadonly
|
|
91
|
+
const isRef: typeof import('vue').isRef
|
|
92
|
+
const isShallow: typeof import('vue').isShallow
|
|
93
|
+
const markRaw: typeof import('vue').markRaw
|
|
94
|
+
const nextTick: typeof import('vue').nextTick
|
|
95
|
+
const onActivated: typeof import('vue').onActivated
|
|
96
|
+
const onBeforeMount: typeof import('vue').onBeforeMount
|
|
97
|
+
const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave
|
|
98
|
+
const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate
|
|
99
|
+
const onBeforeUnmount: typeof import('vue').onBeforeUnmount
|
|
100
|
+
const onBeforeUpdate: typeof import('vue').onBeforeUpdate
|
|
101
|
+
const onDeactivated: typeof import('vue').onDeactivated
|
|
102
|
+
const onErrorCaptured: typeof import('vue').onErrorCaptured
|
|
103
|
+
const onMounted: typeof import('vue').onMounted
|
|
104
|
+
const onRenderTracked: typeof import('vue').onRenderTracked
|
|
105
|
+
const onRenderTriggered: typeof import('vue').onRenderTriggered
|
|
106
|
+
const onScopeDispose: typeof import('vue').onScopeDispose
|
|
107
|
+
const onServerPrefetch: typeof import('vue').onServerPrefetch
|
|
108
|
+
const onUnmounted: typeof import('vue').onUnmounted
|
|
109
|
+
const onUpdated: typeof import('vue').onUpdated
|
|
110
|
+
const onWatcherCleanup: typeof import('vue').onWatcherCleanup
|
|
111
|
+
const provide: typeof import('vue').provide
|
|
112
|
+
const reactive: typeof import('vue').reactive
|
|
113
|
+
const readonly: typeof import('vue').readonly
|
|
114
|
+
const ref: typeof import('vue').ref
|
|
115
|
+
const resolveComponent: typeof import('vue').resolveComponent
|
|
116
|
+
const shallowReactive: typeof import('vue').shallowReactive
|
|
117
|
+
const shallowReadonly: typeof import('vue').shallowReadonly
|
|
118
|
+
const shallowRef: typeof import('vue').shallowRef
|
|
119
|
+
const toRaw: typeof import('vue').toRaw
|
|
120
|
+
const toRef: typeof import('vue').toRef
|
|
121
|
+
const toRefs: typeof import('vue').toRefs
|
|
122
|
+
const toValue: typeof import('vue').toValue
|
|
123
|
+
const triggerRef: typeof import('vue').triggerRef
|
|
124
|
+
const unref: typeof import('vue').unref
|
|
125
|
+
const useAttrs: typeof import('vue').useAttrs
|
|
126
|
+
const useCssModule: typeof import('vue').useCssModule
|
|
127
|
+
const useCssVars: typeof import('vue').useCssVars
|
|
128
|
+
const useId: typeof import('vue').useId
|
|
129
|
+
const useLink: typeof import('vue-router').useLink
|
|
130
|
+
const useModel: typeof import('vue').useModel
|
|
131
|
+
const useRoute: typeof import('vue-router').useRoute
|
|
132
|
+
const useRouter: typeof import('vue-router').useRouter
|
|
133
|
+
const useSlots: typeof import('vue').useSlots
|
|
134
|
+
const useTemplateRef: typeof import('vue').useTemplateRef
|
|
135
|
+
const watch: typeof import('vue').watch
|
|
136
|
+
const watchEffect: typeof import('vue').watchEffect
|
|
137
|
+
const watchPostEffect: typeof import('vue').watchPostEffect
|
|
138
|
+
const watchSyncEffect: typeof import('vue').watchSyncEffect
|
|
118
139
|
}
|
|
119
140
|
// for type re-export
|
|
120
141
|
declare global {
|
|
121
142
|
// @ts-ignore
|
|
122
|
-
export type {
|
|
123
|
-
|
|
124
|
-
Slot,
|
|
125
|
-
Slots,
|
|
126
|
-
ComponentPublicInstance,
|
|
127
|
-
ComputedRef,
|
|
128
|
-
DirectiveBinding,
|
|
129
|
-
ExtractDefaultPropTypes,
|
|
130
|
-
ExtractPropTypes,
|
|
131
|
-
ExtractPublicPropTypes,
|
|
132
|
-
InjectionKey,
|
|
133
|
-
PropType,
|
|
134
|
-
Ref,
|
|
135
|
-
ShallowRef,
|
|
136
|
-
MaybeRef,
|
|
137
|
-
MaybeRefOrGetter,
|
|
138
|
-
VNode,
|
|
139
|
-
WritableComputedRef
|
|
140
|
-
} from "vue";
|
|
141
|
-
import("vue");
|
|
143
|
+
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
|
144
|
+
import('vue')
|
|
142
145
|
}
|