mirror of
https://github.com/hanzoai/node-jsonwebtoken.git
synced 2026-09-24 17:00:48 +00:00
This option allows you to keep a reference to the raw token payload after claims have been applied to it but before it has been encoded.
192 lines
6.3 KiB
JavaScript
192 lines
6.3 KiB
JavaScript
var timespan = require('./lib/timespan');
|
|
var xtend = require('xtend');
|
|
var jws = require('jws');
|
|
var includes = require('lodash.includes');
|
|
var isBoolean = require('lodash.isboolean');
|
|
var isInteger = require('lodash.isinteger');
|
|
var isNumber = require('lodash.isnumber');
|
|
var isPlainObject = require('lodash.isplainobject');
|
|
var isString = require('lodash.isstring');
|
|
var once = require('lodash.once');
|
|
|
|
var sign_options_schema = {
|
|
expiresIn: { isValid: function(value) { return isInteger(value) || isString(value); }, message: '"expiresIn" should be a number of seconds or string representing a timespan' },
|
|
notBefore: { isValid: function(value) { return isInteger(value) || isString(value); }, message: '"notBefore" should be a number of seconds or string representing a timespan' },
|
|
audience: { isValid: function(value) { return isString(value) || Array.isArray(value); }, message: '"audience" must be a string or array' },
|
|
algorithm: { isValid: includes.bind(null, ['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512', 'none']), message: '"algorithm" must be a valid string enum value' },
|
|
header: { isValid: isPlainObject, message: '"header" must be an object' },
|
|
encoding: { isValid: isString, message: '"encoding" must be a string' },
|
|
issuer: { isValid: isString, message: '"issuer" must be a string' },
|
|
subject: { isValid: isString, message: '"subject" must be a string' },
|
|
jwtid: { isValid: isString, message: '"jwtid" must be a string' },
|
|
noTimestamp: { isValid: isBoolean, message: '"noTimestamp" must be a boolean' },
|
|
keyid: { isValid: isString, message: '"keyid" must be a string' },
|
|
mutatePayload: { isValid: isBoolean, message: '"mutatePayload" must be a boolean' }
|
|
};
|
|
|
|
var registered_claims_schema = {
|
|
iat: { isValid: isNumber, message: '"iat" should be a number of seconds' },
|
|
exp: { isValid: isNumber, message: '"exp" should be a number of seconds' },
|
|
nbf: { isValid: isNumber, message: '"nbf" should be a number of seconds' }
|
|
};
|
|
|
|
function validate(schema, allowUnknown, object, parameterName) {
|
|
if (!isPlainObject(object)) {
|
|
throw new Error('Expected "' + parameterName + '" to be a plain object.');
|
|
}
|
|
Object.keys(object)
|
|
.forEach(function(key) {
|
|
var validator = schema[key];
|
|
if (!validator) {
|
|
if (!allowUnknown) {
|
|
throw new Error('"' + key + '" is not allowed in "' + parameterName + '"');
|
|
}
|
|
return;
|
|
}
|
|
if (!validator.isValid(object[key])) {
|
|
throw new Error(validator.message);
|
|
}
|
|
});
|
|
}
|
|
|
|
function validateOptions(options) {
|
|
return validate(sign_options_schema, false, options, 'options');
|
|
}
|
|
|
|
function validatePayload(payload) {
|
|
return validate(registered_claims_schema, true, payload, 'payload');
|
|
}
|
|
|
|
var options_to_payload = {
|
|
'audience': 'aud',
|
|
'issuer': 'iss',
|
|
'subject': 'sub',
|
|
'jwtid': 'jti'
|
|
};
|
|
|
|
var options_for_objects = [
|
|
'expiresIn',
|
|
'notBefore',
|
|
'noTimestamp',
|
|
'audience',
|
|
'issuer',
|
|
'subject',
|
|
'jwtid',
|
|
];
|
|
|
|
module.exports = function (payload, secretOrPrivateKey, options, callback) {
|
|
if (typeof options === 'function') {
|
|
callback = options;
|
|
options = {};
|
|
} else {
|
|
options = options || {};
|
|
}
|
|
|
|
var isObjectPayload = typeof payload === 'object' &&
|
|
!Buffer.isBuffer(payload);
|
|
|
|
var header = xtend({
|
|
alg: options.algorithm || 'HS256',
|
|
typ: isObjectPayload ? 'JWT' : undefined,
|
|
kid: options.keyid
|
|
}, options.header);
|
|
|
|
function failure(err) {
|
|
if (callback) {
|
|
return callback(err);
|
|
}
|
|
throw err;
|
|
}
|
|
|
|
if (!secretOrPrivateKey && options.algorithm !== 'none') {
|
|
return failure(new Error('secretOrPrivateKey must have a value'));
|
|
}
|
|
|
|
if (typeof payload === 'undefined') {
|
|
return failure(new Error('payload is required'));
|
|
} else if (isObjectPayload) {
|
|
try {
|
|
validatePayload(payload);
|
|
}
|
|
catch (error) {
|
|
return failure(error);
|
|
}
|
|
if (!options.mutatePayload) {
|
|
payload = xtend(payload);
|
|
}
|
|
} else {
|
|
var invalid_options = options_for_objects.filter(function (opt) {
|
|
return typeof options[opt] !== 'undefined';
|
|
});
|
|
|
|
if (invalid_options.length > 0) {
|
|
return failure(new Error('invalid ' + invalid_options.join(',') + ' option for ' + (typeof payload ) + ' payload'));
|
|
}
|
|
}
|
|
|
|
if (typeof payload.exp !== 'undefined' && typeof options.expiresIn !== 'undefined') {
|
|
return failure(new Error('Bad "options.expiresIn" option the payload already has an "exp" property.'));
|
|
}
|
|
|
|
if (typeof payload.nbf !== 'undefined' && typeof options.notBefore !== 'undefined') {
|
|
return failure(new Error('Bad "options.notBefore" option the payload already has an "nbf" property.'));
|
|
}
|
|
|
|
try {
|
|
validateOptions(options);
|
|
}
|
|
catch (error) {
|
|
return failure(error);
|
|
}
|
|
|
|
var timestamp = payload.iat || Math.floor(Date.now() / 1000);
|
|
|
|
if (!options.noTimestamp) {
|
|
payload.iat = timestamp;
|
|
} else {
|
|
delete payload.iat;
|
|
}
|
|
|
|
if (typeof options.notBefore !== 'undefined') {
|
|
payload.nbf = timespan(options.notBefore, timestamp);
|
|
if (typeof payload.nbf === 'undefined') {
|
|
return failure(new Error('"notBefore" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'));
|
|
}
|
|
}
|
|
|
|
if (typeof options.expiresIn !== 'undefined' && typeof payload === 'object') {
|
|
payload.exp = timespan(options.expiresIn, timestamp);
|
|
if (typeof payload.exp === 'undefined') {
|
|
return failure(new Error('"expiresIn" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'));
|
|
}
|
|
}
|
|
|
|
Object.keys(options_to_payload).forEach(function (key) {
|
|
var claim = options_to_payload[key];
|
|
if (typeof options[key] !== 'undefined') {
|
|
if (typeof payload[claim] !== 'undefined') {
|
|
return failure(new Error('Bad "options.' + key + '" option. The payload already has an "' + claim + '" property.'));
|
|
}
|
|
payload[claim] = options[key];
|
|
}
|
|
});
|
|
|
|
var encoding = options.encoding || 'utf8';
|
|
|
|
if (typeof callback === 'function') {
|
|
callback = callback && once(callback);
|
|
|
|
jws.createSign({
|
|
header: header,
|
|
privateKey: secretOrPrivateKey,
|
|
payload: payload,
|
|
encoding: encoding
|
|
}).once('error', callback)
|
|
.once('done', function (signature) {
|
|
callback(null, signature);
|
|
});
|
|
} else {
|
|
return jws.sign({header: header, payload: payload, secret: secretOrPrivateKey, encoding: encoding});
|
|
}
|
|
};
|