feat: 更新应用标题和描述,调整谷歌验证和登录流程以支持二步验证
This commit is contained in:
4
.env
4
.env
@@ -2,9 +2,9 @@
|
||||
# if use a sub directory, it must be end with "/", like "/admin/" but not "/admin"
|
||||
VITE_BASE_URL=/
|
||||
|
||||
VITE_APP_TITLE=RiwsanAdmin
|
||||
VITE_APP_TITLE=Financial
|
||||
|
||||
VITE_APP_DESC=RiwsanAdmin is trade palatform admin system
|
||||
VITE_APP_DESC=Financial is trade palatform admin system
|
||||
|
||||
# the prefix of the icon name
|
||||
VITE_ICON_PREFIX=icon
|
||||
|
||||
@@ -20,6 +20,7 @@ const totpCode = ref('');
|
||||
const loading = ref(false);
|
||||
const verifying = ref(false);
|
||||
const disabling = ref(false);
|
||||
const { VITE_APP_TITLE } = import.meta.env;
|
||||
|
||||
function generateQRCode(url: string) {
|
||||
return `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(url)}`;
|
||||
@@ -35,7 +36,7 @@ async function enableTwoFactor() {
|
||||
const { data } = await safeClient(
|
||||
authClient.twoFactor.enable({
|
||||
password: props.password,
|
||||
issuer: 'my-app-name'
|
||||
issuer: VITE_APP_TITLE || 'financial-admin'
|
||||
})
|
||||
);
|
||||
loading.value = false;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, h } from 'vue';
|
||||
import { computed, h, ref } from 'vue';
|
||||
import type { VNode } from 'vue';
|
||||
import { useDialog } from 'naive-ui';
|
||||
import { NInput, useDialog } from 'naive-ui';
|
||||
import { useAuthStore } from '@/store/modules/auth';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import { useSvgIcon } from '@/hooks/common/icon';
|
||||
@@ -64,21 +64,35 @@ function logout() {
|
||||
});
|
||||
}
|
||||
function googleAuth() {
|
||||
const topt = window.prompt('请输入账号登录密码:');
|
||||
if (!topt) {
|
||||
window.$message?.error('请输入密码');
|
||||
return;
|
||||
}
|
||||
const d = dialog.create({
|
||||
const topt = ref('');
|
||||
dialog.create({
|
||||
title: '谷歌验证',
|
||||
content: () =>
|
||||
h(GoogleAuth, {
|
||||
password: topt,
|
||||
onSuccess: () => {
|
||||
d.destroy();
|
||||
window.$message?.success('二步验证已开启');
|
||||
h(NInput, {
|
||||
type: 'password',
|
||||
placeholder: '请输入账号登录密码',
|
||||
onUpdateValue: (value: string) => {
|
||||
topt.value = value;
|
||||
}
|
||||
})
|
||||
}),
|
||||
positiveText: '下一步',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
if (!topt.value) {
|
||||
window.$message?.error('请输入密码');
|
||||
return false;
|
||||
}
|
||||
const d = dialog.create({
|
||||
title: '谷歌验证',
|
||||
content: () =>
|
||||
h(GoogleAuth, {
|
||||
password: topt.value,
|
||||
onSuccess: () => {
|
||||
d.destroy();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@ import { request } from '../request';
|
||||
|
||||
const { VITE_SERVICE_BASE_URL } = import.meta.env;
|
||||
|
||||
const baseURL = import.meta.env.DEV ? window.location.origin : VITE_SERVICE_BASE_URL;
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
baseURL: VITE_SERVICE_BASE_URL,
|
||||
baseURL,
|
||||
fetchOptions: {
|
||||
credentials: 'include',
|
||||
auth: {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { computed, h, reactive, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { defineStore } from 'pinia';
|
||||
import { NInputOtp } from 'naive-ui';
|
||||
import { useLoading } from '@sa/hooks';
|
||||
import { fetchLogin } from '@/service/api';
|
||||
import { authClient, fetchLogin, safeClient } from '@/service/api';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import { localStg } from '@/utils/storage';
|
||||
import { SetupStoreId } from '@/enum';
|
||||
@@ -102,24 +103,40 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||
|
||||
const res = await fetchLogin(userName, password);
|
||||
if (!res.error) {
|
||||
const pass = await loginByToken(res.data);
|
||||
if ('twoFactorRedirect' in res.data && res.data.twoFactorRedirect) {
|
||||
const topt = ref('');
|
||||
window.$dialog?.create({
|
||||
title: '谷歌验证',
|
||||
content: () =>
|
||||
h(NInputOtp, {
|
||||
style: { margin: '30px 0' },
|
||||
size: 'large',
|
||||
allowInput: value => !value || /^\d+$/.test(value),
|
||||
onUpdateValue: value => {
|
||||
topt.value = value.join('');
|
||||
}
|
||||
}),
|
||||
positiveText: '下一步',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
if (!topt.value) {
|
||||
window.$message?.error('请输入密码');
|
||||
} else {
|
||||
const { data } = await safeClient(
|
||||
authClient.twoFactor.verifyTotp({
|
||||
code: topt.value, // required
|
||||
trustDevice: false // 管理员登录不建议信任设备,以提高安全性
|
||||
})
|
||||
);
|
||||
|
||||
if (pass) {
|
||||
// Check if the tab needs to be cleared
|
||||
const isClear = checkTabClear();
|
||||
let needRedirect = redirect;
|
||||
|
||||
if (isClear) {
|
||||
// If the tab needs to be cleared,it means we don't need to redirect.
|
||||
needRedirect = false;
|
||||
}
|
||||
await redirectFromLogin(needRedirect);
|
||||
|
||||
window.$notification?.success({
|
||||
title: $t('page.login.common.loginSuccess'),
|
||||
content: $t('page.login.common.welcomeBack', { userName: userInfo.name }),
|
||||
duration: 4500
|
||||
if (data.value) {
|
||||
saveLogin(data.value, redirect);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
saveLogin(res.data, redirect);
|
||||
}
|
||||
} else {
|
||||
resetStore();
|
||||
@@ -128,6 +145,28 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||
endLoading();
|
||||
}
|
||||
|
||||
async function saveLogin(data: Api.Auth.LoginToken, redirect = true) {
|
||||
const pass = await loginByToken(data);
|
||||
|
||||
if (pass) {
|
||||
// Check if the tab needs to be cleared
|
||||
const isClear = checkTabClear();
|
||||
let needRedirect = redirect;
|
||||
|
||||
if (isClear) {
|
||||
// If the tab needs to be cleared,it means we don't need to redirect.
|
||||
needRedirect = false;
|
||||
}
|
||||
await redirectFromLogin(needRedirect);
|
||||
|
||||
window.$notification?.success({
|
||||
title: $t('page.login.common.loginSuccess'),
|
||||
content: $t('page.login.common.welcomeBack', { userName: userInfo.name }),
|
||||
duration: 4500
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function loginByToken(data: Api.Auth.LoginToken) {
|
||||
// 1. stored in the localStorage, the later requests need it in headers
|
||||
localStg.set('token', data.token);
|
||||
|
||||
@@ -35,7 +35,13 @@ export default defineConfig(configEnv => {
|
||||
host: '0.0.0.0',
|
||||
port: 9527,
|
||||
open: true,
|
||||
proxy: createViteProxy(viteEnv, enableProxy)
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: viteEnv.VITE_SERVICE_BASE_URL,
|
||||
changeOrigin: true,
|
||||
ws: true
|
||||
}
|
||||
}
|
||||
},
|
||||
preview: {
|
||||
port: 9725
|
||||
|
||||
Reference in New Issue
Block a user