feathers-authentication-client
Advanced tools
Comparing version 0.1.0 to 0.1.1
@@ -0,3 +1,9 @@ | ||
// This is an example of using the client on the server with Node.js. | ||
// Most of the code is the same for the browser with the exception | ||
// of how modules are imported and configured. It depends on how you choose | ||
// to load them. Refer to the client section of docs.feathersjs.com for more detail. | ||
// NOTE (EK): You need to uncomment the primus setup | ||
// and comment out the socket.io setup inside app.js | ||
// in order for this to work with the example app. | ||
@@ -4,0 +10,0 @@ const feathers = require('feathers/client'); |
@@ -0,1 +1,6 @@ | ||
// This is an example of using the client on the server with Node.js. | ||
// Most of the code is the same for the browser with the exception | ||
// of how modules are imported and configured. It depends on how you choose | ||
// to load them. Refer to the client section of docs.feathersjs.com for more detail. | ||
// | ||
const feathers = require('feathers/client'); | ||
@@ -2,0 +7,0 @@ const rest = require('feathers-rest/client'); |
@@ -0,1 +1,6 @@ | ||
// This is an example of using the client on the server with Node.js. | ||
// Most of the code is the same for the browser with the exception | ||
// of how modules are imported and configured. It depends on how you choose | ||
// to load them. Refer to the client section of docs.feathersjs.com for more detail. | ||
const feathers = require('feathers/client'); | ||
@@ -2,0 +7,0 @@ const socketio = require('feathers-socketio/client'); |
@@ -35,3 +35,3 @@ 'use strict'; | ||
return app.authentication.verifyJWT(hook.result.accessToken).then(function (payload) { | ||
return app.passport.verifyJWT(hook.result.accessToken).then(function (payload) { | ||
var id = payload[options.field]; | ||
@@ -38,0 +38,0 @@ |
@@ -6,4 +6,24 @@ 'use strict'; | ||
}); | ||
exports.default = init; | ||
exports.default = function () { | ||
var _hooks = require('./hooks'); | ||
var _hooks2 = _interopRequireDefault(_hooks); | ||
var _passport = require('./passport'); | ||
var _passport2 = _interopRequireDefault(_passport); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
var defaults = { | ||
header: 'authorization', | ||
cookie: 'feathers-jwt', | ||
storageKey: 'feathers-jwt', | ||
path: '/authentication', | ||
entity: 'user', | ||
service: 'users' | ||
}; | ||
function init() { | ||
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
@@ -38,23 +58,5 @@ | ||
}; | ||
}; | ||
} | ||
var _hooks = require('./hooks'); | ||
var _hooks2 = _interopRequireDefault(_hooks); | ||
var _passport = require('./passport'); | ||
var _passport2 = _interopRequireDefault(_passport); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
var defaults = { | ||
header: 'authorization', | ||
cookie: 'feathers-jwt', | ||
storageKey: 'feathers-jwt', | ||
path: '/authentication', | ||
entity: 'user', | ||
service: 'users' | ||
}; | ||
init.defaults = defaults; | ||
module.exports = exports['default']; |
@@ -13,4 +13,10 @@ 'use strict'; | ||
var _utils = require('./utils'); | ||
var _jwtDecode = require('jwt-decode'); | ||
var _jwtDecode2 = _interopRequireDefault(_jwtDecode); | ||
var _debug = require('debug'); | ||
var _debug2 = _interopRequireDefault(_debug); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -20,2 +26,4 @@ | ||
var debug = (0, _debug2.default)('feathers-authentication-client'); | ||
var Passport = function () { | ||
@@ -27,32 +35,116 @@ function Passport(app, options) { | ||
this.app = app; | ||
this.storage = app.get('storage') || this.getStorage(options.storage); | ||
if (!app.get('storage')) { | ||
var storage = (0, _utils.getStorage)(options.storage); | ||
app.set('storage', storage); | ||
} | ||
this.setJWT = this.setJWT.bind(this); | ||
this.getJWT().then(function (accessToken) { | ||
if (accessToken) { | ||
app.set('accessToken', accessToken); | ||
app.get('storage').setItem(options.storageKey, accessToken); | ||
} | ||
}); | ||
app.set('storage', this.storage); | ||
this.getJWT().then(this.setJWT); | ||
} | ||
_createClass(Passport, [{ | ||
key: 'connected', | ||
value: function connected() { | ||
var _this = this, | ||
_arguments = arguments; | ||
var app = this.app; | ||
return new Promise(function (resolve, reject) { | ||
if (app.rest) { | ||
return resolve(); | ||
} | ||
var socket = app.io || app.primus; | ||
if (!socket) { | ||
return reject(new Error('It looks like your client connection has not been configured.')); | ||
} | ||
// If the socket is not connected yet we have to wait for the `connect` event | ||
if (app.io && !socket.connected || app.primus && socket.readyState !== 3) { | ||
var connected = app.primus ? 'open' : 'connect'; | ||
debug('Waiting for socket connection'); | ||
socket.on(connected, function () { | ||
debug('Socket connected'); | ||
var emit = app.io ? 'emit' : 'send'; | ||
var disconnect = app.io ? 'disconnect' : 'end'; | ||
var reconnecting = app.io ? 'reconnecting' : 'reconnect'; | ||
var reconnected = app.io ? 'reconnect' : 'reconnected'; | ||
// If one of those events happens before `connect` the promise will be rejected | ||
// If it happens after, it will do nothing (since Promises can only resolve once) | ||
socket.on(disconnect, function () { | ||
debug('Socket disconnected'); | ||
socket.authenticated = false; | ||
socket.removeAllListeners(); | ||
}); | ||
socket.on(reconnecting, function () { | ||
debug('Socket reconnecting'); | ||
}); | ||
socket.on(reconnected, function () { | ||
debug('Socket reconnected'); | ||
// If socket was already authenticated then re-authenticate | ||
// it with the server automatically. | ||
if (socket.authenticated) { | ||
var data = { | ||
strategy: 'jwt', | ||
accessToken: app.get('accessToken') | ||
}; | ||
_this.authenticateSocket(data, socket, emit).then(_this.setJWT).catch(function (error) { | ||
debug('Error re-authenticating after socket upgrade', error); | ||
socket.authenticated = false; | ||
app.emit('reauthentication-error', error); | ||
}); | ||
} | ||
}); | ||
if (socket.io) { | ||
socket.io.engine.on('upgrade', function () { | ||
debug('Socket upgrading', _arguments); | ||
// If socket was already authenticated then re-authenticate | ||
// it with the server automatically. | ||
if (socket.authenticated) { | ||
var data = { | ||
strategy: 'jwt', | ||
accessToken: app.get('accessToken') | ||
}; | ||
_this.authenticateSocket(data, socket, emit).then(_this.setJWT).catch(function (error) { | ||
debug('Error re-authenticating after socket upgrade', error); | ||
socket.authenticated = false; | ||
app.emit('reauthentication-error', error); | ||
}); | ||
} | ||
}); | ||
} | ||
resolve(socket); | ||
}); | ||
} else { | ||
debug('Socket already connected'); | ||
resolve(socket); | ||
} | ||
}); | ||
} | ||
}, { | ||
key: 'authenticate', | ||
value: function authenticate() { | ||
var _this = this; | ||
var _this2 = this; | ||
var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var credentials = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var app = this.app; | ||
var getData = Promise.resolve(data); | ||
var getCredentials = Promise.resolve(credentials); | ||
// If no strategy was given let's try to authenticate with a stored JWT | ||
if (!data.strategy) { | ||
if (data.accessToken) { | ||
data.strategy = 'jwt'; | ||
if (!credentials.strategy) { | ||
if (credentials.accessToken) { | ||
credentials.strategy = 'jwt'; | ||
} else { | ||
getData = this.getJWT().then(function (accessToken) { | ||
getCredentials = this.getJWT().then(function (accessToken) { | ||
if (!accessToken) { | ||
@@ -66,40 +158,46 @@ return Promise.reject(new _feathersErrors2.default.NotAuthenticated('Could not find stored JWT and no authentication strategy was given')); | ||
var handleResponse = function handleResponse(response) { | ||
if (response.accessToken) { | ||
app.set('accessToken', response.accessToken); | ||
app.get('storage').setItem(_this.options.storageKey, response.accessToken); | ||
} | ||
return Promise.resolve(response); | ||
}; | ||
return getData.then(function (data) { | ||
return (0, _utils.connected)(app).then(function (socket) { | ||
return getCredentials.then(function (credentials) { | ||
return _this2.connected(app).then(function (socket) { | ||
if (app.rest) { | ||
return app.service(_this.options.path).create(data).then(handleResponse); | ||
return app.service(_this2.options.path).create(credentials).then(_this2.setJWT); | ||
} | ||
var method = app.io ? 'emit' : 'send'; | ||
return (0, _utils.authenticateSocket)(data, socket, method).then(handleResponse); | ||
var emit = app.io ? 'emit' : 'send'; | ||
return _this2.authenticateSocket(credentials, socket, emit).then(_this2.setJWT); | ||
}); | ||
}); | ||
} | ||
// Returns a promise that authenticates a socket | ||
}, { | ||
key: 'getJWT', | ||
value: function getJWT() { | ||
var _this2 = this; | ||
key: 'authenticateSocket', | ||
value: function authenticateSocket(credentials, socket, emit) { | ||
return new Promise(function (resolve, reject) { | ||
debug('Attempting to authenticate socket'); | ||
socket[emit]('authenticate', credentials, function (error, data) { | ||
if (error) { | ||
return reject(error); | ||
} | ||
var app = this.app; | ||
return new Promise(function (resolve) { | ||
var accessToken = app.get('accessToken'); | ||
if (accessToken) { | ||
return resolve(accessToken); | ||
} | ||
(0, _utils.retrieveJWT)(_this2.options.storageKey, _this2.options.cookie, app.get('storage')).then(resolve); | ||
socket.authenticated = true; | ||
debug('Socket authenticated!'); | ||
resolve(data); | ||
}); | ||
}); | ||
} | ||
}, { | ||
key: 'verifyJWT', | ||
value: function verifyJWT(data) { | ||
return (0, _utils.verifyJWT)(data); | ||
key: 'logoutSocket', | ||
value: function logoutSocket(socket, emit) { | ||
return new Promise(function (resolve, reject) { | ||
socket[emit]('logout', function (error) { | ||
if (error) { | ||
reject(error); | ||
} | ||
socket.authenticated = false; | ||
resolve(); | ||
}); | ||
}); | ||
} | ||
@@ -109,6 +207,8 @@ }, { | ||
value: function logout() { | ||
var _this3 = this; | ||
var app = this.app; | ||
app.set('accessToken', null); | ||
(0, _utils.clearCookie)(this.options.cookie); | ||
this.clearCookie(this.options.cookie); | ||
@@ -122,6 +222,119 @@ // remove the accessToken from localStorage | ||
return (0, _utils.logoutSocket)(socket, method); | ||
return _this3.logoutSocket(socket, method); | ||
} | ||
}); | ||
} | ||
}, { | ||
key: 'setJWT', | ||
value: function setJWT(data) { | ||
var accessToken = data && data.accessToken ? data.accessToken : data; | ||
if (accessToken) { | ||
this.app.set('accessToken', accessToken); | ||
this.app.get('storage').setItem(this.options.storageKey, accessToken); | ||
} | ||
return Promise.resolve(data); | ||
} | ||
}, { | ||
key: 'getJWT', | ||
value: function getJWT() { | ||
var _this4 = this; | ||
var app = this.app; | ||
return new Promise(function (resolve) { | ||
var accessToken = app.get('accessToken'); | ||
if (accessToken) { | ||
return resolve(accessToken); | ||
} | ||
return Promise.resolve(_this4.storage.getItem(_this4.options.storageKey)).then(function (jwt) { | ||
var token = jwt || _this4.getCookie(_this4.options.cookie); | ||
if (token && token !== 'null' && !_this4.payloadIsValid((0, _jwtDecode2.default)(token))) { | ||
token = undefined; | ||
} | ||
return resolve(token); | ||
}); | ||
}); | ||
} | ||
// Pass a jwt token, get back a payload if it's valid. | ||
}, { | ||
key: 'verifyJWT', | ||
value: function verifyJWT(token) { | ||
if (typeof token !== 'string') { | ||
return Promise.reject(new Error('Token provided to verifyJWT is missing or not a string')); | ||
} | ||
try { | ||
var payload = (0, _jwtDecode2.default)(token); | ||
if (this.payloadIsValid(payload)) { | ||
return Promise.resolve(payload); | ||
} | ||
return Promise.reject(new Error('Invalid token: expired')); | ||
} catch (error) { | ||
return Promise.reject(new Error('Cannot decode malformed token.')); | ||
} | ||
} | ||
// Pass a decoded payload and it will return a boolean based on if it hasn't expired. | ||
}, { | ||
key: 'payloadIsValid', | ||
value: function payloadIsValid(payload) { | ||
return payload && payload.exp * 1000 > new Date().getTime(); | ||
} | ||
}, { | ||
key: 'getCookie', | ||
value: function getCookie(name) { | ||
if (typeof document !== 'undefined') { | ||
var value = '; ' + document.cookie; | ||
var parts = value.split('; ' + name + '='); | ||
if (parts.length === 2) { | ||
return parts.pop().split(';').shift(); | ||
} | ||
} | ||
return null; | ||
} | ||
}, { | ||
key: 'clearCookie', | ||
value: function clearCookie(name) { | ||
if (typeof document !== 'undefined') { | ||
document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:01 GMT;'; | ||
} | ||
return null; | ||
} | ||
// Returns a storage implementation | ||
}, { | ||
key: 'getStorage', | ||
value: function getStorage(storage) { | ||
if (storage) { | ||
return storage; | ||
} | ||
return { | ||
store: {}, | ||
getItem: function getItem(key) { | ||
return this.store[key]; | ||
}, | ||
setItem: function setItem(key, value) { | ||
return this.store[key] = value; | ||
}, | ||
removeItem: function removeItem(key) { | ||
delete this.store[key]; | ||
return this; | ||
} | ||
}; | ||
} | ||
}]); | ||
@@ -128,0 +341,0 @@ |
{ | ||
"name": "feathers-authentication-client", | ||
"description": "The authentication plugin for feathers-client", | ||
"version": "0.1.0", | ||
"version": "0.1.1", | ||
"homepage": "https://github.com/feathersjs/feathers-authentication-client", | ||
@@ -6,0 +6,0 @@ "main": "lib/", |
@@ -41,2 +41,22 @@ # feathers-authentication-client | ||
### Handling the special re-authentication errors | ||
In the event that your server goes down or the client loses connectivity, it will automatically handle attempting to re-authenticate the socket when the client regains connectivity with the server. In order to handle an authentication failure during automatic re-authentication you need to implement the following event listener: | ||
```js | ||
const errorHandler = error => { | ||
app.authenticate({ | ||
strategy: 'local', | ||
email: 'admin@feathersjs.com', | ||
password: 'admin' | ||
}).then(response => { | ||
// You are now authenticated again | ||
}); | ||
}; | ||
// Handle when auth fails during a reconnect or a transport upgrade | ||
app.on('reauthentication-error', errorHandler) | ||
``` | ||
### Default Options | ||
@@ -43,0 +63,0 @@ |
31379
595
129
15