feat: auth plugin

This commit is contained in:
Rafi
2023-02-22 16:50:53 +08:00
parent eb7f062144
commit 3e46512c15
5 changed files with 143 additions and 107 deletions

View File

@@ -0,0 +1,21 @@
export const useAuthFetch = async (url, options = {}) => {
const { $auth } = useNuxtApp()
const token = await $auth.retrieveToken()
if (!token) {
return await $auth.redirectToLogin()
}
options = Object.assign(options, {
headers: {
'Authorization': 'Bearer ' + token
}
})
const res = await useFetch(url, options)
if (res.error.value && res.error.value.status === 401) {
await $auth.logout()
}
return res
}

7
layouts/vuetifyApp.vue Normal file
View File

@@ -0,0 +1,7 @@
<template>
<v-app
:theme="$colorMode.value"
>
<slot />
</v-app>
</template>

View File

@@ -1,17 +1,87 @@
<template> <template>
<v-text-field v-model="val" /> <v-card
<v-btn @click="login">Login</v-btn> color="red-lighten-5"
style="height: 100vh"
>
<v-container>
<v-row>
<v-col
sm="9"
offset-sm="1"
md="8"
offset-md="2"
>
<v-card
class="mt-15"
>
<v-card-title>Sign in</v-card-title>
<v-card-text>
<v-form ref="signInForm">
<v-text-field
v-model="formData.username"
:rules="formRules.username"
label="User name"
variant="underlined"
></v-text-field>
<v-text-field
v-model="formData.password"
:rules="formRules.password"
label="Password"
variant="underlined"
></v-text-field>
<div v-if="errorMsg" class="text-red">{{ errorMsg }}</div>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
variant="elevated"
color="primary"
:loading="submitting"
@click="submit"
>Submit</v-btn>
</v-card-actions>
</v-form>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-container>
</v-card>
</template> </template>
<script setup> <script setup>
definePageMeta({ definePageMeta({
layout: false layout: 'vuetify-app'
})
const formData = ref({
username: '',
password: ''
})
const formRules = ref({
username: [
v => !!v || 'Username is required'
],
password: [
v => !!v || 'Password is required'
]
}) })
const val = ref('')
const { $auth } = useNuxtApp() const { $auth } = useNuxtApp()
const login = () => { const errorMsg = ref(null)
$auth.login() const signInForm = ref(null)
navigateTo('/test') const valid = ref(true)
const submitting = ref(false)
const submit = async () => {
errorMsg.value = null
const { valid } = await signInForm.value.validate()
if (valid) {
submitting.value = true
const error = await $auth.login(formData.value.username, formData.value.password)
submitting.value = false
if (!error) {
return await $auth.callback()
}
errorMsg.value = error
}
} }
</script> </script>

View File

@@ -1,72 +0,0 @@
<template>
<v-container>
<!-- <v-btn @click="stop">Cancel</v-btn>-->
<!--<v-text-field-->
<!-- v-model="message"-->
<!-- label="Message"-->
<!-- outlined-->
<!-- ></v-text-field>-->
<!-- <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>
</template>
<script setup>
definePageMeta({
middleware: ["auth"],
layout: false
})
import {EventStreamContentType, fetchEventSource} from '@microsoft/fetch-event-source'
const message = ref('')
let ctrl
const fetchReply = async () => {
ctrl = new AbortController()
try {
await fetchEventSource('/api/sse', {
signal: ctrl.signal,
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: message.value,
}),
onopen(response) {
if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
return;
}
throw new Error(`Failed to send message. HTTP ${response.status} - ${response.statusText}`);
},
onclose() {
console.log('onclose')
// throw new Error(`Failed to send message. Server closed the connection unexpectedly.`);
},
onerror(err) {
console.log('err', err)
throw err;
},
onmessage(message) {
console.log(message)
if (message.event === 'done') {
// ctrl.abort()
}
},
})
} catch (err) {
console.log(err)
ctrl.abort()
}
}
const stop = () => {
if (ctrl) {
ctrl.abort()
}
}
</script>

