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"
|
# if use a sub directory, it must be end with "/", like "/admin/" but not "/admin"
|
||||||
VITE_BASE_URL=/
|
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
|
# the prefix of the icon name
|
||||||
VITE_ICON_PREFIX=icon
|
VITE_ICON_PREFIX=icon
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const totpCode = ref('');
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const verifying = ref(false);
|
const verifying = ref(false);
|
||||||
const disabling = ref(false);
|
const disabling = ref(false);
|
||||||
|
const { VITE_APP_TITLE } = import.meta.env;
|
||||||
|
|
||||||
function generateQRCode(url: string) {
|
function generateQRCode(url: string) {
|
||||||
return `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(url)}`;
|
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(
|
const { data } = await safeClient(
|
||||||
authClient.twoFactor.enable({
|
authClient.twoFactor.enable({
|
||||||
password: props.password,
|
password: props.password,
|
||||||
issuer: 'my-app-name'
|
issuer: VITE_APP_TITLE || 'financial-admin'
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, h } from 'vue';
|
import { computed, h, ref } from 'vue';
|
||||||
import type { VNode } 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 { useAuthStore } from '@/store/modules/auth';
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
import { useSvgIcon } from '@/hooks/common/icon';
|
import { useSvgIcon } from '@/hooks/common/icon';
|
||||||
@@ -64,21 +64,35 @@ function logout() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
function googleAuth() {
|
function googleAuth() {
|
||||||
const topt = window.prompt('请输入账号登录密码:');
|
const topt = ref('');
|
||||||
if (!topt) {
|
dialog.create({
|
||||||
window.$message?.error('请输入密码');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const d = dialog.create({
|
|
||||||
title: '谷歌验证',
|
title: '谷歌验证',
|
||||||
content: () =>
|
content: () =>
|
||||||
h(GoogleAuth, {
|
h(NInput, {
|
||||||
password: topt,
|
type: 'password',
|
||||||
onSuccess: () => {
|
placeholder: '请输入账号登录密码',
|
||||||
d.destroy();
|
onUpdateValue: (value: string) => {
|
||||||
window.$message?.success('二步验证已开启');
|
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 { VITE_SERVICE_BASE_URL } = import.meta.env;
|
||||||
|
|
||||||
|
const baseURL = import.meta.env.DEV ? window.location.origin : VITE_SERVICE_BASE_URL;
|
||||||
|
|
||||||
export const authClient = createAuthClient({
|
export const authClient = createAuthClient({
|
||||||
baseURL: VITE_SERVICE_BASE_URL,
|
baseURL,
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
auth: {
|
auth: {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { computed, reactive, ref } from 'vue';
|
import { computed, h, reactive, ref } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
import { NInputOtp } from 'naive-ui';
|
||||||
import { useLoading } from '@sa/hooks';
|
import { useLoading } from '@sa/hooks';
|
||||||
import { fetchLogin } from '@/service/api';
|
import { authClient, fetchLogin, safeClient } from '@/service/api';
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
import { localStg } from '@/utils/storage';
|
import { localStg } from '@/utils/storage';
|
||||||
import { SetupStoreId } from '@/enum';
|
import { SetupStoreId } from '@/enum';
|
||||||
@@ -102,24 +103,40 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
|||||||
|
|
||||||
const res = await fetchLogin(userName, password);
|
const res = await fetchLogin(userName, password);
|
||||||
if (!res.error) {
|
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) {
|
if (data.value) {
|
||||||
// Check if the tab needs to be cleared
|
saveLogin(data.value, redirect);
|
||||||
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
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
saveLogin(res.data, redirect);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
resetStore();
|
resetStore();
|
||||||
@@ -128,6 +145,28 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
|||||||
endLoading();
|
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) {
|
async function loginByToken(data: Api.Auth.LoginToken) {
|
||||||
// 1. stored in the localStorage, the later requests need it in headers
|
// 1. stored in the localStorage, the later requests need it in headers
|
||||||
localStg.set('token', data.token);
|
localStg.set('token', data.token);
|
||||||
|
|||||||
@@ -35,7 +35,13 @@ export default defineConfig(configEnv => {
|
|||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
port: 9527,
|
port: 9527,
|
||||||
open: true,
|
open: true,
|
||||||
proxy: createViteProxy(viteEnv, enableProxy)
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: viteEnv.VITE_SERVICE_BASE_URL,
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
port: 9725
|
port: 9725
|
||||||
|
|||||||
Reference in New Issue
Block a user