feat: auth plugin

This commit is contained in:
Rafi
2023-02-21 21:27:00 +08:00
parent 3c7d45154e
commit eb7f062144
7 changed files with 275 additions and 8 deletions

View File

108
layouts/default.vue Normal file
View 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>

View File

@@ -48,5 +48,14 @@ export default defineNuxtConfig({
vueI18n: { vueI18n: {
fallbackLocale: 'en', fallbackLocale: 'en',
}, },
},
nitro: {
devProxy: {
"/api": {
target: "http://localhost:8000/api",
prependPath: true
} }
}
},
}) })

View File

@@ -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
View 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>

View File

@@ -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
View 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
}
}
})