feat: auth plugin
This commit is contained in:
108
layouts/default.vue
Normal file
108
layouts/default.vue
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<script setup>
|
||||||
|
const { $i18n } = useNuxtApp()
|
||||||
|
const runtimeConfig = useRuntimeConfig()
|
||||||
|
const colorMode = useColorMode()
|
||||||
|
const drawer = ref(null)
|
||||||
|
const themes = ref([
|
||||||
|
{ title: $i18n.t('lightMode'), value: 'light' },
|
||||||
|
{ title: $i18n.t('darkMode'), value: 'dark' },
|
||||||
|
{ title: $i18n.t('followSystem'), value: 'system'}
|
||||||
|
])
|
||||||
|
const setTheme = (theme) => {
|
||||||
|
colorMode.preference = theme
|
||||||
|
}
|
||||||
|
const feedback = () => {
|
||||||
|
window.open('https://github.com/WongSaang/chatgpt-ui/issues', '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
|
const { locale, locales, setLocale } = useI18n()
|
||||||
|
const setLang = (lang) => {
|
||||||
|
setLocale(lang)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-app
|
||||||
|
:theme="$colorMode.value"
|
||||||
|
>
|
||||||
|
<v-navigation-drawer
|
||||||
|
v-model="drawer"
|
||||||
|
>
|
||||||
|
<v-list>
|
||||||
|
<ModelDialog/>
|
||||||
|
</v-list>
|
||||||
|
|
||||||
|
<template v-slot:append>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-list>
|
||||||
|
<ApiKeyDialog/>
|
||||||
|
|
||||||
|
<v-menu
|
||||||
|
>
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-list-item
|
||||||
|
v-bind="props"
|
||||||
|
rounded="xl"
|
||||||
|
:prepend-icon="$colorMode.value === 'light' ? 'light_mode' : 'dark_mode'"
|
||||||
|
:title="$t('themeMode')"
|
||||||
|
></v-list-item>
|
||||||
|
</template>
|
||||||
|
<v-list
|
||||||
|
bg-color="white"
|
||||||
|
>
|
||||||
|
<v-list-item
|
||||||
|
v-for="(theme, idx) in themes"
|
||||||
|
:key="idx"
|
||||||
|
@click="setTheme(theme.value)"
|
||||||
|
>
|
||||||
|
<v-list-item-title>{{ theme.title }}</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
|
||||||
|
<SettingsLanguages/>
|
||||||
|
|
||||||
|
<v-list-item
|
||||||
|
rounded="xl"
|
||||||
|
prepend-icon="help_outline"
|
||||||
|
:title="$t('feedback')"
|
||||||
|
@click="feedback"
|
||||||
|
></v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</template>
|
||||||
|
</v-navigation-drawer>
|
||||||
|
|
||||||
|
<v-app-bar
|
||||||
|
class="d-lg-none"
|
||||||
|
>
|
||||||
|
<v-app-bar-nav-icon @click="drawer = !drawer"></v-app-bar-nav-icon>
|
||||||
|
|
||||||
|
<v-toolbar-title>{{ runtimeConfig.public.appName }}</v-toolbar-title>
|
||||||
|
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
|
||||||
|
<v-menu
|
||||||
|
>
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
v-bind="props"
|
||||||
|
icon="help_outline"
|
||||||
|
title="Feedback"
|
||||||
|
></v-btn>
|
||||||
|
</template>
|
||||||
|
<v-list
|
||||||
|
>
|
||||||
|
<v-list-item
|
||||||
|
@click="feedback"
|
||||||
|
>
|
||||||
|
<v-list-item-title>{{ $t('feedback') }}</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</v-app-bar>
|
||||||
|
|
||||||
|
<v-main>
|
||||||
|
<NuxtPage/>
|
||||||
|
</v-main>
|
||||||
|
</v-app>
|
||||||
|
</template>
|
||||||
@@ -48,5 +48,14 @@ export default defineNuxtConfig({
|
|||||||
vueI18n: {
|
vueI18n: {
|
||||||
fallbackLocale: 'en',
|
fallbackLocale: 'en',
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
nitro: {
|
||||||
|
devProxy: {
|
||||||
|
"/api": {
|
||||||
|
target: "http://localhost:8000/api",
|
||||||
|
prependPath: true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
definePageMeta({
|
||||||
|
middleware: ["auth"]
|
||||||
|
})
|
||||||
import {EventStreamContentType, fetchEventSource} from '@microsoft/fetch-event-source'
|
import {EventStreamContentType, fetchEventSource} from '@microsoft/fetch-event-source'
|
||||||
|
|
||||||
const { $i18n } = useNuxtApp()
|
const { $i18n } = useNuxtApp()
|
||||||
|
|||||||
17
pages/login.vue
Normal file
17
pages/login.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<v-text-field v-model="val" />
|
||||||
|
<v-btn @click="login">Login</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
definePageMeta({
|
||||||
|
layout: false
|
||||||
|
})
|
||||||
|
const val = ref('')
|
||||||
|
const { $auth } = useNuxtApp()
|
||||||
|
const login = () => {
|
||||||
|
$auth.login()
|
||||||
|
navigateTo('/test')
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -1,16 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container>
|
<v-container>
|
||||||
<v-btn @click="stop">Cancel</v-btn>
|
<!-- <v-btn @click="stop">Cancel</v-btn>-->
|
||||||
<v-text-field
|
<!--<v-text-field-->
|
||||||
v-model="message"
|
<!-- v-model="message"-->
|
||||||
label="Message"
|
<!-- label="Message"-->
|
||||||
outlined
|
<!-- outlined-->
|
||||||
></v-text-field>
|
<!-- ></v-text-field>-->
|
||||||
<v-btn @click="fetchReply">Send</v-btn>
|
<!-- <v-btn @click="fetchReply">Send</v-btn>-->
|
||||||
|
<v-btn color="green">{{ $auth.loginIn }}</v-btn>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<v-btn @click="$auth.login()">Login</v-btn>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
middleware: ["auth"],
|
||||||
|
layout: false
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
import {EventStreamContentType, fetchEventSource} from '@microsoft/fetch-event-source'
|
import {EventStreamContentType, fetchEventSource} from '@microsoft/fetch-event-source'
|
||||||
const message = ref('')
|
const message = ref('')
|
||||||
let ctrl
|
let ctrl
|
||||||
|
|||||||
118
plugins/auth.js
Normal file
118
plugins/auth.js
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import login from "~/pages/login.vue";
|
||||||
|
|
||||||
|
const AUTH_ROUTE = {
|
||||||
|
home: '/',
|
||||||
|
login: '/login'
|
||||||
|
}
|
||||||
|
|
||||||
|
const COOKIE_OPTIONS = {
|
||||||
|
prefix: '_Secure-auth',
|
||||||
|
path: '/',
|
||||||
|
tokenName: 'access-token',
|
||||||
|
refreshTokenName: 'refresh-token',
|
||||||
|
}
|
||||||
|
|
||||||
|
const ENDPOINTS = {
|
||||||
|
login: {
|
||||||
|
url: '/api/auth/signin'
|
||||||
|
},
|
||||||
|
refresh: {
|
||||||
|
url: '/api/auth/token/refresh'
|
||||||
|
},
|
||||||
|
logout: {
|
||||||
|
url: '/api/auth/signout'
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
url: '/api/auth/session'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default defineNuxtPlugin(() => {
|
||||||
|
const token = useCookie(COOKIE_OPTIONS.prefix + '.' + COOKIE_OPTIONS.tokenName, {
|
||||||
|
maxAge: 60 * 30,
|
||||||
|
})
|
||||||
|
const refreshToken = useCookie(COOKIE_OPTIONS.prefix + '.' + COOKIE_OPTIONS.refreshTokenName, {
|
||||||
|
maxAge: 60 * 60 * 24,
|
||||||
|
})
|
||||||
|
console.log('token', token.value)
|
||||||
|
console.log('refreshToken', refreshToken.value)
|
||||||
|
|
||||||
|
class Auth {
|
||||||
|
constructor() {
|
||||||
|
this.loginIn = useState('loginIn', () => false)
|
||||||
|
this.user = useState('user')
|
||||||
|
}
|
||||||
|
|
||||||
|
async login () {
|
||||||
|
const { data, error } = await useFetch(ENDPOINTS.login.url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
username: 'admin',
|
||||||
|
password: 'admin'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!error.value) {
|
||||||
|
token.value = data.value.access
|
||||||
|
refreshToken.value = data.value.refresh
|
||||||
|
}
|
||||||
|
console.log(error.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
logout () {
|
||||||
|
this.loginIn.value = false
|
||||||
|
this.user.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchUser () {
|
||||||
|
const { data, error } = await useFetch(ENDPOINTS.user.url, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + token.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!error.value) {
|
||||||
|
this.user = data.value
|
||||||
|
this.loginIn.value = true
|
||||||
|
}
|
||||||
|
console.log('fetchUser', error.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
async refresh () {
|
||||||
|
const { data, error } = await useFetch(ENDPOINTS.refresh.url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
'refresh': refreshToken.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log('refresh', data)
|
||||||
|
if (!error.value) {
|
||||||
|
token.value = data.value.access
|
||||||
|
}
|
||||||
|
console.log('refresh', error.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const auth = new Auth()
|
||||||
|
|
||||||
|
addRouteMiddleware('auth', async () => {
|
||||||
|
if (!auth.loginIn.value) {
|
||||||
|
console.log('check', token.value, refreshToken.value)
|
||||||
|
// Refresh token has expired
|
||||||
|
if (!refreshToken.value) {
|
||||||
|
console.log('refreshToken expired')
|
||||||
|
return navigateTo(AUTH_ROUTE.login)
|
||||||
|
}
|
||||||
|
if (!token.value) {
|
||||||
|
await auth.refresh()
|
||||||
|
}
|
||||||
|
await auth.fetchUser()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
provide: {
|
||||||
|
auth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user