@liujitcn/kratos-admin-core 0.0.29 → 0.0.32
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 +15 -8
- package/build/auto-imports.json +73 -0
- package/build/plugins.ts +2 -58
- package/dist/package/declarations/src/api/base/{config.d.ts → v1/config.d.ts} +1 -1
- package/dist/package/declarations/src/api/base/{file.d.ts → v1/file.d.ts} +3 -1
- package/dist/package/declarations/src/api/base/{language.d.ts → v1/language.d.ts} +1 -1
- package/dist/package/declarations/src/api/base/{login.d.ts → v1/login.d.ts} +2 -2
- package/dist/package/declarations/src/api/base/v1/mfa.d.ts +26 -0
- package/dist/package/declarations/src/api/base/{oauth.d.ts → v1/oauth.d.ts} +1 -1
- package/dist/package/declarations/src/api/system/{auth.d.ts → admin/v1/auth.d.ts} +2 -2
- package/dist/package/declarations/src/api/system/{base_dict.d.ts → admin/v1/base_dict.d.ts} +1 -1
- package/dist/package/declarations/src/api/system/{base_tenant.d.ts → admin/v1/base_tenant.d.ts} +2 -2
- 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/ProTable/index.vue.d.ts +114 -0
- package/dist/package/declarations/src/components/RichTextPreview/index.vue.d.ts +8 -0
- package/dist/package/declarations/src/components/TreeFilter/index.vue.d.ts +84 -76
- package/dist/package/declarations/src/components/Upload/File.vue.d.ts +1 -1
- package/dist/package/declarations/src/components/Upload/Files.vue.d.ts +1 -1
- package/dist/package/declarations/src/components/Upload/Img.vue.d.ts +2 -2
- package/dist/package/declarations/src/components/Upload/Imgs.vue.d.ts +2 -2
- package/dist/package/declarations/src/components/WangEditor/index.vue.d.ts +2 -0
- package/dist/package/declarations/src/documentTitle.d.ts +2 -0
- package/dist/package/declarations/src/format.d.ts +1 -1
- package/dist/package/declarations/src/index.d.ts +2 -1
- package/dist/package/declarations/src/layouts/components/LockScreen.vue.d.ts +3 -0
- package/dist/package/declarations/src/locales/generated.d.ts +208 -0
- package/dist/package/declarations/src/locales/index.d.ts +2 -0
- package/dist/package/declarations/src/modules/index.d.ts +1 -3
- package/dist/package/declarations/src/request.d.ts +1 -1
- package/dist/package/declarations/src/routers/index.d.ts +1 -1
- package/dist/package/declarations/src/rpc/base/v1/login.d.ts +37 -5
- 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/auth.d.ts +13 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_dict.d.ts +0 -101
- package/dist/package/declarations/src/rpc/system/admin/v1/base_i18n.d.ts +10 -6
- package/dist/package/declarations/src/rpc/system/common/v1/common.d.ts +26 -0
- package/dist/package/declarations/src/security.d.ts +2 -0
- package/dist/package/declarations/src/stores/interface/index.d.ts +19 -1
- package/dist/package/declarations/src/stores/modules/lockScreen.d.ts +20 -0
- package/dist/package/declarations/src/stores/modules/user.d.ts +7 -6
- 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/passwordStrength.d.ts +1 -12
- package/dist/package/declarations/src/utils/request.d.ts +13 -2
- package/dist/package/declarations/src/utils/utils.d.ts +4 -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/{config.ts → v1/config.ts} +2 -2
- package/dist/package/src/api/base/{file.ts → v1/file.ts} +20 -24
- package/dist/package/src/api/base/{language.ts → v1/language.ts} +2 -2
- package/dist/package/src/api/base/{login.ts → v1/login.ts} +3 -3
- package/dist/package/src/api/base/v1/mfa.ts +114 -0
- package/dist/package/src/api/base/{oauth.ts → v1/oauth.ts} +2 -2
- package/dist/package/src/api/system/{auth.ts → admin/v1/auth.ts} +3 -3
- package/dist/package/src/api/system/{base_dict.ts → admin/v1/base_dict.ts} +2 -2
- package/dist/package/src/api/system/{base_tenant.ts → admin/v1/base_tenant.ts} +3 -3
- package/dist/package/src/bootstrap.ts +6 -5
- package/dist/package/src/components/CronExpression/index.vue +4 -3
- package/dist/package/src/components/Dialog/PasswordVerifyDialog.vue +176 -0
- package/dist/package/src/components/Dialog/ProDialog.vue +1 -0
- package/dist/package/src/components/ImportExcel/index.vue +4 -2
- package/dist/package/src/components/Loading/fullScreen.ts +2 -1
- package/dist/package/src/components/MarkdownPreview/index.vue +22 -2
- 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/PasswordStrength/index.vue +1 -1
- 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/components/TableColumn.vue +14 -6
- package/dist/package/src/components/ProTable/index.vue +9 -5
- package/dist/package/src/components/RichTextPreview/index.vue +56 -0
- package/dist/package/src/components/SelectIcon/index.vue +3 -2
- package/dist/package/src/components/Upload/File.vue +2 -2
- package/dist/package/src/components/Upload/Files.vue +2 -2
- package/dist/package/src/components/Upload/Img.vue +7 -5
- package/dist/package/src/components/Upload/Imgs.vue +6 -5
- package/dist/package/src/components/WangEditor/index.vue +10 -7
- package/dist/package/src/config/index.ts +1 -1
- package/dist/package/src/documentTitle.ts +8 -0
- package/dist/package/src/format.ts +1 -1
- package/dist/package/src/index.ts +2 -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 +26 -6
- package/dist/package/src/layouts/components/Header/components/InfoDialog.vue +4 -3
- package/dist/package/src/layouts/components/Header/components/PasswordDialog.vue +4 -3
- package/dist/package/src/layouts/components/Header/components/SearchMenu.vue +16 -8
- package/dist/package/src/layouts/components/LockScreen.vue +573 -0
- package/dist/package/src/layouts/components/Tabs/index.vue +12 -3
- package/dist/package/src/layouts/components/ThemeDrawer/index.scss +4 -4
- 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 +51 -3
- package/dist/package/src/locales/index.ts +16 -12
- package/dist/package/src/locales/ja-JP.json +51 -3
- package/dist/package/src/locales/zh-CN.json +51 -3
- package/dist/package/src/locales/zh-TW.json +51 -3
- package/dist/package/src/modules/index.ts +2 -4
- package/dist/package/src/request.ts +3 -0
- package/dist/package/src/routers/index.ts +15 -14
- package/dist/package/src/rpc/base/v1/login.ts +37 -4
- 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/auth.ts +13 -0
- package/dist/package/src/rpc/system/admin/v1/base_dict.ts +0 -112
- package/dist/package/src/rpc/system/admin/v1/base_i18n.ts +10 -6
- package/dist/package/src/rpc/system/common/v1/common.ts +35 -0
- package/dist/package/src/security.ts +2 -0
- package/dist/package/src/stores/interface/index.ts +20 -1
- package/dist/package/src/stores/modules/auth.ts +1 -1
- package/dist/package/src/stores/modules/config.ts +2 -2
- package/dist/package/src/stores/modules/dict.ts +1 -1
- package/dist/package/src/stores/modules/lockScreen.ts +78 -0
- package/dist/package/src/stores/modules/user.ts +34 -22
- package/dist/package/src/stores/runtime.ts +1 -0
- package/dist/package/src/styles/element.scss +35 -4
- package/dist/package/src/utils/clipboard.ts +22 -0
- package/dist/package/src/utils/passwordCrypto.ts +1 -1
- package/dist/package/src/utils/passwordStrength.ts +7 -24
- package/dist/package/src/utils/request.ts +175 -40
- package/dist/package/src/utils/tenant.ts +1 -1
- package/dist/package/src/utils/utils.ts +25 -6
- package/dist/package/src/utils/webauthn.ts +90 -0
- package/dist/package/src/views/login/components/LoginForm.vue +257 -24
- package/dist/package/src/views/login/index.scss +2 -2
- 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 +4 -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>
|
|
@@ -96,11 +99,12 @@
|
|
|
96
99
|
</el-tooltip>
|
|
97
100
|
</div>
|
|
98
101
|
</div>
|
|
99
|
-
<
|
|
102
|
+
<ProDialog
|
|
100
103
|
v-model="behaviorDialogVisible"
|
|
101
104
|
width="364px"
|
|
102
105
|
top="16vh"
|
|
103
106
|
:show-close="false"
|
|
107
|
+
:show-footer="false"
|
|
104
108
|
append-to-body
|
|
105
109
|
class="behavior-captcha-dialog"
|
|
106
110
|
>
|
|
@@ -127,7 +131,49 @@
|
|
|
127
131
|
:events="rotateCaptchaEvents"
|
|
128
132
|
/>
|
|
129
133
|
</div>
|
|
130
|
-
</
|
|
134
|
+
</ProDialog>
|
|
135
|
+
<ProDialog v-model="mfaDialogVisible" :title="t('core.login.mfa_title')" width="360px" :close-on-click-modal="false">
|
|
136
|
+
<ProForm
|
|
137
|
+
ref="mfaLoginFormRef"
|
|
138
|
+
:model="mfaLoginForm"
|
|
139
|
+
:fields="mfaLoginFormFields"
|
|
140
|
+
:rules="mfaLoginFormRules"
|
|
141
|
+
size="default"
|
|
142
|
+
label-width="auto"
|
|
143
|
+
@keyup.enter.prevent="verifyMfaLogin"
|
|
144
|
+
/>
|
|
145
|
+
<template #footer>
|
|
146
|
+
<el-button @click="mfaDialogVisible = false">{{ t("common.action.cancel") }}</el-button>
|
|
147
|
+
<el-button type="primary" :loading="mfaLoading" @click="verifyMfaLogin">
|
|
148
|
+
{{ mfaMethod === "webauthn" ? t("core.login.mfa_webauthn_action") : t("common.action.confirm") }}
|
|
149
|
+
</el-button>
|
|
150
|
+
</template>
|
|
151
|
+
</ProDialog>
|
|
152
|
+
<ProDialog v-model="mfaSetupDialogVisible" :title="t('core.login.mfa_setup_title')" width="520px" :close-on-click-modal="false">
|
|
153
|
+
<template v-if="mfaSetupMethod !== 'webauthn'">
|
|
154
|
+
<MfaSetupPanel :uri="mfaSetupUri" />
|
|
155
|
+
<ProForm
|
|
156
|
+
ref="mfaSetupFormRef"
|
|
157
|
+
:model="mfaSetupForm"
|
|
158
|
+
:fields="mfaSetupFormFields"
|
|
159
|
+
:rules="mfaSetupFormRules"
|
|
160
|
+
size="default"
|
|
161
|
+
label-width="auto"
|
|
162
|
+
@keyup.enter.prevent="confirmMfaSetup"
|
|
163
|
+
/>
|
|
164
|
+
</template>
|
|
165
|
+
<template #footer>
|
|
166
|
+
<el-button :disabled="mfaLoading" @click="mfaSetupDialogVisible = false">{{ t("common.action.cancel") }}</el-button>
|
|
167
|
+
<el-button type="primary" :loading="mfaLoading" @click="confirmMfaSetup">
|
|
168
|
+
{{ mfaSetupMethod === "webauthn" ? t("core.login.mfa_webauthn_action") : t("common.action.confirm") }}
|
|
169
|
+
</el-button>
|
|
170
|
+
</template>
|
|
171
|
+
</ProDialog>
|
|
172
|
+
<MfaRecoveryCodesDialog
|
|
173
|
+
v-model="recoveryCodesDialogVisible"
|
|
174
|
+
:codes="recoveryCodes"
|
|
175
|
+
@confirm="finishMfaEnrollment"
|
|
176
|
+
/>
|
|
131
177
|
</template>
|
|
132
178
|
|
|
133
179
|
<script setup lang="ts">
|
|
@@ -135,10 +181,16 @@ import { computed, ref, reactive, onMounted, watch } from "vue";
|
|
|
135
181
|
import { useRoute, useRouter } from "vue-router";
|
|
136
182
|
import { HOME_URL } from "../../../config";
|
|
137
183
|
import { getTimeState } from "../../../utils";
|
|
138
|
-
import { defLoginService } from "../../../api/base/login";
|
|
139
|
-
import {
|
|
140
|
-
import {
|
|
141
|
-
import
|
|
184
|
+
import { defLoginService } from "../../../api/base/v1/login";
|
|
185
|
+
import { defMfaService } from "../../../api/base/v1/mfa";
|
|
186
|
+
import { defOauthService } from "../../../api/base/v1/oauth";
|
|
187
|
+
import { createWebAuthnCredential, getWebAuthnAssertion } from "../../../security";
|
|
188
|
+
import ProDialog from "../../../components/Dialog/ProDialog.vue";
|
|
189
|
+
import ProForm from "../../../components/ProForm/index.vue";
|
|
190
|
+
import MfaSetupPanel from "../../../components/Mfa/MfaSetupPanel.vue";
|
|
191
|
+
import MfaRecoveryCodesDialog from "../../../components/Mfa/MfaRecoveryCodesDialog.vue";
|
|
192
|
+
import type { ProFormField, ProFormInstance } from "../../../components/ProForm/interface";
|
|
193
|
+
import { LoginStatus, type LoginRequest, type LoginResponse } from "../../../rpc/base/v1/login";
|
|
142
194
|
import type { OauthProvider } from "../../../rpc/base/v1/oauth";
|
|
143
195
|
import { getOauthProviderIcon, withOauthProviderDisplay, type OauthProviderDisplay } from "../../../utils/oauthProvider";
|
|
144
196
|
import { useUserStore } from "../../../stores/modules/user";
|
|
@@ -147,13 +199,13 @@ import { useTabsStore } from "../../../stores/modules/tabs";
|
|
|
147
199
|
import { useKeepAliveStore } from "../../../stores/modules/keepAlive";
|
|
148
200
|
import { initDynamicRouter } from "../../../routers/modules/dynamicRouter";
|
|
149
201
|
import { isUnmatchedRoute, navigateTo, resolveFrontendRouteURL } from "../../../utils/router";
|
|
150
|
-
import {
|
|
151
|
-
import type { ElForm } from "element-plus";
|
|
202
|
+
import type { ElForm, FormRules } from "element-plus";
|
|
152
203
|
import { PASSWORD_CRYPTO_SCENE, encryptPassword } from "../../../utils/passwordCrypto";
|
|
153
204
|
import { useConfigStore } from "../../../stores/modules/config";
|
|
154
205
|
import { Click as GoCaptchaClick, Rotate as GoCaptchaRotate, Slide as GoCaptchaSlide } from "go-captcha-vue";
|
|
155
206
|
import "go-captcha-vue/dist/style.css";
|
|
156
207
|
import { useLocaleStore } from "../../../locales";
|
|
208
|
+
import { clearPasswordChangeRequired, handlePasswordChangeRequired } from "../../../utils/request";
|
|
157
209
|
|
|
158
210
|
const router = useRouter();
|
|
159
211
|
const route = useRoute();
|
|
@@ -182,6 +234,84 @@ const loginRules = computed(() => ({
|
|
|
182
234
|
const loading = ref(false);
|
|
183
235
|
const oauthLoadingProvider = ref("");
|
|
184
236
|
const oauthTicketLoading = ref(false);
|
|
237
|
+
const mfaDialogVisible = ref(false);
|
|
238
|
+
const mfaLoading = ref(false);
|
|
239
|
+
const mfaChallengeId = ref("");
|
|
240
|
+
const mfaLoginFormRef = ref<ProFormInstance>();
|
|
241
|
+
const mfaLoginForm = reactive({ code: "", recoveryCode: "" });
|
|
242
|
+
const mfaMethod = ref("totp");
|
|
243
|
+
const mfaWebAuthnOptionsJson = ref("");
|
|
244
|
+
const mfaSetupDialogVisible = ref(false);
|
|
245
|
+
const mfaSetupTicket = ref("");
|
|
246
|
+
const mfaSetupUri = ref("");
|
|
247
|
+
const mfaSetupFormRef = ref<ProFormInstance>();
|
|
248
|
+
const mfaSetupForm = reactive({ code: "" });
|
|
249
|
+
const recoveryCodesDialogVisible = ref(false);
|
|
250
|
+
const recoveryCodes = ref<string[]>([]);
|
|
251
|
+
const mfaSetupMethod = ref("totp");
|
|
252
|
+
const mfaSetupWebAuthnOptionsJson = ref("");
|
|
253
|
+
|
|
254
|
+
/** 登录阶段 MFA 输入字段,验证码和恢复码至少填写其一。 */
|
|
255
|
+
const mfaLoginFormFields = computed<ProFormField[]>(() => {
|
|
256
|
+
const recoveryCodeField: ProFormField = {
|
|
257
|
+
prop: "recoveryCode",
|
|
258
|
+
label: t("core.login.mfa_recovery_code"),
|
|
259
|
+
component: "input",
|
|
260
|
+
props: {
|
|
261
|
+
autocomplete: "one-time-code",
|
|
262
|
+
placeholder: t("core.login.mfa_recovery_code")
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
if (mfaMethod.value === "webauthn") return [recoveryCodeField];
|
|
266
|
+
return [
|
|
267
|
+
{
|
|
268
|
+
prop: "code",
|
|
269
|
+
label: t("core.login.mfa_code"),
|
|
270
|
+
component: "input",
|
|
271
|
+
props: {
|
|
272
|
+
autocomplete: "one-time-code",
|
|
273
|
+
inputmode: "numeric",
|
|
274
|
+
maxlength: 8,
|
|
275
|
+
placeholder: t("core.login.mfa_code")
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
recoveryCodeField
|
|
279
|
+
];
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const mfaLoginFormRules = computed<FormRules>(() => {
|
|
283
|
+
if (mfaMethod.value === "webauthn") return {};
|
|
284
|
+
const factorRule = {
|
|
285
|
+
validator: (_rule: unknown, value: string, callback: (error?: Error) => void) => {
|
|
286
|
+
if (value || mfaLoginForm.code || mfaLoginForm.recoveryCode) {
|
|
287
|
+
callback();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
callback(new Error(t("core.login.mfa_code")));
|
|
291
|
+
},
|
|
292
|
+
trigger: "blur"
|
|
293
|
+
};
|
|
294
|
+
return { code: [factorRule], recoveryCode: [factorRule] };
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
/** 强制绑定 MFA 的 TOTP 输入字段。 */
|
|
298
|
+
const mfaSetupFormFields = computed<ProFormField[]>(() => [
|
|
299
|
+
{
|
|
300
|
+
prop: "code",
|
|
301
|
+
label: t("core.login.mfa_code"),
|
|
302
|
+
component: "input",
|
|
303
|
+
props: {
|
|
304
|
+
autocomplete: "one-time-code",
|
|
305
|
+
inputmode: "numeric",
|
|
306
|
+
maxlength: 8,
|
|
307
|
+
placeholder: t("core.login.mfa_code")
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
]);
|
|
311
|
+
|
|
312
|
+
const mfaSetupFormRules = computed<FormRules>(() => ({
|
|
313
|
+
code: [{ required: true, message: t("core.login.mfa_code"), trigger: "blur" }]
|
|
314
|
+
}));
|
|
185
315
|
|
|
186
316
|
/** 登录页三方登录展示项。 */
|
|
187
317
|
type LoginOauthProvider = OauthProvider & OauthProviderDisplay;
|
|
@@ -412,12 +542,14 @@ const handleOauthLogin = async (provider: LoginOauthProvider) => {
|
|
|
412
542
|
};
|
|
413
543
|
|
|
414
544
|
/** 完成登录后的用户信息、字典与动态路由初始化。 */
|
|
415
|
-
const finishLogin = async () => {
|
|
545
|
+
const finishLogin = async (mustChangePassword = false) => {
|
|
416
546
|
// 1.获取用户信息
|
|
417
547
|
await userStore.getUserInfo();
|
|
418
548
|
|
|
419
|
-
|
|
420
|
-
|
|
549
|
+
if (!mustChangePassword) {
|
|
550
|
+
// 2.预加载字典缓存,避免页面首次渲染时字典值为空
|
|
551
|
+
await dictStore.loadDictionaries();
|
|
552
|
+
}
|
|
421
553
|
|
|
422
554
|
// 3.添加动态路由
|
|
423
555
|
await initDynamicRouter();
|
|
@@ -426,7 +558,7 @@ const finishLogin = async () => {
|
|
|
426
558
|
tabsStore.setTabs([]);
|
|
427
559
|
keepAliveStore.setKeepAliveName([]);
|
|
428
560
|
|
|
429
|
-
// 5
|
|
561
|
+
// 5.无论是否需要改密,都回到登录前页面;强制改密状态由布局中的全局弹窗展示。
|
|
430
562
|
// 统一走动态路由感知跳转,避免首次登录后目标页面尚未完成挂载时直接进入 404。
|
|
431
563
|
await navigateTo(router, getLoginRedirectPath());
|
|
432
564
|
ElNotification({
|
|
@@ -437,6 +569,101 @@ const finishLogin = async () => {
|
|
|
437
569
|
});
|
|
438
570
|
};
|
|
439
571
|
|
|
572
|
+
/** 处理密码或 OAuth 返回的 MFA 登录状态。 */
|
|
573
|
+
const handleLoginResponse = async (result: LoginResponse) => {
|
|
574
|
+
if (result.status === LoginStatus.LOGIN_STATUS_MFA_REQUIRED) {
|
|
575
|
+
mfaChallengeId.value = result.mfa_challenge_id;
|
|
576
|
+
mfaMethod.value = result.mfa_method || "totp";
|
|
577
|
+
mfaWebAuthnOptionsJson.value = result.mfa_webauthn_options_json || "";
|
|
578
|
+
mfaLoginForm.code = "";
|
|
579
|
+
mfaLoginForm.recoveryCode = "";
|
|
580
|
+
mfaDialogVisible.value = true;
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
if (result.status === LoginStatus.LOGIN_STATUS_MFA_ENROLLMENT_REQUIRED) {
|
|
584
|
+
mfaSetupTicket.value = result.mfa_setup_ticket;
|
|
585
|
+
mfaSetupMethod.value = result.mfa_method || "totp";
|
|
586
|
+
await beginMfaSetup(result.mfa_setup_ticket);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
const mustChangePassword = result.status === LoginStatus.LOGIN_STATUS_PASSWORD_CHANGE_REQUIRED;
|
|
590
|
+
if (mustChangePassword) {
|
|
591
|
+
handlePasswordChangeRequired(t("core.layout.password_change_required"));
|
|
592
|
+
} else {
|
|
593
|
+
clearPasswordChangeRequired();
|
|
594
|
+
}
|
|
595
|
+
userStore.updateTokenAuth(result.access_token, result.token_type ?? "", result.expires_in);
|
|
596
|
+
await finishLogin(mustChangePassword);
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
/** 校验登录阶段 MFA 并完成登录。 */
|
|
600
|
+
const verifyMfaLogin = async () => {
|
|
601
|
+
if (mfaLoading.value || !mfaChallengeId.value) return;
|
|
602
|
+
if (mfaMethod.value !== "webauthn" && !(await mfaLoginFormRef.value?.validate())) return;
|
|
603
|
+
mfaLoading.value = true;
|
|
604
|
+
try {
|
|
605
|
+
const useRecoveryCode = Boolean(mfaLoginForm.recoveryCode);
|
|
606
|
+
const webauthnResponseJson =
|
|
607
|
+
mfaMethod.value === "webauthn" && !useRecoveryCode ? await getWebAuthnAssertion(mfaWebAuthnOptionsJson.value) : "";
|
|
608
|
+
if (mfaMethod.value !== "webauthn" && !mfaLoginForm.code && !mfaLoginForm.recoveryCode) return;
|
|
609
|
+
const result = await defMfaService.VerifyMfa({
|
|
610
|
+
challenge_id: mfaChallengeId.value,
|
|
611
|
+
code: useRecoveryCode ? "" : mfaLoginForm.code,
|
|
612
|
+
recovery_code: mfaLoginForm.recoveryCode,
|
|
613
|
+
webauthn_response_json: webauthnResponseJson
|
|
614
|
+
});
|
|
615
|
+
mfaDialogVisible.value = false;
|
|
616
|
+
await handleLoginResponse(result);
|
|
617
|
+
} finally {
|
|
618
|
+
mfaLoading.value = false;
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
/** 开始强制绑定 MFA。 */
|
|
623
|
+
const beginMfaSetup = async (setupTicket: string) => {
|
|
624
|
+
mfaLoading.value = true;
|
|
625
|
+
try {
|
|
626
|
+
const result = await defMfaService.BeginMfaEnrollment({ setup_ticket: setupTicket });
|
|
627
|
+
mfaSetupTicket.value = result.setup_ticket;
|
|
628
|
+
mfaSetupUri.value = result.otpauth_uri;
|
|
629
|
+
mfaSetupMethod.value = result.method || "totp";
|
|
630
|
+
mfaSetupWebAuthnOptionsJson.value = result.webauthn_options_json || "";
|
|
631
|
+
mfaSetupForm.code = "";
|
|
632
|
+
mfaSetupDialogVisible.value = true;
|
|
633
|
+
} finally {
|
|
634
|
+
mfaLoading.value = false;
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
/** 关闭恢复码弹窗并结束强制绑定登录流程。 */
|
|
639
|
+
const finishMfaEnrollment = async () => {
|
|
640
|
+
if (!recoveryCodes.value.length) return;
|
|
641
|
+
recoveryCodes.value = [];
|
|
642
|
+
ElMessage.success(t("core.login.mfa_setup_success"));
|
|
643
|
+
await loadPageCaptcha();
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
/** 确认强制绑定 MFA 并要求用户重新登录。 */
|
|
647
|
+
const confirmMfaSetup = async () => {
|
|
648
|
+
if (mfaLoading.value || !mfaSetupTicket.value) return;
|
|
649
|
+
if (mfaSetupMethod.value !== "webauthn" && !(await mfaSetupFormRef.value?.validate())) return;
|
|
650
|
+
mfaLoading.value = true;
|
|
651
|
+
try {
|
|
652
|
+
const webauthnResponseJson =
|
|
653
|
+
mfaSetupMethod.value === "webauthn" ? await createWebAuthnCredential(mfaSetupWebAuthnOptionsJson.value) : "";
|
|
654
|
+
const result = await defMfaService.ConfirmMfaEnrollment({
|
|
655
|
+
setup_ticket: mfaSetupTicket.value,
|
|
656
|
+
code: mfaSetupForm.code,
|
|
657
|
+
webauthn_response_json: webauthnResponseJson
|
|
658
|
+
});
|
|
659
|
+
mfaSetupDialogVisible.value = false;
|
|
660
|
+
recoveryCodes.value = result.recovery_codes;
|
|
661
|
+
recoveryCodesDialogVisible.value = true;
|
|
662
|
+
} finally {
|
|
663
|
+
mfaLoading.value = false;
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
|
|
440
667
|
/** 消费 OAuth 回调携带的一次性票据。 */
|
|
441
668
|
const consumeOauthTicket = async () => {
|
|
442
669
|
const oauthError = route.query.oauth_error;
|
|
@@ -452,8 +679,7 @@ const consumeOauthTicket = async () => {
|
|
|
452
679
|
oauthTicketLoading.value = true;
|
|
453
680
|
try {
|
|
454
681
|
const result = await defOauthService.ExchangeOauthTicket({ ticket });
|
|
455
|
-
|
|
456
|
-
await finishLogin();
|
|
682
|
+
await handleLoginResponse(result);
|
|
457
683
|
} finally {
|
|
458
684
|
oauthTicketLoading.value = false;
|
|
459
685
|
}
|
|
@@ -537,10 +763,9 @@ const submitLogin = async (captchaToken: string) => {
|
|
|
537
763
|
captcha_code: captchaToken,
|
|
538
764
|
captcha_id: loginForm.captcha_id
|
|
539
765
|
};
|
|
540
|
-
// 1
|
|
541
|
-
await userStore.login(loginRequest);
|
|
542
|
-
|
|
543
|
-
await finishLogin();
|
|
766
|
+
// 1.执行登录接口,挑战态不保存令牌,等待 MFA 完成。
|
|
767
|
+
const result = await userStore.login(loginRequest);
|
|
768
|
+
await handleLoginResponse(result);
|
|
544
769
|
} catch (_error) {
|
|
545
770
|
await loadPageCaptcha();
|
|
546
771
|
} finally {
|
|
@@ -638,7 +863,7 @@ watch(
|
|
|
638
863
|
height: 40px;
|
|
639
864
|
cursor: pointer;
|
|
640
865
|
object-fit: contain;
|
|
641
|
-
border-radius:
|
|
866
|
+
border-radius: var(--admin-page-radius);
|
|
642
867
|
}
|
|
643
868
|
.behavior-captcha-body {
|
|
644
869
|
min-height: 0;
|
|
@@ -748,15 +973,19 @@ watch(
|
|
|
748
973
|
--go-captcha-theme-dot-color-color: #ffffff;
|
|
749
974
|
--go-captcha-theme-dot-bg-color: color-mix(in srgb, var(--el-color-primary) 68%, transparent);
|
|
750
975
|
--go-captcha-theme-dot-border-color: var(--el-bg-color);
|
|
751
|
-
border-radius:
|
|
976
|
+
border-radius: var(--admin-page-radius);
|
|
752
977
|
box-shadow: rgb(0 0 0 / 10%) 0 2px 10px 2px;
|
|
753
978
|
}
|
|
754
979
|
|
|
980
|
+
:global(.behavior-captcha-dialog .go-captcha.gc-theme) {
|
|
981
|
+
border-radius: var(--admin-page-radius);
|
|
982
|
+
}
|
|
983
|
+
|
|
755
984
|
:global(.behavior-captcha-dialog .el-dialog__header) {
|
|
756
985
|
display: none;
|
|
757
986
|
}
|
|
758
987
|
|
|
759
|
-
:global(.behavior-captcha-dialog .el-dialog__body) {
|
|
988
|
+
:global(.behavior-captcha-dialog.pro-dialog .el-dialog__body) {
|
|
760
989
|
padding: 10px 12px 12px;
|
|
761
990
|
}
|
|
762
991
|
|
|
@@ -778,7 +1007,11 @@ watch(
|
|
|
778
1007
|
|
|
779
1008
|
:global(.behavior-captcha-dialog .go-captcha .gc-body) {
|
|
780
1009
|
margin-top: 0;
|
|
781
|
-
border-radius:
|
|
1010
|
+
border-radius: var(--admin-page-radius);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
:global(.behavior-captcha-dialog .go-captcha .gc-button-block button) {
|
|
1014
|
+
border-radius: var(--admin-page-radius);
|
|
782
1015
|
}
|
|
783
1016
|
|
|
784
1017
|
:global(.behavior-captcha-dialog .go-captcha .gc-footer) {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
height: 94%;
|
|
16
16
|
padding: 0 50px;
|
|
17
17
|
background-color: rgb(255 255 255 / 80%);
|
|
18
|
-
border-radius:
|
|
18
|
+
border-radius: var(--admin-page-radius);
|
|
19
19
|
.dark {
|
|
20
20
|
position: absolute;
|
|
21
21
|
top: 13px;
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
max-width: 100%;
|
|
42
42
|
padding: 50px 40px 45px;
|
|
43
43
|
background-color: var(--el-bg-color);
|
|
44
|
-
border-radius:
|
|
44
|
+
border-radius: var(--admin-page-radius);
|
|
45
45
|
box-shadow: rgb(0 0 0 / 10%) 0 2px 10px 2px;
|
|
46
46
|
.login-logo {
|
|
47
47
|
display: flex;
|
|
@@ -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);
|