mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-11-04 03:17:00 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			215 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			215 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
var timespan = require('./lib/timespan');
 | 
						|
var PS_SUPPORTED = true
 | 
						|
var jws = require('../jws');
 | 
						|
var once = require('../lodash.once');
 | 
						|
 | 
						|
var SUPPORTED_ALGS = ['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512', 'none'];
 | 
						|
if (PS_SUPPORTED) {
 | 
						|
  SUPPORTED_ALGS.splice(3, 0, 'PS256', 'PS384', 'PS512');
 | 
						|
}
 | 
						|
 | 
						|
function isPlainObject(value) {
 | 
						|
  var type = typeof value;
 | 
						|
  return !!value && type == 'object' && !Array.isArray(value);
 | 
						|
}
 | 
						|
function isInteger(val) {
 | 
						|
  return !isNaN(val) && val !== null && !String(val).includes('.')
 | 
						|
}
 | 
						|
function isNumber(val) {
 | 
						|
  return !isNaN(val) && val !== null
 | 
						|
}
 | 
						|
function isString(val) {
 | 
						|
  return typeof val == 'string'
 | 
						|
}
 | 
						|
 | 
						|
var sign_options_schema = {
 | 
						|
  expiresIn: { isValid: function (value) { return isInteger(value) || (isString(value) && value); }, message: '"expiresIn" should be a number of seconds or string representing a timespan' },
 | 
						|
  notBefore: { isValid: function (value) { return isInteger(value) || (isString(value) && 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: function (value) { return SUPPORTED_ALGS.includes(value); }, 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: function (value) { return value === true || value === false; }, message: '"noTimestamp" must be a boolean' },
 | 
						|
  keyid: { isValid: isString, message: '"keyid" must be a string' },
 | 
						|
  mutatePayload: { isValid: function (value) { return value === true || value === false; }, 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 = Object.assign({
 | 
						|
    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 = Object.assign({}, 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) {
 | 
						|
    delete payload.iat;
 | 
						|
  } else if (isObjectPayload) {
 | 
						|
    payload.iat = timestamp;
 | 
						|
  }
 | 
						|
 | 
						|
  if (typeof options.notBefore !== 'undefined') {
 | 
						|
    try {
 | 
						|
      payload.nbf = timespan(options.notBefore, timestamp);
 | 
						|
    }
 | 
						|
    catch (err) {
 | 
						|
      return failure(err);
 | 
						|
    }
 | 
						|
    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') {
 | 
						|
    try {
 | 
						|
      payload.exp = timespan(options.expiresIn, timestamp);
 | 
						|
    }
 | 
						|
    catch (err) {
 | 
						|
      return failure(err);
 | 
						|
    }
 | 
						|
    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 });
 | 
						|
  }
 | 
						|
};
 |