feat: auth plugin
This commit is contained in:
21
composables/useAuthFetch.js
Normal file
21
composables/useAuthFetch.js
Normal 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
7
layouts/vuetifyApp.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<v-app
|
||||||
|
:theme="$colorMode.value"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</v-app>
|
||||||
|
</template>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
|
||||||
@@ -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()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user