├── .babelrc ├── LICENCE.md ├── README.md ├── build.js ├── circle.yml ├── dist ├── vue-auth.js └── vue-auth.min.js ├── karma.conf.js ├── package.json ├── release.sh ├── src ├── index.js └── util │ └── jwt.js └── test └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "development": { 4 | "presets": ["es2015"] 5 | }, 6 | "production": { 7 | "presets": ["es2015-rollup"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Wade Urry 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-auth 2 | 3 | [![CircleCI](https://img.shields.io/circleci/project/iWader/vue-auth/master.svg)](https://circleci.com/gh/iWader/vue-auth) [![NPM](https://img.shields.io/npm/v/vue-auth.svg)](https://www.npmjs.com/package/vue-auth) 4 | 5 | Vue plugin for easily managing your app's auth state 6 | 7 | This is a work in progress and the API may change without notice 8 | 9 | ## Installation 10 | 11 | ```javascript 12 | npm install vue-auth --save 13 | ``` 14 | 15 | Looking for Vue 2 support? 16 | ```javascript 17 | npm install vue-auth@next --save 18 | ``` 19 | 20 | ```javascript 21 | var Vue = require('vue') 22 | var VueAuth = require('vue-auth') 23 | 24 | Vue.use(VueAuth) 25 | ``` 26 | 27 | ## Usage 28 | 29 | To access the auth object you'll find the `$auth` property on your application instance. (e.g `this.$auth.getToken()`) 30 | 31 | #### Options 32 | 33 | - `storagePrefix` - Prefix to the storage keys used in localStorage 34 | - `authPath` - URI the user should be redirected to to re-authenticate 35 | - `redirectType` - May be either `router` or `browser`. Which method should the user be redirected with? 36 | 37 | Setting options is done the same as all Vue plugins. 38 | 39 | ```javascript 40 | Vue.use(VueAuth, { 41 | storagePrefix: '_prefix.', 42 | redirectType: 'browser' 43 | }) 44 | ``` 45 | 46 | ##### Methods 47 | 48 | - `getToken()` 49 | - `setToken(token)` 50 | - `removeToken()` 51 | - `hasToken()` 52 | - `isAuthenticated()` 53 | - `getUserData()` 54 | - `setUserData(data)` 55 | 56 | ## Licence 57 | 58 | Copyright (c) 2016 Wade Urry - Released under the [MIT Licence](LICENCE.md) 59 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | // Borrowed from https://github.com/vuejs/vuex/blob/master/build/build.js 2 | 3 | process.env.BABEL_ENV = 'production' 4 | 5 | var fs = require('fs'), 6 | rollup = require('rollup'), 7 | babel = require('rollup-plugin-babel'), 8 | nodeResolve = require('rollup-plugin-node-resolve'), 9 | commonjs = require('rollup-plugin-commonjs'), 10 | uglify = require('uglify-js'), 11 | version = process.env.VERSION || require('./package.json').version 12 | 13 | var banner = 14 | '/*!\n' + 15 | ' * vue-auth v' + version + '\n' + 16 | ' * (c) ' + new Date().getFullYear() + ' Wade Urry\n' + 17 | ' * Released under the MIT License.\n' + 18 | ' */' 19 | 20 | rollup.rollup({ 21 | entry: 'src/index.js', 22 | plugins: [ 23 | nodeResolve(), 24 | commonjs({ 25 | include: 'node_modules/**' 26 | }), 27 | babel() 28 | ] 29 | }) 30 | 31 | .then(function(bundle) { 32 | return write('dist/vue-auth.js', bundle.generate({ 33 | format: 'umd', 34 | banner: banner, 35 | moduleName: 'VueAuth' 36 | }).code) 37 | }) 38 | 39 | .then(function() { 40 | return rollup.rollup({ 41 | entry: 'src/index.js', 42 | plugins: [ 43 | nodeResolve(), 44 | commonjs({ 45 | include: 'node_modules/**' 46 | }), 47 | babel() 48 | ] 49 | }) 50 | }) 51 | 52 | .then(function(bundle) { 53 | var code = bundle.generate({ 54 | format: 'umd', 55 | moduleName: 'VueAuth' 56 | }).code 57 | 58 | var minified = banner + '\n' + uglify.minify(code, { 59 | fromString: true, 60 | output: { 61 | ascii_only: true 62 | } 63 | }).code 64 | 65 | return write('dist/vue-auth.min.js', minified) 66 | }) 67 | 68 | .catch(logError) 69 | 70 | function write(dest, code) { 71 | return new Promise(function(resolve, reject) { 72 | fs.writeFile(dest, code, function(err) { 73 | if (err) return reject(err) 74 | console.log(blue(dest) + ' ' + getSize(code)); 75 | resolve(); 76 | }) 77 | }) 78 | } 79 | 80 | function getSize(code) { 81 | return (code.length / 1024).toFixed(2) + 'kb' 82 | } 83 | 84 | function logError(err) { 85 | console.log(err) 86 | } 87 | 88 | function blue(str) { 89 | return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m' 90 | } 91 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 5 -------------------------------------------------------------------------------- /dist/vue-auth.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * vue-auth v1.0.0-alpha.1 3 | * (c) 2016 Wade Urry 4 | * Released under the MIT License. 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 8 | typeof define === 'function' && define.amd ? define(factory) : 9 | (global.VueAuth = factory()); 10 | }(this, (function () { 'use strict'; 11 | 12 | function createCommonjsModule(fn, module) { 13 | return module = { exports: {} }, fn(module, module.exports), module.exports; 14 | } 15 | 16 | var base64 = createCommonjsModule(function (module, exports) { 17 | (function () { 18 | 19 | var object = typeof exports != 'undefined' ? exports : self; // #8: web workers 20 | var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; 21 | 22 | function InvalidCharacterError(message) { 23 | this.message = message; 24 | } 25 | InvalidCharacterError.prototype = new Error(); 26 | InvalidCharacterError.prototype.name = 'InvalidCharacterError'; 27 | 28 | // encoder 29 | // [https://gist.github.com/999166] by [https://github.com/nignag] 30 | object.btoa || (object.btoa = function (input) { 31 | var str = String(input); 32 | for ( 33 | // initialize result and counter 34 | var block, charCode, idx = 0, map = chars, output = ''; 35 | // if the next str index does not exist: 36 | // change the mapping table to "=" 37 | // check if d has no fractional digits 38 | str.charAt(idx | 0) || (map = '=', idx % 1); 39 | // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8 40 | output += map.charAt(63 & block >> 8 - idx % 1 * 8)) { 41 | charCode = str.charCodeAt(idx += 3 / 4); 42 | if (charCode > 0xFF) { 43 | throw new InvalidCharacterError("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range."); 44 | } 45 | block = block << 8 | charCode; 46 | } 47 | return output; 48 | }); 49 | 50 | // decoder 51 | // [https://gist.github.com/1020396] by [https://github.com/atk] 52 | object.atob || (object.atob = function (input) { 53 | var str = String(input).replace(/=+$/, ''); 54 | if (str.length % 4 == 1) { 55 | throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded."); 56 | } 57 | for ( 58 | // initialize result and counters 59 | var bc = 0, bs, buffer, idx = 0, output = ''; 60 | // get next character 61 | buffer = str.charAt(idx++); 62 | // character found in table? initialize bit storage and add its ascii value; 63 | ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, 64 | // and if not first of each 4 characters, 65 | // convert the first 8 bits to one ascii character 66 | bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0) { 67 | // try to find character in table (0-63, not found => -1) 68 | buffer = chars.indexOf(buffer); 69 | } 70 | return output; 71 | }); 72 | })(); 73 | }); 74 | 75 | var JWT = { 76 | decode: function decode(token) { 77 | 78 | var parts = token.split('.'); 79 | 80 | var encoded = parts[1].replace(/-/g, '+').replace(/_/g, '/'); 81 | switch (encoded.length % 4) { 82 | case 0: 83 | break; 84 | case 2: 85 | encoded += '=='; 86 | break; 87 | case 3: 88 | encoded += '='; 89 | break; 90 | } 91 | 92 | return JSON.parse(decodeURIComponent(base64.atob(encoded))); 93 | }, 94 | getDeadline: function getDeadline(token) { 95 | 96 | var decoded = this.decode(token); 97 | 98 | if (typeof decoded.exp === 'undefined') return null; 99 | 100 | var deadline = new Date(0); 101 | 102 | deadline.setUTCSeconds(decoded.exp); 103 | 104 | return deadline; 105 | }, 106 | isExpired: function isExpired(token) { 107 | 108 | var deadline = this.getDeadline(token); 109 | 110 | if (deadline === null) return false; 111 | 112 | var now = new Date(); 113 | 114 | return deadline.valueOf() <= now.valueOf(); 115 | } 116 | }; 117 | 118 | function plugin(Vue, options) { 119 | 120 | if (plugin.installed) { 121 | return; 122 | } 123 | 124 | options = options || {}; 125 | 126 | Vue.auth = { 127 | 128 | storagePrefix: options.storagePrefix || '_auth.', 129 | redirectType: options.redirectType || 'router', 130 | authPath: options.authPath || '/login', 131 | userData: undefined, 132 | 133 | getStorageKey: function getStorageKey(part) { 134 | 135 | return this.storagePrefix + part; 136 | }, 137 | setUserData: function setUserData(data) { 138 | 139 | this.userData = data; 140 | 141 | return localStorage.setItem(this.getStorageKey('user'), JSON.stringify(data)); 142 | }, 143 | getUserData: function getUserData() { 144 | 145 | return JSON.parse(localStorage.getItem(this.getStorageKey('user'))); 146 | }, 147 | setToken: function setToken(token) { 148 | 149 | return localStorage.setItem(this.getStorageKey('token'), token); 150 | }, 151 | getToken: function getToken() { 152 | 153 | return localStorage.getItem(this.getStorageKey('token')); 154 | }, 155 | removeToken: function removeToken() { 156 | 157 | return localStorage.removeItem(this.getStorageKey('token')); 158 | }, 159 | hasToken: function hasToken() { 160 | 161 | return this.getToken() !== null; 162 | }, 163 | isAuthenticated: function isAuthenticated() { 164 | 165 | return !JWT.isExpired(this.getToken()); 166 | }, 167 | installInterceptor: function installInterceptor(instance) { 168 | var _this = this; 169 | 170 | if (typeof Vue.http === 'undefined') { 171 | throw new Error('Please install vue-resource before attempting to install the interceptor.'); 172 | } 173 | 174 | return function (request, next) { 175 | request.headers['Authorization'] = 'Bearer ' + _this.getToken(); 176 | 177 | next(function (response) { 178 | if (response.status === 401) { 179 | instance.$auth.setToken(undefined); 180 | 181 | if (instance.$auth.redirectType === 'browser') { 182 | window.location.href = instance.$auth.authPath; 183 | } else { 184 | instance.$router.go(instance.$auth.authPath); 185 | } 186 | } 187 | 188 | return response; 189 | }); 190 | }; 191 | } 192 | }; 193 | 194 | Object.defineProperties(Vue.prototype, { 195 | 196 | $auth: { 197 | get: function get() { 198 | return Vue.auth; 199 | } 200 | } 201 | 202 | }); 203 | } 204 | 205 | if (typeof window !== 'undefined' && window.Vue) { 206 | window.Vue.use(plugin); 207 | } 208 | 209 | return plugin; 210 | 211 | }))); 212 | -------------------------------------------------------------------------------- /dist/vue-auth.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * vue-auth v1.0.0-alpha.1 3 | * (c) 2016 Wade Urry 4 | * Released under the MIT License. 5 | */ 6 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.VueAuth=t()}(this,function(){"use strict";function e(e,t){return t={exports:{}},e(t,t.exports),t.exports}function t(e,r){t.installed||(r=r||{},e.auth={storagePrefix:r.storagePrefix||"_auth.",redirectType:r.redirectType||"router",authPath:r.authPath||"/login",userData:void 0,getStorageKey:function(e){return this.storagePrefix+e},setUserData:function(e){return this.userData=e,localStorage.setItem(this.getStorageKey("user"),JSON.stringify(e))},getUserData:function(){return JSON.parse(localStorage.getItem(this.getStorageKey("user")))},setToken:function(e){return localStorage.setItem(this.getStorageKey("token"),e)},getToken:function(){return localStorage.getItem(this.getStorageKey("token"))},removeToken:function(){return localStorage.removeItem(this.getStorageKey("token"))},hasToken:function(){return null!==this.getToken()},isAuthenticated:function(){return!n.isExpired(this.getToken())},installInterceptor:function(t){var r=this;if("undefined"==typeof e.http)throw new Error("Please install vue-resource before attempting to install the interceptor.");return function(e,n){e.headers.Authorization="Bearer "+r.getToken(),n(function(e){return 401===e.status&&(t.$auth.setToken(void 0),"browser"===t.$auth.redirectType?window.location.href=t.$auth.authPath:t.$router.go(t.$auth.authPath)),e})}}},Object.defineProperties(e.prototype,{$auth:{get:function(){return e.auth}}}))}var r=e(function(e,t){!function(){function e(e){this.message=e}var r="undefined"!=typeof t?t:self,n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";e.prototype=new Error,e.prototype.name="InvalidCharacterError",r.btoa||(r.btoa=function(t){for(var r,o,a=String(t),i=0,u=n,s="";a.charAt(0|i)||(u="=",i%1);s+=u.charAt(63&r>>8-i%1*8)){if(o=a.charCodeAt(i+=.75),o>255)throw new e("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.");r=r<<8|o}return s}),r.atob||(r.atob=function(t){var r=String(t).replace(/=+$/,"");if(r.length%4==1)throw new e("'atob' failed: The string to be decoded is not correctly encoded.");for(var o,a,i=0,u=0,s="";a=r.charAt(u++);~a&&(o=i%4?64*o+a:a,i++%4)?s+=String.fromCharCode(255&o>>(-2*i&6)):0)a=n.indexOf(a);return s})}()}),n={decode:function(e){var t=e.split("."),n=t[1].replace(/-/g,"+").replace(/_/g,"/");switch(n.length%4){case 0:break;case 2:n+="==";break;case 3:n+="="}return JSON.parse(decodeURIComponent(r.atob(n)))},getDeadline:function(e){var t=this.decode(e);if("undefined"==typeof t.exp)return null;var r=new Date(0);return r.setUTCSeconds(t.exp),r},isExpired:function(e){var t=this.getDeadline(e);if(null===t)return!1;var r=new Date;return t.valueOf()<=r.valueOf()}};return"undefined"!=typeof window&&window.Vue&&window.Vue.use(t),t}); -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | basePath: '', 4 | frameworks: ['jasmine'], 5 | browsers: ['PhantomJS'], 6 | files: [ 7 | './test/*.js' 8 | ], 9 | preprocessors: { 10 | './test/*.js': ['webpack'] 11 | }, 12 | webpack: { 13 | module: { 14 | devtool: 'inline-source-map', 15 | loaders: [ 16 | { 17 | exclude: /node_modules/, 18 | loader: 'babel-loader', 19 | test: /\.js$/, 20 | query: { 21 | plugins: ['transform-runtime'], 22 | presets: ['es2015'] 23 | } 24 | } 25 | ] 26 | } 27 | }, 28 | webpackMiddleware: { 29 | noInfo: true, 30 | }, 31 | singleRun: true 32 | }); 33 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-auth", 3 | "version": "1.0.0-alpha.1", 4 | "description": "Vue plugin for easily managing auth state", 5 | "main": "dist/vue-auth.js", 6 | "scripts": { 7 | "test": "./node_modules/karma/bin/karma start", 8 | "build": "node ./build.js" 9 | }, 10 | "files": [ 11 | "dist", 12 | "src" 13 | ], 14 | "keywords": [ 15 | "vue", 16 | "auth", 17 | "authentication", 18 | "jwt", 19 | "oauth", 20 | "token" 21 | ], 22 | "repository": { 23 | "type": "git", 24 | "url": "git@github.com:iWader/vue-auth.git" 25 | }, 26 | "author": { 27 | "name": "Wade Urry", 28 | "email": "wade@iwader.co.uk", 29 | "url": "https://iwader.co.uk" 30 | }, 31 | "license": "MIT", 32 | "dependencies": { 33 | "Base64": "^1.0.0" 34 | }, 35 | "devDependencies": { 36 | "babel-loader": "^6.2.5", 37 | "babel-plugin-transform-runtime": "^6.12.0", 38 | "babel-preset-es2015": "^6.14.0", 39 | "babel-preset-es2015-rollup": "^1.2.0", 40 | "jasmine": "^2.5.0", 41 | "karma": "^1.2.0", 42 | "karma-babel-preprocessor": "^6.0.1", 43 | "karma-jasmine": "^1.0.2", 44 | "karma-phantomjs-launcher": "^1.0.2", 45 | "karma-webpack": "^1.8.0", 46 | "rollup": "^0.35.15", 47 | "rollup-plugin-babel": "^2.6.1", 48 | "rollup-plugin-commonjs": "^5.0.4", 49 | "rollup-plugin-node-resolve": "^2.0.0", 50 | "uglify-js": "^2.7.3", 51 | "vue": "^1.0.26", 52 | "webpack": "^1.13.2" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | echo "Enter release version: " 5 | read VERSION 6 | 7 | read -p "Releasing $VERSION - are you sure? (y/n)" -n 1 -r 8 | echo # (optional) move to a new line 9 | 10 | if [[ $REPLY =~ ^[Yy]$ ]] 11 | then 12 | echo "Releasing $VERSION ..." 13 | 14 | # run tests 15 | npm test 2> /dev/null 16 | 17 | # build 18 | VERSION=$VERSION npm run build 19 | 20 | # commit 21 | git add -A 22 | git commit -m "[build] $VERSION" 23 | npm version $VERSION --message "[release] $VERSION" 24 | 25 | # publish 26 | git push origin refs/tags/v$VERSION 27 | git push 28 | npm publish 29 | fi 30 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import JWT from './util/jwt' 2 | 3 | function plugin(Vue, options) { 4 | 5 | if (plugin.installed) { 6 | return; 7 | } 8 | 9 | options = options || {}; 10 | 11 | Vue.auth = { 12 | 13 | storagePrefix: options.storagePrefix || '_auth.', 14 | redirectType: options.redirectType || 'router', 15 | authPath: options.authPath || '/login', 16 | userData: undefined, 17 | 18 | getStorageKey(part) { 19 | 20 | return this.storagePrefix + part 21 | 22 | }, 23 | 24 | setUserData(data) { 25 | 26 | this.userData = data; 27 | 28 | return localStorage.setItem(this.getStorageKey('user'), JSON.stringify(data)); 29 | 30 | }, 31 | 32 | getUserData() { 33 | 34 | return JSON.parse(localStorage.getItem(this.getStorageKey('user'))) 35 | 36 | }, 37 | 38 | setToken(token) { 39 | 40 | return localStorage.setItem(this.getStorageKey('token'), token) 41 | 42 | }, 43 | 44 | getToken() { 45 | 46 | return localStorage.getItem(this.getStorageKey('token')) 47 | 48 | }, 49 | 50 | removeToken() { 51 | 52 | return localStorage.removeItem(this.getStorageKey('token')) 53 | 54 | }, 55 | 56 | hasToken() { 57 | 58 | return this.getToken() !== null 59 | 60 | }, 61 | 62 | isAuthenticated() { 63 | 64 | return ! JWT.isExpired(this.getToken()); 65 | 66 | }, 67 | 68 | installInterceptor(instance) { 69 | 70 | if (typeof Vue.http === 'undefined') { 71 | throw new Error('Please install vue-resource before attempting to install the interceptor.'); 72 | } 73 | 74 | return (request, next) => { 75 | request.headers['Authorization'] = 'Bearer ' + this.getToken(); 76 | 77 | next(response => { 78 | if (response.status === 401) { 79 | instance.$auth.setToken(undefined); 80 | 81 | if (instance.$auth.redirectType === 'browser') { 82 | window.location.href = instance.$auth.authPath; 83 | } 84 | else { 85 | instance.$router.go(instance.$auth.authPath); 86 | } 87 | } 88 | 89 | return response; 90 | }); 91 | } 92 | } 93 | 94 | }; 95 | 96 | Object.defineProperties(Vue.prototype, { 97 | 98 | $auth: { 99 | get() { 100 | return Vue.auth; 101 | } 102 | } 103 | 104 | }); 105 | 106 | } 107 | 108 | if (typeof window !== 'undefined' && window.Vue) { 109 | window.Vue.use(plugin); 110 | } 111 | 112 | export default plugin 113 | -------------------------------------------------------------------------------- /src/util/jwt.js: -------------------------------------------------------------------------------- 1 | import Base64 from 'Base64' 2 | 3 | export default { 4 | 5 | decode(token) { 6 | 7 | const parts = token.split('.'); 8 | 9 | var encoded = parts[1].replace(/-/g, '+').replace(/_/g, '/'); 10 | switch (encoded.length % 4) { 11 | case 0: 12 | break; 13 | case 2: 14 | encoded += '=='; 15 | break; 16 | case 3: 17 | encoded += '='; 18 | break; 19 | } 20 | 21 | return JSON.parse(decodeURIComponent(Base64.atob(encoded))); 22 | 23 | }, 24 | 25 | getDeadline(token) { 26 | 27 | const decoded = this.decode(token); 28 | 29 | if (typeof decoded.exp === 'undefined') return null; 30 | 31 | var deadline = new Date(0); 32 | 33 | deadline.setUTCSeconds(decoded.exp); 34 | 35 | return deadline; 36 | 37 | }, 38 | 39 | isExpired(token) { 40 | 41 | const deadline = this.getDeadline(token); 42 | 43 | if (deadline === null) return false; 44 | 45 | const now = new Date(); 46 | 47 | return deadline.valueOf() <= now.valueOf(); 48 | 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueAuth from '../src/index' 3 | import JWT from '../src/util/jwt' 4 | 5 | Vue.use(VueAuth); 6 | 7 | describe('vue-auth', function() { 8 | afterEach(function () { 9 | localStorage.removeItem('_auth.token') 10 | localStorage.removeItem('_auth.user') 11 | localStorage.removeItem('_prefix.token') 12 | localStorage.removeItem('_prefix.user') 13 | }); 14 | 15 | it('to call vue constructor with no arguments', function() { 16 | expect(function() { 17 | new Vue(); 18 | }).not.toThrow(); 19 | }); 20 | 21 | it('to have $auth property', function() { 22 | var vm = new Vue(); 23 | 24 | expect(vm.$auth).not.toBeUndefined(); 25 | }) 26 | 27 | it('to return the correct storage key', function() { 28 | var vm = new Vue(); 29 | 30 | expect(vm.$auth.getStorageKey('token')).toBe('_auth.token'); 31 | 32 | vm.$auth.storagePrefix = '_prefix.'; 33 | 34 | expect(vm.$auth.getStorageKey('user')).toBe('_prefix.user'); 35 | }); 36 | 37 | it('to store and retrieve an auth token', function() { 38 | var vm = new Vue(); 39 | 40 | vm.$auth.setToken('kebab-news-thing'); 41 | 42 | expect(vm.$auth.getToken()).toBe('kebab-news-thing'); 43 | }) 44 | 45 | it('to store and retrieve user data', function() { 46 | var vm = new Vue(); 47 | 48 | const dataIn = {name: 'Jane Doe', age: 20}; 49 | 50 | vm.$auth.setUserData(dataIn); 51 | 52 | const dataOut = vm.$auth.getUserData(); 53 | 54 | expect(dataOut.name).toBeDefined(); 55 | expect(dataOut.name).toEqual('Jane Doe'); 56 | expect(dataOut.age).toBeDefined(); 57 | expect(dataOut.age).toEqual(20); 58 | }) 59 | 60 | it('to detect is has a token stored', function() { 61 | var vm = new Vue(); 62 | 63 | expect(vm.$auth.hasToken()).toBe(false); 64 | 65 | vm.$auth.setToken('kebab-news-thing'); 66 | 67 | expect(vm.$auth.hasToken()).toBe(true); 68 | }) 69 | }) 70 | 71 | describe('JWT Helper', function() { 72 | it('to decode a valid token', function() { 73 | const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOjEsIm5hbWUiOiJKYW5lIERvZSJ9.F0aM7iXee139nQE4FD5Lw5l89NxkKuFJ_2iU09MNYUk" 74 | const decoded = JWT.decode(token); 75 | 76 | expect(decoded.aud).toBeDefined() 77 | expect(decoded.aud).toBe(1) 78 | expect(decoded.name).toBeDefined() 79 | expect(decoded.name).toBe('Jane Doe') 80 | }) 81 | 82 | it('to decode and convert the expiry time', function() { 83 | // Exp: 2524608000 84 | const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIxIiwiZXhwIjoyNTI0NjA4MDAwfQ.egn6G7vB80nH3NwlgUZ9bwUAlLnkEV8kR8PN0edKCJI" 85 | 86 | // Missing exp property 87 | const missing = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIxIn0.ClQFOEhtNQtmx71eSzmUhW1lfs9LHGCsT-Y4v8LHE5k" 88 | 89 | expect(JWT.getDeadline(token).getTime()).toBe(2524608000000) 90 | expect(JWT.getDeadline(missing)).toBe(null) 91 | }) 92 | 93 | it('to correctly test if a token has expired', function() { 94 | // Expired in 2000 95 | const expired = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIxIiwiZXhwIjo5NDY2ODQ4MDB9.4DPaehBA1-6ll6E6xiSpGjqv9P9X1yOCj1-I6tyyuv8" 96 | 97 | // Expires in 2050 98 | const unexpired = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIxIiwiZXhwIjoyNTI0NjA4MDAwfQ.egn6G7vB80nH3NwlgUZ9bwUAlLnkEV8kR8PN0edKCJI" 99 | 100 | // Missing exp property 101 | const missing = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIxIn0.ClQFOEhtNQtmx71eSzmUhW1lfs9LHGCsT-Y4v8LHE5k" 102 | 103 | expect(JWT.isExpired(expired)).toBe(true) 104 | expect(JWT.isExpired(unexpired)).toBe(false) 105 | expect(JWT.isExpired(missing)).toBe(false) 106 | }) 107 | }) 108 | --------------------------------------------------------------------------------