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 './app'
|
||||||
export * from './chat'
|
export * from './chat'
|
||||||
export * from './user'
|
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