mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-10 07:37:30 +00:00
added logging and refreshing with jwt token
This commit is contained in:
parent
0a884058df
commit
ee2b1e76d3
12 changed files with 151 additions and 5 deletions
backend/src/baserow/config
web-frontend
config
pages/login
plugins
services
store
|
@ -1,4 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
import datetime
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
@ -21,10 +22,12 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
|
||||||
'rest_framework'
|
'rest_framework',
|
||||||
|
'corsheaders'
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
'corsheaders.middleware.CorsMiddleware',
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
@ -118,3 +121,13 @@ REST_FRAMEWORK = {
|
||||||
'rest_framework.authentication.BasicAuthentication',
|
'rest_framework.authentication.BasicAuthentication',
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CORS_ORIGIN_WHITELIST = (
|
||||||
|
'http://localhost:3000',
|
||||||
|
)
|
||||||
|
|
||||||
|
JWT_AUTH = {
|
||||||
|
'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300),
|
||||||
|
'JWT_ALLOW_REFRESH': True,
|
||||||
|
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ from django.urls import path, include
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
from rest_framework_jwt.views import obtain_jwt_token
|
from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token
|
||||||
|
|
||||||
|
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
|
@ -10,5 +10,6 @@ router = routers.DefaultRouter()
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^api/token-auth/', obtain_jwt_token),
|
url(r'^api/token-auth/', obtain_jwt_token),
|
||||||
|
url(r'^api/token-refresh/', refresh_jwt_token),
|
||||||
path('api/', include(router.urls)),
|
path('api/', include(router.urls)),
|
||||||
]
|
]
|
||||||
|
|
|
@ -25,7 +25,7 @@ export default {
|
||||||
/*
|
/*
|
||||||
** Plugins to load before mounting the App
|
** Plugins to load before mounting the App
|
||||||
*/
|
*/
|
||||||
plugins: [{ src: '@/plugins/Vuelidate.js' }],
|
plugins: [{ src: '@/plugins/auth.js' }, { src: '@/plugins/vuelidate.js' }],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Nuxt.js modules
|
** Nuxt.js modules
|
||||||
|
@ -40,5 +40,14 @@ export default {
|
||||||
*/
|
*/
|
||||||
axios: {
|
axios: {
|
||||||
// See https://github.com/nuxt-community/axios-module#options
|
// See https://github.com/nuxt-community/axios-module#options
|
||||||
|
},
|
||||||
|
|
||||||
|
env: {
|
||||||
|
// The API base url, this will be prepended to the urls of the remote calls.
|
||||||
|
baseUrl: 'http://localhost:8000',
|
||||||
|
|
||||||
|
// The JWT token expire time in seconds, when this time passes after a login
|
||||||
|
// or refresh, the token will be refreshed.
|
||||||
|
JWTTokenExpire: 300
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import base from './nuxt.config.base.js'
|
||||||
const config = {
|
const config = {
|
||||||
build: {
|
build: {
|
||||||
extend(config, ctx) {
|
extend(config, ctx) {
|
||||||
// Run ESLint ad Stylelint on save
|
|
||||||
if (ctx.isDev && ctx.isClient) {
|
if (ctx.isDev && ctx.isClient) {
|
||||||
config.module.rules.push({
|
config.module.rules.push({
|
||||||
enforce: 'pre',
|
enforce: 'pre',
|
||||||
|
|
|
@ -3,14 +3,25 @@
|
||||||
<h1 class="box-title">
|
<h1 class="box-title">
|
||||||
<img src="@/static/img/logo.svg" alt="" />
|
<img src="@/static/img/logo.svg" alt="" />
|
||||||
</h1>
|
</h1>
|
||||||
|
<div v-if="invalid" class="alert alert-error alert-has-icon">
|
||||||
|
<div class="alert-icon">
|
||||||
|
<i class="fas fa-exclamation"></i>
|
||||||
|
</div>
|
||||||
|
<div class="alert-title">Incorrect credentials</div>
|
||||||
|
<p class="alert-content">
|
||||||
|
The provided e-mail address or password is incorrect.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
authenticated: {{ loggedIn }}
|
||||||
<form @submit.prevent="login">
|
<form @submit.prevent="login">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<label class="control-label">E-mail address</label>
|
<label class="control-label">E-mail address</label>
|
||||||
<div class="control-elements">
|
<div class="control-elements">
|
||||||
<input
|
<input
|
||||||
|
ref="email"
|
||||||
v-model="credentials.email"
|
v-model="credentials.email"
|
||||||
:class="{ 'input-error': $v.credentials.email.$error }"
|
:class="{ 'input-error': $v.credentials.email.$error }"
|
||||||
type="text"
|
type="email"
|
||||||
class="input input-large"
|
class="input input-large"
|
||||||
@blur="$v.credentials.email.$touch()"
|
@blur="$v.credentials.email.$touch()"
|
||||||
/>
|
/>
|
||||||
|
@ -23,6 +34,7 @@
|
||||||
<label class="control-label">Password</label>
|
<label class="control-label">Password</label>
|
||||||
<div class="control-elements">
|
<div class="control-elements">
|
||||||
<input
|
<input
|
||||||
|
ref="password"
|
||||||
v-model="credentials.password"
|
v-model="credentials.password"
|
||||||
:class="{ 'input-error': $v.credentials.password.$error }"
|
:class="{ 'input-error': $v.credentials.password.$error }"
|
||||||
type="password"
|
type="password"
|
||||||
|
@ -55,6 +67,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
import { required, email } from 'vuelidate/lib/validators'
|
import { required, email } from 'vuelidate/lib/validators'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -67,12 +80,14 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
|
invalid: false,
|
||||||
credentials: {
|
credentials: {
|
||||||
email: '',
|
email: '',
|
||||||
password: ''
|
password: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: { ...mapGetters({ loggedIn: 'auth/loggedIn' }) },
|
||||||
validations: {
|
validations: {
|
||||||
credentials: {
|
credentials: {
|
||||||
email: { required, email },
|
email: { required, email },
|
||||||
|
@ -84,6 +99,23 @@ export default {
|
||||||
this.$v.$touch()
|
this.$v.$touch()
|
||||||
if (!this.$v.$invalid) {
|
if (!this.$v.$invalid) {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
this.$store
|
||||||
|
.dispatch('auth/login', {
|
||||||
|
email: this.credentials.email,
|
||||||
|
password: this.credentials.password
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log('@TODO navigate to main page')
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.invalid = true
|
||||||
|
this.credentials.password = ''
|
||||||
|
this.$v.$reset()
|
||||||
|
this.$refs.password.focus()
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
web-frontend/plugins/auth.js
Normal file
11
web-frontend/plugins/auth.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
export default function({ store }) {
|
||||||
|
if (!process.browser) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = JSON.parse(localStorage.getItem('user'))
|
||||||
|
if (user) {
|
||||||
|
store.commit('auth/SET_USER_DATA', user)
|
||||||
|
store.dispatch('auth/refresh')
|
||||||
|
}
|
||||||
|
}
|
15
web-frontend/services/authService.js
Normal file
15
web-frontend/services/authService.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { client } from './client'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
login(username, password) {
|
||||||
|
return client.post('/api/token-auth/', {
|
||||||
|
username: username,
|
||||||
|
password: password
|
||||||
|
})
|
||||||
|
},
|
||||||
|
refresh(token) {
|
||||||
|
return client.post('/api/token-refresh/', {
|
||||||
|
token: token
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
10
web-frontend/services/client.js
Normal file
10
web-frontend/services/client.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
export const client = axios.create({
|
||||||
|
baseURL: process.env.baseUrl,
|
||||||
|
withCredentials: false,
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
56
web-frontend/store/auth.js
Normal file
56
web-frontend/store/auth.js
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import AuthService from '@/services/authService.js'
|
||||||
|
import { client } from '@/services/client.js'
|
||||||
|
|
||||||
|
export const state = () => ({
|
||||||
|
user: null
|
||||||
|
})
|
||||||
|
|
||||||
|
export const mutations = {
|
||||||
|
SET_USER_DATA(state, data) {
|
||||||
|
state.user = data
|
||||||
|
localStorage.setItem('user', JSON.stringify(data))
|
||||||
|
client.defaults.headers.common.Authorization = `JWT ${data.token}`
|
||||||
|
},
|
||||||
|
CLEAR_USER_DATA(state) {
|
||||||
|
state.user = null
|
||||||
|
localStorage.removeItem('user')
|
||||||
|
client.defaults.headers.common.pop('Authorization')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
login({ commit, dispatch }, { email, password }) {
|
||||||
|
return AuthService.login(email, password).then(({ data }) => {
|
||||||
|
commit('SET_USER_DATA', data)
|
||||||
|
dispatch('startRefreshTimeout')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
refresh({ commit, state, dispatch }) {
|
||||||
|
return AuthService.refresh(state.user.token)
|
||||||
|
.then(({ data }) => {
|
||||||
|
commit('SET_USER_DATA', data)
|
||||||
|
dispatch('startRefreshTimeout')
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// The token could not be refreshed, this means the token is no longer
|
||||||
|
// valid and the user not logged in anymore.
|
||||||
|
commit('CLEAR_USER_DATA')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Because the token expires within a configurable time, we need to keep
|
||||||
|
* refreshing the token before that happens.
|
||||||
|
*/
|
||||||
|
startRefreshTimeout({ dispatch }) {
|
||||||
|
clearTimeout(this.refreshTimeout)
|
||||||
|
this.refreshTimeout = setTimeout(() => {
|
||||||
|
dispatch('refresh')
|
||||||
|
}, (process.env.JWTTokenExpire - 2) * 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getters = {
|
||||||
|
loggedIn(state) {
|
||||||
|
return !!state.user
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue