diff --git a/README.md b/README.md index 4a1112e77..4876adda0 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Edit `nuxt.config.js`: ```js { modules: [ - '@nuxtjs/axios', // <-- Should be before @nuxtjs/auth + '@nuxtjs/axios', '@nuxtjs/auth' ], diff --git a/lib/auth.js b/lib/auth.js new file mode 100644 index 000000000..c2280c25b --- /dev/null +++ b/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) + } + } +} diff --git a/lib/auth.plugin.js b/lib/auth.plugin.js new file mode 100644 index 000000000..081bd0cdf --- /dev/null +++ b/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]) + ) + }) +} diff --git a/lib/defaults.js b/lib/defaults.js index db62b37f5..8803e2428 100644 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -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: '/' + } } } diff --git a/lib/module.js b/lib/module.js index fdf992c02..1af8b7b00 100644 --- a/lib/module.js +++ b/lib/module.js @@ -1,28 +1,40 @@ -const { resolve } = require('path') +const { resolve, join } = require('path') const merge = require('lodash/merge') const defaults = require('./defaults') module.exports = function (moduleOptions) { - const options = merge(defaults, moduleOptions, this.options.auth) + const options = merge({}, defaults, moduleOptions, this.options.auth) + + // Enforce vuex store because auth depends on it + if (!this.options.store) { + throw new Error('[Auth] Enable vuex store by creating store/index.js') + } + + // Normalize options.endpoints + for (let key in options.endpoints) { + let e = options.endpoints[key] + if (typeof e === 'string') { + e = { url: e } + } + e.method = e.method ? e.method.toLowerCase() : 'post' + options.endpoints[key] = e + } // Plugin - this.addPlugin({ - src: resolve(__dirname, './templates/auth.plugin.js'), + const { dst } = this.addTemplate({ + src: resolve(__dirname, './auth.plugin.js'), fileName: 'auth.plugin.js', options }) - // Middleware - this.addTemplate({ - src: resolve(__dirname, './templates/auth.middleware.js'), - fileName: 'auth.middleware.js', - options - }) + // Add plugin just after $axios + const index = this.options.plugins.findIndex(p => p.src.includes('/axios.js')) + this.options.plugins.splice(index + 1, 0, join(this.options.buildDir, dst)) - // Store + // Class this.addTemplate({ - src: resolve(__dirname, './templates/auth.store.js'), - fileName: 'auth.store.js', + src: resolve(__dirname, './auth.js'), + fileName: 'auth.js', options }) } diff --git a/lib/templates/auth.middleware.js b/lib/templates/auth.middleware.js deleted file mode 100644 index 4bf60dab9..000000000 --- a/lib/templates/auth.middleware.js +++ /dev/null @@ -1,46 +0,0 @@ -import middleware from './middleware' - -// Register auth middleware -middleware.auth = function ({ route, redirect, store }) { - // Registering guest and auth routes. - const guestRoute = '<%= options.redirect.notLoggedIn %>' - const authRoute = '<%= options.redirect.loggedIn %>' - - // Skip if route is not guarded - if (isRouteGuarded(route)) { - return - } - - // Redirect guest users to {notLoggedIn} - <% if (options.redirect.guest) { %> - if (!store.getters['auth/loggedIn'] && guestRoute !== route.path) { - return redirect(guestRoute) - } - <% } %> - - // Redirect authenticated users to {loggedIn} - <% if (options.redirect.user) { %> - if (store.getters['auth/loggedIn'] && authRoute !== route.path) { - return redirect(authRoute) - } - <% } %> -} - -/** - * isRouteGuarded utility - * @param {*} route - * @returns {boolean} - */ -function isRouteGuarded(route) { - return route.matched.some(m => { - // SSR - if (process.server) { - return Object.values(m.components) - .some(component => Object.values(component._Ctor) - .some(ctor => ctor.options && ctor.options.guarded)) - } - // Client - return Object.values(m.components) - .some(component => component.options.guarded) - }) -} diff --git a/lib/templates/auth.plugin.js b/lib/templates/auth.plugin.js deleted file mode 100644 index b2161c688..000000000 --- a/lib/templates/auth.plugin.js +++ /dev/null @@ -1,21 +0,0 @@ -import './auth.middleware' -import authStore from './auth.store' - -export default async function ({ store, app }, inject) { - // Register auth store module - store.registerModule('auth', authStore, { - preserveState: Boolean(store.state.auth), - }) - - // Fetch initial state - await store.dispatch('auth/fetch') - - // Redirect on login and logout - store.watch(() => store.getters['auth/loggedIn'], newAuthState => { - if (newAuthState) { - app.router.replace('<%= options.redirect.loggedIn %>') - return - } - app.router.replace('<%= options.redirect.notLoggedIn %>') - }) -} diff --git a/lib/templates/auth.store.js b/lib/templates/auth.store.js deleted file mode 100644 index 26d8f4638..000000000 --- a/lib/templates/auth.store.js +++ /dev/null @@ -1,182 +0,0 @@ -<% if (options.token.enabled && options.token.cookie) { %> -import Cookie from 'cookie' -import Cookies from 'js-cookie' -<% } %> - -export default { - namespaced: true, - - state: () => ({ - <% if (options.token.enabled) { %>token: null,<% } %> - <% if (options.user.enabled) { %>user: null<% } %> - }), - - getters: { - loggedIn(state) { - return Boolean( - true - <% if (options.user.enabled) { %>&& state.user<% } %> - <% if (options.token.enabled) { %>&& state.token<% } %> - ) - } - }, - - mutations: { - <% if (options.user.enabled) { %> - // SET_USER - SET_USER (state, user) { - state.user = user - }, - <% } %> - - <% if (options.token.enabled) { %> - // SET_TOKEN - SET_TOKEN (state, token) { - state.token = token - } - <% } %> - }, - - actions: { - <% if (options.token.enabled) { %> - // Update token - async updateToken ({ commit }, token) { - // Update token in store's state - commit('SET_TOKEN', token) - - // Set Authorization token for all axios requests - this.$axios.setToken(token, '<%= options.token.type %>') - - <% if (options.token.localStorage) { %> - // Update localStorage - if (process.browser && localStorage) { - if (token) { - localStorage.setItem('<%= options.token.name %>', token) - } else { - localStorage.removeItem('<%= options.token.name %>') - } - } - <% } %> - - <% if (options.token.cookie) { %> - // Update cookies - if (process.browser) { - // ...Browser - if (token) { - Cookies.set('<%= options.token.cookieName %>', token) - } else { - Cookies.remove('<%= options.token.cookieName %>') - } - } else { - // ...Server - let params = { - domain: '/' - } - if (!token) { - let expires - let date = new Date() - expires = date.setDate(date.getDate() - 1) - params.expires = new Date(expires) - } - this.app.context.res.setHeader('Set-Cookie', Cookie.serialize('<%= options.token.cookieName %>', token, params)) - } - <% } %> - }, - <% } %> - - <% if (options.token.enabled) { %> - // Fetch Token - async fetchToken ({ dispatch }) { - let token - - <% if (options.token.localStorage) { %> - // Try to extract token from localStorage - if (process.browser && localStorage) { - token = localStorage.getItem('<%= options.token.name %>') - } - <% } %> - - <% if (options.token.cookie) { %> - // Try to extract token from cookies - if (!token) { - const cookieStr = process.browser ? document.cookie : this.app.context.req.headers.cookie - const cookies = Cookie.parse(cookieStr || '') || {} - token = cookies['<%= options.token.cookieName %>'] - } - <% } %> - - if (token) { - await dispatch('updateToken', token) - } - }, - <% } %> - - // Reset - async reset ({ dispatch, commit }) { - <% if (options.user.enabled) { %>commit('SET_USER', null)<% } %> - <% if (options.token.enabled) { %>await dispatch('updateToken', null)<% } %> - }, - - <% if (options.user.enabled) { %> - // Fetch - async fetch ({ getters, state, commit, dispatch }, { endpoint = '<%= options.user.endpoint %>' } = {}) { - <% if (options.token.enabled) { %> - // Fetch and update latest token - await dispatch('fetchToken') - - // Skip if there is no token set - if (!state.token) { - return - } - <% } %> - - // Try to get user profile - try { - const data = await this.$axios.$<%= options.user.method.toLowerCase() %>(endpoint) - commit('SET_USER', data<%= options.user.propertyName ? ('[\'' + options.user.propertyName + '\']') : '' %>) - } catch (e) { - console.error(e) - <% if (options.errorHandler.fetch) { %> - await options.errorHandler.fetch(this, e) - <% } %> - <% if (options.user.resetOnFail) { %> - // Reset store - await dispatch('reset') - <% } %> - } - }, - <% } %> - - // Login - async login ({ dispatch }, { fields, endpoint = '<%= options.login.endpoint %>' } = {}) { - // Send credentials to API - let data = await this.$axios.$post(endpoint, fields) - - <% if (options.token.enabled) { %> - await dispatch('updateToken', data.<%= options.token.name %>) - <% } %> - - // Fetch authenticated user - <% if (options.user.enabled) { %> - await dispatch('fetch') - <% } %> - }, - - // Logout - async logout ({ dispatch, state }, { endpoint = '<%= options.logout.endpoint %>' } = {}) { - // Server side logout - try { - await this.$axios.$<%= options.logout.method.toLowerCase() %>(endpoint) - } catch (e) { - // eslint-disable-next-line no-console - console.error('Error while logging out', e) - <% if (options.errorHandler.logout) { %> - await options.errorHandler.logout(this, e) - <% } %> - } - - // Reset store - await dispatch('reset') - } - } -} diff --git a/package.json b/package.json index b8f8a774d..ce2646b7f 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "access": "public" }, "scripts": { + "dev": "nuxt test/fixture", "lint": "eslint lib src test", "lint-fix": "eslint --fix lib src test", "test": "npm run lint && jest", @@ -43,11 +44,14 @@ "dependencies": { "@nuxtjs/axios": "^5.0.0-rc.2", "cookie": "^0.3.1", + "hable": "^0.0.7", "js-cookie": "^2.2.0", "lodash": "^4.17.4" }, "devDependencies": { + "body-parser": "^1.18.2", "codecov": "latest", + "cookie-parser": "^1.4.3", "eslint": "latest", "eslint-config-standard": "latest", "eslint-plugin-import": "latest", @@ -56,9 +60,11 @@ "eslint-plugin-promise": "latest", "eslint-plugin-standard": "latest", "eslint-plugin-vue": "latest", + "express": "latest", + "express-jwt": "^5.3.0", "jest": "latest", "jsdom": "latest", - "nuxt": "^1.3.0", + "nuxt": "latest", "standard-version": "latest" } } diff --git a/test/fixture/api/auth.js b/test/fixture/api/auth.js new file mode 100644 index 000000000..10b4cae0b --- /dev/null +++ b/test/fixture/api/auth.js @@ -0,0 +1,59 @@ +const express = require('express') +const bodyParser = require('body-parser') +const cookieParser = require('cookie-parser') +const jwt = require('express-jwt') +const jsonwebtoken = require('jsonwebtoken') + +// Create app +const app = express() + +// Install middleware +app.use(cookieParser()) +app.use(bodyParser.json()) + +// JWT middleware +app.use( + jwt({ + secret: 'dummy' + }).unless({ + path: '/api/auth/login' + }) +) + +// -- Routes -- + +// [POST] /login +app.post('/login', (req, res, next) => { + const { username, password } = req.body + const valid = username === 'user' && password === 'pass' + + if (!valid) { + throw new Error('Invalid username or password') + } + + const token = jsonwebtoken.sign({ username }, 'dummy') + + res.json({ token }) +}) + +// [GET] /user +app.get('/user', (req, res, next) => { + res.json({ user: req.user }) +}) + +// [POST] /logout +app.post('/logout', (req, res, next) => { + res.json({ status: 'OK' }) +}) + +// Error handler +app.use((err, req, res, next) => { + console.error(err) + res.status(401).send(err + '') +}) + +// -- export app -- +module.exports = { + path: '/api/auth', + handler: app +} diff --git a/test/fixture/nuxt.config.js b/test/fixture/nuxt.config.js index 40024640b..c1aeab42a 100644 --- a/test/fixture/nuxt.config.js +++ b/test/fixture/nuxt.config.js @@ -7,5 +7,8 @@ module.exports = { render: { resourceHints: false }, - modules: ['@@', '@nuxtjs/axios'] + serverMiddleware: [ + '~/api/auth' + ], + modules: ['@nuxtjs/axios', '@@'] } diff --git a/test/fixture/pages/index.vue b/test/fixture/pages/index.vue index 4e79e49b6..14ee85b11 100644 --- a/test/fixture/pages/index.vue +++ b/test/fixture/pages/index.vue @@ -1,11 +1,38 @@ \ No newline at end of file + diff --git a/yarn.lock b/yarn.lock index dc440cafb..1ed7d9a36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,8 +3,8 @@ "@babel/code-frame@^7.0.0-beta.35": - version "7.0.0-beta.38" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.38.tgz#c0af5930617e55e050336838e3a3670983b0b2b2" + version "7.0.0-beta.39" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.39.tgz#91c90bb65207fc5a55128cb54956ded39e850457" dependencies: chalk "^2.0.0" esutils "^2.0.2" @@ -302,7 +302,7 @@ async-limiter@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" -async@^1.4.0: +async@^1.4.0, async@^1.5.0: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -945,6 +945,10 @@ base64-js@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" +base64url@2.0.0, base64url@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb" + base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -985,7 +989,7 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" -body-parser@1.18.2: +body-parser@1.18.2, body-parser@^1.18.2: version "1.18.2" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" dependencies: @@ -1139,6 +1143,10 @@ bser@^2.0.0: dependencies: node-int64 "^0.4.0" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -1737,6 +1745,13 @@ convert-source-map@^1.4.0, convert-source-map@^1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" +cookie-parser@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.3.tgz#0fe31fa19d000b95f4aadf1f53fdc2b8a203baa5" + dependencies: + cookie "0.3.1" + cookie-signature "1.0.6" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -2233,6 +2248,13 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" +ecdsa-sig-formatter@1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1" + dependencies: + base64url "^2.0.0" + safe-buffer "^5.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -2646,7 +2668,20 @@ expect@^22.1.0: jest-message-util "^22.1.0" jest-regex-util "^22.1.0" -express@^4.15.2: +express-jwt@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/express-jwt/-/express-jwt-5.3.0.tgz#3d90cd65802e6336252f19e6a3df3e149e0c5ea0" + dependencies: + async "^1.5.0" + express-unless "^0.3.0" + jsonwebtoken "^7.3.0" + lodash.set "^4.0.0" + +express-unless@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/express-unless/-/express-unless-0.3.1.tgz#2557c146e75beb903e2d247f9b5ba01452696e20" + +express@^4.15.2, express@latest: version "4.16.2" resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" dependencies: @@ -3157,6 +3192,12 @@ gzip-size@^3.0.0: dependencies: duplexer "^0.1.1" +hable@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/hable/-/hable-0.0.7.tgz#9f75bb220d91e02baad5604b9143105e8d8fb8e9" + dependencies: + items-promise "^1.0.0" + handlebars@^4.0.2, handlebars@^4.0.3: version "4.0.11" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" @@ -3800,6 +3841,10 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" +isemail@1.x.x: + version "1.2.0" + resolved "https://registry.yarnpkg.com/isemail/-/isemail-1.2.0.tgz#be03df8cc3e29de4d2c5df6501263f1fa4595e9a" + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -3885,6 +3930,10 @@ istanbul-reports@^1.1.3: dependencies: handlebars "^4.0.3" +items-promise@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/items-promise/-/items-promise-1.0.0.tgz#a71ff03063078eb82db468f1e2f12df4da34dc01" + jest-changed-files@^22.1.4: version "22.1.4" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-22.1.4.tgz#1f7844bcb739dec07e5899a633c0cb6d5069834e" @@ -4135,6 +4184,15 @@ jest@latest: dependencies: jest-cli "^22.1.4" +joi@^6.10.1: + version "6.10.1" + resolved "https://registry.yarnpkg.com/joi/-/joi-6.10.1.tgz#4d50c318079122000fe5f16af1ff8e1917b77e06" + dependencies: + hoek "2.x.x" + isemail "1.x.x" + moment "2.x.x" + topo "1.x.x" + js-base64@^2.1.9: version "2.4.3" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.3.tgz#2e545ec2b0f2957f41356510205214e98fad6582" @@ -4248,6 +4306,16 @@ jsonparse@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" +jsonwebtoken@^7.3.0: + version "7.4.3" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-7.4.3.tgz#77f5021de058b605a1783fa1283e99812e645638" + dependencies: + joi "^6.10.1" + jws "^3.1.4" + lodash.once "^4.0.0" + ms "^2.0.0" + xtend "^4.0.1" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -4257,6 +4325,23 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jwa@^1.1.4: + version "1.1.5" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5" + dependencies: + base64url "2.0.0" + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.9" + safe-buffer "^5.0.1" + +jws@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2" + dependencies: + base64url "^2.0.0" + jwa "^1.1.4" + safe-buffer "^5.0.1" + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -4400,6 +4485,14 @@ lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + +lodash.set@^4.0.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" @@ -4689,6 +4782,10 @@ modify-values@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.0.tgz#e2b6cdeb9ce19f99317a53722f3dbf5df5eaaab2" +moment@2.x.x: + version "2.20.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.20.1.tgz#d6eb1a46cbcc14a2b2f9434112c1ff8907f313fd" + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -4704,6 +4801,10 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" +ms@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + mustache@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.0.tgz#4028f7778b17708a489930a6e52ac3bca0da41d0" @@ -4884,7 +4985,7 @@ number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" -nuxt@^1.3.0: +nuxt@latest: version "1.3.0" resolved "https://registry.yarnpkg.com/nuxt/-/nuxt-1.3.0.tgz#a862818aa39e8a8c2b60ede73cba4ff8628f1a5d" dependencies: @@ -7033,6 +7134,12 @@ to-regex@^3.0.1: extend-shallow "^2.0.1" regex-not "^1.0.0" +topo@1.x.x: + version "1.1.0" + resolved "https://registry.yarnpkg.com/topo/-/topo-1.1.0.tgz#e9d751615d1bb87dc865db182fa1ca0a5ef536d5" + dependencies: + hoek "2.x.x" + toposort@^1.0.0: version "1.0.6" resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.6.tgz#c31748e55d210effc00fdcdc7d6e68d7d7bb9cec"