Skip to content

Commit

Permalink
refactor: modernize codebase
Browse files Browse the repository at this point in the history
  • Loading branch information
frbuceta committed Oct 3, 2024
1 parent 6d48569 commit 0b533df
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 412 deletions.
176 changes: 61 additions & 115 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,160 +8,106 @@
const async = require('async');
const errors = require('restify-errors');
const jwt = require('jsonwebtoken');
const {unless} = require('express-unless');
const { unless } = require('express-unless');

const DEFAULT_REVOKED_FUNCTION = (_, __, cb) => {
return cb(null, false);
};
const DEFAULT_REVOKED_FUNCTION = (_, __, cb) => cb(null, false);

/**
* @param {any} object
* @return {boolean}
*/
function isFunction(object) {
return Object.prototype.toString.call(object) === '[object Function]';
}
const isFunction = (object) => typeof object === 'function';

/**
* @param {string} secret
* @return {function(*, *, *): *}
*/
function wrapStaticSecretInCallback(secret) {
return (_, __, cb) => {
return cb(null, secret);
};
}
const wrapStaticSecretInCallback = (secret) => (_, __, cb) => cb(null, secret);

module.exports = (options) => {
if (!options || !options.secret) throw new Error('secret should be set');

let secretCallback = options.secret;

if (!isFunction(secretCallback)) {
secretCallback = wrapStaticSecretInCallback(secretCallback);
if (!options || !options.secret) {
throw new Error('secret should be set');
}

const isRevokedCallback = options.isRevoked || DEFAULT_REVOKED_FUNCTION;

const _requestProperty = options.userProperty ||
options.requestProperty ||
'user';
const secretCallback = isFunction(options.secret)
? options.secret
: wrapStaticSecretInCallback(options.secret);


const credentialsRequired = typeof options.credentialsRequired === 'undefined' ?
true :
options.credentialsRequired;
const isRevokedCallback = options.isRevoked || DEFAULT_REVOKED_FUNCTION;
const requestProperty = options.userProperty || options.requestProperty || 'user';
const credentialsRequired = options.credentialsRequired !== false;

const middleware = (req, res, next) => {
let token;

if (
req.method === 'OPTIONS' &&
Object.hasOwn(req.headers, 'access-control-request-headers')
) {
const hasAuthInAccessControl = !!~req.headers[
'access-control-request-headers'
]
// Handle CORS preflight requests
if (req.method === 'OPTIONS' && req.headers['access-control-request-headers']) {
const hasAuthInAccessControl = req.headers['access-control-request-headers']
.split(',')
.map((header) => {
return header.trim();
})
.indexOf('authorization');
.map(header => header.trim())
.includes('authorization');

if (hasAuthInAccessControl) {
return next();
}
if (hasAuthInAccessControl) return next();
}

if (options.getToken && typeof options.getToken === 'function') {
try {
token = options.getToken(req);
} catch (e) {
return next(e);
}
} else if (req.headers && req.headers.authorization) {
const parts = req.headers.authorization.split(' ');
if (parts.length === 2) {
const scheme = parts[0];
const credentials = parts[1];

if (/^(?:Bearer|JWT)$/i.test(scheme)) {
token = credentials;
} else {
return next(
new errors.InvalidCredentialsError(
'Format is Authorization: Bearer [token] or Jwt [token]',
),
);
}
} else {
return next(
new errors.InvalidCredentialsError(
'Format is Authorization: Bearer [token] or Jwt [token]',
),
);
}
// Get the token from the request
try {
token = options.getToken ? options.getToken(req) : extractToken(req);
} catch (e) {
return next(e);
}

// Check if token is required
if (!token) {
if (credentialsRequired) {
return next(
new errors.InvalidCredentialsError(
'No authorization token was found',
),
);
} else {
return next();
return next(new errors.InvalidCredentialsError('No authorization token was found'));
}
return next();
}

let dToken;

let decodedToken;
try {
dToken = jwt.decode(token, {complete: true}) || {};
decodedToken = jwt.decode(token, { complete: true }) || {};
} catch {
return next(new errors.InvalidCredentialsError('The token is corrupted'));
}

async.parallel([
(callback) => { // getSecret
const arity = secretCallback.length;
if (arity === 4) {
secretCallback(req, dToken.header, dToken.payload, callback);
} else { // arity == 3
secretCallback(req, dToken.payload, callback);
}
},
(callback) => { // checkRevoked
isRevokedCallback(req, dToken.payload, callback);
},
], (err, results) => {
if (err) {
return next(err);
}
const revoked = results[1];
(callback) => getSecret(secretCallback, req, decodedToken, callback),
(callback) => checkRevoked(isRevokedCallback, req, decodedToken, callback),
], (err, [secret, revoked]) => {
if (err) return next(err);

if (revoked) {
return next(
new errors.UnauthorizedError(
'The token has been revoked.',
),
);
return next(new errors.UnauthorizedError('The token has been revoked.'));
}

const secret = results[0];

// Verify the token
jwt.verify(token, secret, options, (err, decoded) => {
if (err && credentialsRequired) {
return (err.name === 'TokenExpiredError') ?
next(new errors.UnauthorizedError('The token has expired')) :
next(new errors.InvalidCredentialsError(err));
return next(err.name === 'TokenExpiredError'
? new errors.UnauthorizedError('The token has expired')
: new errors.InvalidCredentialsError(err));
}
req[_requestProperty] = decoded;
req[requestProperty] = decoded;
next();
});
});
};

const extractToken = (req) => {
const authHeader = req.headers?.authorization;
if (!authHeader) return null;

const parts = authHeader.split(' ');
if (parts.length === 2 && /^(Bearer|JWT)$/i.test(parts[0])) {
return parts[1];
}

throw new errors.InvalidCredentialsError('Format is Authorization: Bearer [token] or Jwt [token]');
};

const getSecret = (callback, req, token, done) => {
const arity = callback.length;
return arity === 4
? callback(req, token.header, token.payload, done)
: callback(req, token.payload, done);
};

const checkRevoked = (callback, req, token, done) => callback(req, token.payload, done);

middleware.unless = unless;
return middleware;
};
};
Loading

0 comments on commit 0b533df

Please sign in to comment.