View File

@@ -1,4 +1,3 @@
import login from "~/pages/login.vue";
const AUTH_ROUTE = { const AUTH_ROUTE = {
home: '/', home: '/',
@@ -19,9 +18,6 @@ const ENDPOINTS = {
refresh: { refresh: {
url: '/api/auth/token/refresh' url: '/api/auth/token/refresh'
}, },
logout: {
url: '/api/auth/signout'
},
user: { user: {
url: '/api/auth/session' url: '/api/auth/session'
} }
@@ -35,8 +31,6 @@ export default defineNuxtPlugin(() => {
const refreshToken = useCookie(COOKIE_OPTIONS.prefix + '.' + COOKIE_OPTIONS.refreshTokenName, { const refreshToken = useCookie(COOKIE_OPTIONS.prefix + '.' + COOKIE_OPTIONS.refreshTokenName, {
maxAge: 60 * 60 * 24, maxAge: 60 * 60 * 24,
}) })
console.log('token', token.value)
console.log('refreshToken', refreshToken.value)
class Auth { class Auth {
constructor() { constructor() {
@@ -44,37 +38,39 @@ export default defineNuxtPlugin(() => {
this.user = useState('user') this.user = useState('user')
} }
async login () { async login (username, password) {
const { data, error } = await useFetch(ENDPOINTS.login.url, { const { data, error } = await useFetch(ENDPOINTS.login.url, {
method: 'POST', method: 'POST',
body: { body: {
username: 'admin', username,
password: 'admin' password
} }
}) })
if (!error.value) { if (!error.value) {
token.value = data.value.access token.value = data.value.access
refreshToken.value = data.value.refresh refreshToken.value = data.value.refresh
return null
} }
console.log(error.value) if (error.value.status === 401) {
return error.value.data.detail
}
return 'Request failed, please try again.'
} }
logout () { async logout () {
this.loginIn.value = false this.loginIn.value = false
this.user.value = null this.user.value = null
await this.redirectToLogin()
} }
async fetchUser () { async fetchUser () {
const { data, error } = await useFetch(ENDPOINTS.user.url, { const { data, error } = await useAuthFetch(ENDPOINTS.user.url)
headers: {
'Authorization': 'Bearer ' + token.value
}
})
if (!error.value) { if (!error.value) {
this.user = data.value this.user = data.value
this.loginIn.value = true this.loginIn.value = true
return null
} }
console.log('fetchUser', error.value) return error
} }
async refresh () { async refresh () {
@@ -84,29 +80,43 @@ export default defineNuxtPlugin(() => {
'refresh': refreshToken.value 'refresh': refreshToken.value
} }
}) })
console.log('refresh', data)
if (!error.value) { if (!error.value) {
token.value = data.value.access token.value = data.value.access
} }
console.log('refresh', error.value) }
async callback () {
return await navigateTo(AUTH_ROUTE.home)
}
async redirectToLogin () {
return await navigateTo(AUTH_ROUTE.login)
}
async retrieveToken () {
if (!refreshToken.value) {
return null
}
if (!token.value) {
await this.refresh()
}
return token.value || null
} }
} }
const auth = new Auth() const auth = new Auth()
addRouteMiddleware('auth', async () => { addRouteMiddleware('auth', async (to, from) => {
if (!auth.loginIn.value) { if (!auth.loginIn.value) {
console.log('check', token.value, refreshToken.value) const token = await auth.retrieveToken()
// Refresh token has expired if (!token) {
if (!refreshToken.value) { return await auth.redirectToLogin()
console.log('refreshToken expired')
return navigateTo(AUTH_ROUTE.login)
} }
if (!token.value) { const error = await auth.fetchUser()
await auth.refresh() if (error) {
return await auth.redirectToLogin()
} }
await auth.fetchUser()
} }
}) })