-
Notifications
You must be signed in to change notification settings - Fork 926
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
BREAKING CHANGE: Lot's of API and Usage changes
- Loading branch information
Showing
13 changed files
with
590 additions
and
300 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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]) | ||
) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,25 @@ | ||
module.exports = { | ||
user: { | ||
endpoint: '/api/auth/user', | ||
propertyName: 'user', | ||
resetOnFail: true, | ||
enabled: true, | ||
method: 'GET' | ||
}, | ||
login: { | ||
endpoint: '/api/auth/login' | ||
}, | ||
logout: { | ||
endpoint: '/api/auth/logout', | ||
method: 'GET' | ||
fetchUserOnLogin: true, | ||
resetOnError: true, | ||
namespace: 'auth', | ||
endpoints: { | ||
login: { url: '/api/auth/login', method: 'post', propertyName: 'token' }, | ||
logout: { url: '/api/auth/logout', method: 'post' }, | ||
user: { url: '/api/auth/user', method: 'get', propertyName: 'user' } | ||
}, | ||
redirect: { | ||
guest: true, | ||
user: true, | ||
notLoggedIn: '/login', | ||
loggedIn: '/' | ||
login: '/login', | ||
home: '/' | ||
}, | ||
token: { | ||
enabled: true, | ||
type: 'Bearer', | ||
localStorage: true, | ||
name: 'token', | ||
cookie: true, | ||
cookieName: 'token' | ||
localStorage: true | ||
}, | ||
errorHandler: { | ||
fetch: null, | ||
logout: null | ||
cookie: { | ||
name: 'token', | ||
params: { | ||
path: '/' | ||
} | ||
} | ||
} |
Oops, something went wrong.