chore: version 2.10.0
* feat: 权限验证功能 * chore: v2.10.0 * feat: 500 服务异常页面 * feat: 只有结束才会滚动到底部 * chore: 修改 CHANGELOG * chore: 不存在时输出默认报错main
parent
a2ffa3cb3a
commit
ffd4da91cf
@ -0,0 +1,19 @@
|
||||
const auth = async (req, res, next) => {
|
||||
const AUTH_SECRET_KEY = process.env.AUTH_SECRET_KEY
|
||||
if (typeof AUTH_SECRET_KEY === 'string' && AUTH_SECRET_KEY.length > 0) {
|
||||
try {
|
||||
const Authorization = req.header('Authorization')
|
||||
if (!Authorization || Authorization.replace('Bearer ', '').trim() !== AUTH_SECRET_KEY.trim())
|
||||
throw new Error('Error: 无访问权限 | No access rights')
|
||||
next()
|
||||
}
|
||||
catch (error) {
|
||||
res.send({ status: 'Unauthorized', message: error.message ?? 'Please authenticate.', data: null })
|
||||
}
|
||||
}
|
||||
else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
export { auth }
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 19 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,25 @@
|
||||
import type { Router } from 'vue-router'
|
||||
import { useAuthStoreWithout } from '@/store/modules/auth'
|
||||
|
||||
export function setupPageGuard(router: Router) {
|
||||
router.beforeEach(async (from, to, next) => {
|
||||
const authStore = useAuthStoreWithout()
|
||||
if (!authStore.session) {
|
||||
try {
|
||||
const data = await authStore.getSession()
|
||||
if (String(data.auth) === 'false' && authStore.token)
|
||||
authStore.removeToken()
|
||||
next()
|
||||
}
|
||||
catch (error) {
|
||||
if (from.path !== '/500')
|
||||
next({ name: '500' })
|
||||
else
|
||||
next()
|
||||
}
|
||||
}
|
||||
else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import { ss } from '@/utils/storage'
|
||||
|
||||
const LOCAL_NAME = 'SECRET_TOKEN'
|
||||
|
||||
export function getToken() {
|
||||
return ss.get(LOCAL_NAME)
|
||||
}
|
||||
|
||||
export function setToken(token: string) {
|
||||
return ss.set(LOCAL_NAME, token)
|
||||
}
|
||||
|
||||
export function removeToken() {
|
||||
return ss.remove(LOCAL_NAME)
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { getToken, removeToken, setToken } from './helper'
|
||||
import { store } from '@/store'
|
||||
import { fetchSession } from '@/api'
|
||||
|
||||
export interface AuthState {
|
||||
token: string | undefined
|
||||
session: { auth: boolean } | null
|
||||
}
|
||||
|
||||
export const useAuthStore = defineStore('auth-store', {
|
||||
state: (): AuthState => ({
|
||||
token: getToken(),
|
||||
session: null,
|
||||
}),
|
||||
|
||||
actions: {
|
||||
async getSession() {
|
||||
try {
|
||||
const { data } = await fetchSession<{ auth: boolean }>()
|
||||
this.session = { ...data }
|
||||
return Promise.resolve(data)
|
||||
}
|
||||
catch (error) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
},
|
||||
|
||||
setToken(token: string) {
|
||||
this.token = token
|
||||
setToken(token)
|
||||
},
|
||||
|
||||
removeToken() {
|
||||
this.token = undefined
|
||||
removeToken()
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export function useAuthStoreWithout() {
|
||||
return useAuthStore(store)
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
export * from './app'
|
||||
export * from './chat'
|
||||
export * from './user'
|
||||
export * from './auth'
|
||||
|
@ -0,0 +1,81 @@
|
||||
<script setup lang='ts'>
|
||||
import { computed, ref } from 'vue'
|
||||
import { NButton, NInput, NModal, useMessage } from 'naive-ui'
|
||||
import { fetchVerify } from '@/api'
|
||||
import { useAuthStore } from '@/store'
|
||||
import Icon403 from '@/icons/403.vue'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const ms = useMessage()
|
||||
|
||||
const loading = ref(false)
|
||||
const token = ref('')
|
||||
|
||||
const disabled = computed(() => !token.value.trim() || loading.value)
|
||||
|
||||
async function handleVerify() {
|
||||
const secretKey = token.value.trim()
|
||||
|
||||
if (!secretKey)
|
||||
return
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
await fetchVerify(secretKey)
|
||||
authStore.setToken(secretKey)
|
||||
ms.success('success')
|
||||
window.location.reload()
|
||||
}
|
||||
catch (error: any) {
|
||||
ms.error(error.message ?? 'error')
|
||||
authStore.removeToken()
|
||||
token.value = ''
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handlePress(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter' && !event.shiftKey) {
|
||||
event.preventDefault()
|
||||
handleVerify()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal :show="visible" style="width: 90%; max-width: 640px">
|
||||
<div class="p-10 bg-white rounded dark:bg-slate-800">
|
||||
<div class="space-y-4">
|
||||
<header class="space-y-2">
|
||||
<h2 class="text-2xl font-bold text-center text-slate-800 dark:text-neutral-200">
|
||||
403
|
||||
</h2>
|
||||
<p class="text-base text-center text-slate-500 dark:text-slate-500">
|
||||
{{ $t('common.unauthorizedTips') }}
|
||||
</p>
|
||||
<Icon403 class="w-[200px] m-auto" />
|
||||
</header>
|
||||
<NInput v-model:value="token" type="text" placeholder="" @keypress="handlePress" />
|
||||
|
||||
<NButton
|
||||
block
|
||||
type="primary"
|
||||
:disabled="disabled"
|
||||
:loading="loading"
|
||||
@click="handleVerify"
|
||||
>
|
||||
{{ $t('common.verify') }}
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
</NModal>
|
||||
</template>
|
@ -1,34 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { NButton } from 'naive-ui'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
function goHome() {
|
||||
router.push('/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-full">
|
||||
<div class="px-4 m-auto space-y-4 text-center max-[400px]">
|
||||
<h1 class="text-4xl text-slate-800 dark:text-neutral-200">
|
||||
No permission
|
||||
</h1>
|
||||
<p class="text-base text-slate-500 dark:text-neutral-400">
|
||||
The page you're trying access has restricted access.
|
||||
Please refer to your system administrator
|
||||
</p>
|
||||
<div class="flex items-center justify-center text-center">
|
||||
<div class="w-[300px]">
|
||||
<div class="w-[300px]">
|
||||
<img src="../../../icons/403.svg" alt="404">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NButton type="primary" @click="goHome">
|
||||
Go to Home
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -0,0 +1,32 @@
|
||||
<script lang="ts" setup>
|
||||
import { NButton } from 'naive-ui'
|
||||
import { useRouter } from 'vue-router'
|
||||
import Icon500 from '@/icons/500.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
function goHome() {
|
||||
router.push('/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-full dark:bg-neutral-800">
|
||||
<div class="px-4 m-auto space-y-4 text-center max-[400px]">
|
||||
<header class="space-y-2">
|
||||
<h2 class="text-2xl font-bold text-center text-slate-800 dark:text-neutral-200">
|
||||
500
|
||||
</h2>
|
||||
<p class="text-base text-center text-slate-500 dark:text-slate-500">
|
||||
Server error
|
||||
</p>
|
||||
<div class="flex items-center justify-center text-center">
|
||||
<Icon500 class="w-[300px]" />
|
||||
</div>
|
||||
</header>
|
||||
<NButton type="primary" @click="goHome">
|
||||
Go to Home
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
Loading…
Reference in New Issue