mirror of
https://github.com/Thream/socketio-jwt.git
synced 2024-07-21 09:38:31 +02:00
0f3aadfccc
customDecoded enables you to change the value of the decoded token. the decoded token is passed to the function and you can you do whatever you want with the decoded token and return it to be changed.
260 lines
7.6 KiB
JavaScript
260 lines
7.6 KiB
JavaScript
const xtend = require('xtend');
|
|
const jwt = require('jsonwebtoken');
|
|
const UnauthorizedError = require('./UnauthorizedError');
|
|
|
|
function noQsMethod (options) {
|
|
const defaults = { required: true };
|
|
options = xtend(defaults, options);
|
|
|
|
return (socket) => {
|
|
'use strict'; // Node 4.x workaround
|
|
const server = this.server || socket.server;
|
|
|
|
if (!server.$emit) {
|
|
//then is socket.io 1.0
|
|
const Namespace = Object.getPrototypeOf(server.sockets).constructor;
|
|
if (!~Namespace.events.indexOf('authenticated')) {
|
|
Namespace.events.push('authenticated');
|
|
}
|
|
}
|
|
|
|
let auth_timeout = null;
|
|
if (options.required) {
|
|
auth_timeout = setTimeout(() => {
|
|
socket.disconnect('unauthorized');
|
|
}, options.timeout || 5000);
|
|
}
|
|
|
|
socket.on('authenticate', (data) => {
|
|
if (options.required) {
|
|
clearTimeout(auth_timeout);
|
|
}
|
|
|
|
// error handler
|
|
const onError = (err, code) => {
|
|
if (err) {
|
|
code = code || 'unknown';
|
|
const error = new UnauthorizedError(code, {
|
|
message: (Object.prototype.toString.call(err) === '[object Object]' && err.message) ? err.message : err
|
|
});
|
|
|
|
let callback_timeout;
|
|
// If callback explicitly set to false, start timeout to disconnect socket
|
|
if (options.callback === false || typeof options.callback === 'number') {
|
|
if (typeof options.callback === 'number') {
|
|
if (options.callback < 0) {
|
|
// If callback is negative(invalid value), make it positive
|
|
options.callback = Math.abs(options.callback);
|
|
}
|
|
}
|
|
|
|
callback_timeout = setTimeout(() => {
|
|
socket.disconnect('unauthorized');
|
|
}, (options.callback === false ? 0 : options.callback));
|
|
}
|
|
|
|
socket.emit('unauthorized', error, () => {
|
|
if (typeof options.callback === 'number') {
|
|
clearTimeout(callback_timeout);
|
|
}
|
|
socket.disconnect('unauthorized');
|
|
});
|
|
return; // stop logic, socket will be close on next tick
|
|
}
|
|
};
|
|
|
|
const token = options.cookie ? socket.request.cookies[options.cookie] : (data ? data.token : undefined);
|
|
|
|
if (!token || typeof token !== 'string') {
|
|
return onError({ message: 'invalid token datatype' }, 'invalid_token');
|
|
}
|
|
|
|
// Store encoded JWT
|
|
socket[options.encodedPropertyName] = token;
|
|
|
|
const onJwtVerificationReady = (err, decoded) => {
|
|
if (err) {
|
|
return onError(err, 'invalid_token');
|
|
}
|
|
|
|
// success handler
|
|
const onSuccess = () => {
|
|
socket[options.decodedPropertyName] = options.customDecoded
|
|
? options.customDecoded(decoded)
|
|
: decoded;
|
|
socket.emit('authenticated');
|
|
if (server.$emit) {
|
|
server.$emit('authenticated', socket);
|
|
} else {
|
|
//try getting the current namespace otherwise fallback to all sockets.
|
|
const namespace = (server.nsps && socket.nsp &&
|
|
server.nsps[socket.nsp.name]) ||
|
|
server.sockets;
|
|
|
|
// explicit namespace
|
|
namespace.emit('authenticated', socket);
|
|
}
|
|
};
|
|
|
|
if (options.additional_auth && typeof options.additional_auth === 'function') {
|
|
options.additional_auth(decoded, onSuccess, onError);
|
|
} else {
|
|
onSuccess();
|
|
}
|
|
};
|
|
|
|
const onSecretReady = (err, secret) => {
|
|
if (err || !secret) {
|
|
return onError(err, 'invalid_secret');
|
|
}
|
|
|
|
jwt.verify(token, secret, options, onJwtVerificationReady);
|
|
};
|
|
|
|
getSecret(socket.request, options.secret, token, onSecretReady);
|
|
});
|
|
};
|
|
}
|
|
|
|
function authorize (options) {
|
|
options = xtend({ decodedPropertyName: 'decoded_token', encodedPropertyName: 'encoded_token' }, options);
|
|
|
|
if (typeof options.secret !== 'string' && typeof options.secret !== 'function') {
|
|
throw new Error(`Provided secret ${options.secret} is invalid, must be of type string or function.`);
|
|
}
|
|
|
|
if (!options.handshake) {
|
|
return noQsMethod(options);
|
|
}
|
|
|
|
const defaults = {
|
|
success: (socket, accept) => {
|
|
if (socket.request) {
|
|
accept();
|
|
} else {
|
|
accept(null, true);
|
|
}
|
|
},
|
|
fail: (error, socket, accept) => {
|
|
if (socket.request) {
|
|
accept(error);
|
|
} else {
|
|
accept(null, false);
|
|
}
|
|
}
|
|
};
|
|
|
|
const auth = xtend(defaults, options);
|
|
|
|
return (socket, accept) => {
|
|
'use strict'; // Node 4.x workaround
|
|
let token, error;
|
|
|
|
const handshake = socket.handshake;
|
|
const req = socket.request || socket;
|
|
const authorization_header = (req.headers || {}).authorization;
|
|
|
|
if (authorization_header) {
|
|
const parts = authorization_header.split(' ');
|
|
if (parts.length == 2) {
|
|
const scheme = parts[0],
|
|
credentials = parts[1];
|
|
|
|
if (scheme.toLowerCase() === 'bearer') {
|
|
token = credentials;
|
|
}
|
|
} else {
|
|
error = new UnauthorizedError('credentials_bad_format', {
|
|
message: 'Format is Authorization: Bearer [token]'
|
|
});
|
|
return auth.fail(error, socket, accept);
|
|
}
|
|
}
|
|
|
|
// Check if the header has to include authentication
|
|
if (options.auth_header_required && !token) {
|
|
return auth.fail(new UnauthorizedError('missing_authorization_header', {
|
|
message: 'Server requires Authorization Header'
|
|
}), socket, accept);
|
|
}
|
|
|
|
// Get the token from handshake or query string
|
|
if (handshake && handshake.query.token) {
|
|
token = handshake.query.token;
|
|
}
|
|
else if (req._query && req._query.token) {
|
|
token = req._query.token;
|
|
}
|
|
else if (req.query && req.query.token) {
|
|
token = req.query.token;
|
|
}
|
|
|
|
if (!token) {
|
|
error = new UnauthorizedError('credentials_required', {
|
|
message: 'no token provided'
|
|
});
|
|
return auth.fail(error, socket, accept);
|
|
}
|
|
|
|
// Store encoded JWT
|
|
socket[options.encodedPropertyName] = token;
|
|
|
|
const onJwtVerificationReady = (err, decoded) => {
|
|
if (err) {
|
|
error = new UnauthorizedError(err.code || 'invalid_token', err);
|
|
return auth.fail(error, socket, accept);
|
|
}
|
|
|
|
socket[options.decodedPropertyName] = options.customDecoded
|
|
? options.customDecoded(decoded)
|
|
: decoded;
|
|
|
|
return auth.success(socket, accept);
|
|
};
|
|
|
|
const onSecretReady = (err, secret) => {
|
|
if (err) {
|
|
error = new UnauthorizedError(err.code || 'invalid_secret', err);
|
|
return auth.fail(error, socket, accept);
|
|
}
|
|
|
|
jwt.verify(token, secret, options, onJwtVerificationReady);
|
|
};
|
|
|
|
getSecret(req, options.secret, token, onSecretReady);
|
|
};
|
|
}
|
|
|
|
function getSecret (request, secret, token, callback) {
|
|
'use strict'; // Node 4.x workaround
|
|
|
|
if (typeof secret === 'function') {
|
|
if (!token) {
|
|
return callback({ code: 'invalid_token', message: 'jwt must be provided' });
|
|
}
|
|
|
|
const parts = token.split('.');
|
|
|
|
if (parts.length < 3) {
|
|
return callback({ code: 'invalid_token', message: 'jwt malformed' });
|
|
}
|
|
|
|
if (parts[2].trim() === '') {
|
|
return callback({ code: 'invalid_token', message: 'jwt signature is required' });
|
|
}
|
|
|
|
let decodedToken = jwt.decode(token);
|
|
|
|
if (!decodedToken) {
|
|
return callback({ code: 'invalid_token', message: 'jwt malformed' });
|
|
}
|
|
|
|
secret(request, decodedToken, callback);
|
|
} else {
|
|
callback(null, secret);
|
|
}
|
|
}
|
|
|
|
exports.authorize = authorize;
|
|
exports.UnauthorizedError = UnauthorizedError;
|