Skip to content

Commit

Permalink
feat: use new Auth class
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Lot's of API and Usage changes
  • Loading branch information
Pooya Parsa authored and pi0 committed Feb 2, 2018
1 parent c44c91c commit d4da740
Show file tree
Hide file tree
Showing 13 changed files with 590 additions and 300 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -47,7 +47,7 @@ Edit `nuxt.config.js`:
```js ```js
{ {
modules: [ modules: [
'@nuxtjs/axios', // <-- Should be before @nuxtjs/auth '@nuxtjs/axios',
'@nuxtjs/auth' '@nuxtjs/auth'
], ],


Expand Down
290 changes: 290 additions & 0 deletions lib/auth.js
@@ -0,0 +1,290 @@
import Cookie from 'cookie'
import Cookies from 'js-cookie'
import Vue from 'vue'
import Hookable from 'hable'

export default class Auth extends Hookable {
constructor (ctx, options) {
super()

this.ctx = ctx
this.app = ctx.app
this.options = options

// Keep token out of the store for security reasons
Vue.set(this, 'token', null)

// Reset on error
if (this.options.resetOnError) {
this._resetOnError()
}

this._registerVuexStore()
}

_registerVuexStore () {
const authModule = {
namespaced: true,
state: () => ({
user: null,
loggedIn: false
}),
mutations: {
SET (state, payload) {
Vue.set(state, payload.key, payload.value)
}
}
}

this.$store.registerModule(this.options.namespace, authModule, {
preserveState: Boolean(this.$store.state[this.options.namespace])
})
}

_resetOnError () {
this.hook('error', () => {
this.reset()
})
}

_watchLoggedIn () {
return this.$store.watch(
() => this.$store.stat[this.options.namespace + '/loggedIn'],
newAuthState => {
if (newAuthState) {
this.redirectToHome()
} else {
this.redirectToLogin()
}
}
)
}

get $axios () {
if (!this.app.$axios) {
throw new Error('$axios is not available')
}

return this.app.$axios
}

get $store () {
return this.ctx.store
}

get $req () {
return this.app.context.req
}

get $res () {
return this.app.context.res
}

get isAPIRequest () {
return (
process.server &&
this.$req.url.indexOf(this.options.endpoints.user.url) === 0
)
}

get state () {
return this.$store.state[this.options.namespace]
}

reset () {
this.setState('loggedIn', false)
this.setState('token', null)
this.setState('user', null)

if (this.options.cookie) {
this.setCookie(this.options.cookie.name, null)
}

if (this.options.token.localStorage) {
this.setLocalStorage(this.options.token.name, null)
}
}

setState (key, value) {
if (key === 'token') {
this.token = value
return
}

this.$store.commit(this.options.namespace + '/SET', { key, value })
}

getState (key) {
if (key === 'token') {
return this.token
}

return this.state[key]
}

setLocalStorage (name, value) {
if (typeof localStorage !== 'undefined') {
if (value) {
localStorage.setItem(name, value)
} else {
localStorage.removeItem(name)
}
}
}

getLocalStorage (name) {
if (typeof localStorage !== 'undefined') {
return localStorage.getItem(name)
}
}

setCookie (name, value, params = {}) {
if (!this.options.cookie) {
return
}

const _params = Object.assign({}, this.options.cookie.params, params)

if (!value) {
let date = new Date()
date.setDate(date.getDate() - 1)
_params.expires = date
}

if (process.browser) {
Cookies.set(name, value, _params)
} else {
// Don't send duplicate token via Set-Cookie
if (!value) {
this.$res.setHeader(
'Set-Cookie',
Cookie.serialize(name, value, _params)
)
}
}
}

getCookie (name) {
const cookieStr = process.browser
? document.cookie
: this.$req.headers.cookie

const cookies = Cookie.parse(cookieStr || '') || {}

return cookies[name]
}

async _fetch (name, endpoint) {
const defaults = this.options.endpoints[name]
if (!defaults) {
return
}

try {
const { data } = await this.$axios.request(
Object.assign({}, defaults, endpoint)
)
return data
} catch (error) {
await this.callHook('error', { name, endpoint, error })
}
}

async login (endpoint) {
const data = await this._fetch('login', endpoint)
if (!data) {
return
}

// Extract and set token
this.setToken(data.token)

// Fetch User
if (this.options.fetchUserOnLogin) {
return this.fetchUser()
}

// Set loggedIn to true
this.setState('loggedIn', true)
}

async fetchUser (endpoint) {
if (this.options.token && !this.getState('token')) {
return
}

const data = await this._fetch('user', endpoint)
if (!data) {
return
}

this.setState('user', data.user)
this.setState('loggedIn', true)
}

async logout (endpoint) {
await this._fetch('logout', endpoint)

this.reset()
}

setToken (token) {
if (!this.options.token) {
return
}

// Update local state
this.setState('token', token)

// Set Authorization token for all axios requests
this.$axios.setToken(token, this.options.token.type)

// Save it in cookies
if (this.options.cookie) {
this.setCookie(this.options.cookie.name, token)
}

// Save it in localSotage
if (this.options.token.localStorage) {
this.setLocalStorage(this.options.token.name, token)
}
}

syncToken () {
if (!this.options.token) {
return
}

let token = this.getState('token')

if (!token && this.options.cookie) {
token = this.getCookie(this.options.cookie.name)
}

if (!token && this.options.token.localStorage) {
token = this.getLocalStorage(this.options.token.name)
}

this.setToken(token)
}

redirect () {
if (this.getState('loggedIn')) {
this.redirectToHome()
} else {
this.redirectToLogin()
}
}

redirectToLogin () {
if (this.options.redirect.login) {
this.ctx.redirect(this.options.redirect.login)
}
}

redirectToHome () {
if (this.options.redirect.home) {
this.ctx.redirect(this.options.redirect.home)
}
}
}
44 changes: 44 additions & 0 deletions lib/auth.plugin.js
@@ -0,0 +1,44 @@
import Auth from './auth'
import Middleware from './middleware'

export default function (ctx, inject) {
// Create new Auth instance
const $auth = new Auth(ctx, <%= JSON.stringify(options, undefined, 2).replace(/"/g,'\'') %>)

// Prevent infinity redirects
if ($auth.isAPIRequest) {
return
}

// Inject it to nuxt context as $auth
inject('auth', $auth)

// Sync token
$auth.syncToken()

// Fetch user if is not available
if (!$auth.state.user) {
return $auth.fetchUser()
}
}

// Register auth middleware
Middleware.auth = function (ctx) {
if (!routeOption(ctx.oute, 'noRedirect')) {
ctx.app.$auth.redirect()
}
}

// Utility to get route option
function routeOption (route, key) {
return route.matched.some(m => {
// Browser
if (process.browser) {
return Object.values(m.components).some(component => component.options[key])
}
// SSR
return Object.values(m.components).some(component =>
Object.values(component._Ctor).some(ctor => ctor.options && ctor.options[key])
)
})
}
39 changes: 15 additions & 24 deletions lib/defaults.js
@@ -1,34 +1,25 @@
module.exports = { module.exports = {
user: { fetchUserOnLogin: true,
endpoint: '/api/auth/user', resetOnError: true,
propertyName: 'user', namespace: 'auth',
resetOnFail: true, endpoints: {
enabled: true, login: { url: '/api/auth/login', method: 'post', propertyName: 'token' },
method: 'GET' logout: { url: '/api/auth/logout', method: 'post' },
}, user: { url: '/api/auth/user', method: 'get', propertyName: 'user' }
login: {
endpoint: '/api/auth/login'
},
logout: {
endpoint: '/api/auth/logout',
method: 'GET'
}, },
redirect: { redirect: {
guest: true, login: '/login',
user: true, home: '/'
notLoggedIn: '/login',
loggedIn: '/'
}, },
token: { token: {
enabled: true,
type: 'Bearer', type: 'Bearer',
localStorage: true,
name: 'token', name: 'token',
cookie: true, localStorage: true
cookieName: 'token'
}, },
errorHandler: { cookie: {
fetch: null, name: 'token',
logout: null params: {
path: '/'
}
} }
} }

0 comments on commit d4da740

Please sign in to comment.