UNPKG

cybersource-rest-client

Version:

Node.js SDK for the CyberSource REST API

182 lines (153 loc) 7.29 kB
'use strict'; const Jwt = require('jwt-simple'); const crypto = require('crypto'); const Constants = require('../util/Constants'); const KeyCertificate = require('./KeyCertificateGenerator'); const MLEUtility = require('../util/MLEUtility'); // Constants for algorithms const JWT_ALGORITHM_RSA = 'RS256'; const JWT_ALGORITHM_HMAC = 'HS256'; const DIGEST_ALGORITHM = 'SHA-256'; /** * JWTSigToken module generates JWT tokens for API authentication. * * The JWT token contains a claim set encoded with JWT using RS256 algorithm. * For POST, PUT, and PATCH methods, a digest of the request payload is added to the claim set. * For MLE-enabled APIs, a response MLE key ID is added to the claim set. * * @module authentication/jwt/JWTSigToken */ /** * Generates the payload claim set for the JWT token based on the merchant configuration and request details. * @private * @param {Object} merchantConfig - The merchant configuration object containing necessary details. * @param {boolean} isResponseMLEForApi - Flag indicating if MLE is enabled for this API * @param {Object} logger - Logger instance for logging errors and information. */ function getPayloadClaimSet(merchantConfig, isResponseMLEForApi, logger) { const { randomUUID } = require('crypto'); const DigestGenerator = require('../payloadDigest/DigestGenerator'); const payloadClaimSet = {}; // Determine issuer const issuer = merchantConfig.getUseMetaKey() ? merchantConfig.getPortfolioID() : merchantConfig.getMerchantID(); const requestVerb = merchantConfig.getRequestType().toLowerCase(); // Add digest claims for POST / PUT / PATCH if ([Constants.POST, Constants.PUT, Constants.PATCH].includes(requestVerb)) { payloadClaimSet["digest"] = DigestGenerator.generateDigest(merchantConfig, logger); payloadClaimSet["digest-algorithm"] = DIGEST_ALGORITHM; } // Current UTC time in seconds const nowEpochSeconds = Math.floor(Date.now() / 1000); payloadClaimSet["iat"] = nowEpochSeconds; payloadClaimSet["exp"] = nowEpochSeconds + 120; // +2 minutes // Request metadata payloadClaimSet["request-host"] = merchantConfig.getRunEnvironment(); payloadClaimSet["request-resource-path"] = merchantConfig.requestTarget; payloadClaimSet["request-method"] = requestVerb; // Standard JWT claims payloadClaimSet["iss"] = issuer; payloadClaimSet["jti"] = randomUUID(); payloadClaimSet["v-c-jwt-version"] = "2"; // Merchant ID (if present) if (merchantConfig.getMerchantID() != null) { payloadClaimSet["v-c-merchant-id"] = merchantConfig.getMerchantID(); } // Optional Response MLE handling try { if (isResponseMLEForApi) { payloadClaimSet["v-c-response-mle-kid"] = MLEUtility.validateAndAutoExtractResponseMleKid(merchantConfig, logger); } } catch (err) { logger.error('JWT signature generation failed due to MLE KID validation error'); logger.error(err); throw new Error(`JWT signature generation failed: ${err.message}`); } return payloadClaimSet; } /** * Generates a JWT header claim set with a key ID. * * @param {Object} options - The options object * @param {string} [options.merchantKeyId=''] - The merchant key ID to use for the 'kid' claim * @param {Object} [options.certificate=null] - The certificate object to extract the serial number from certificate * @returns {Object} An object containing the JWT header with the 'kid' (key ID) claim set * @example * // Returns { header: { kid: '1234567890' } } * getHeaderClaimSet({ merchantKeyId: '1234567890' }); */ function getHeaderClaimSet({ merchantKeyId = '', certificate = null}) { let kidValue = ''; if(certificate != null){ kidValue = getSerialNumberFromCert(certificate); } else if(merchantKeyId){ kidValue = merchantKeyId; } const headerClaimSet = { 'header': { 'kid': kidValue } }; return headerClaimSet; } /** * Gets the serial number from the certificate's subject attributes. * @private * @param {*} cert - The certificate object containing subject attributes. * @returns {string} The serial number extracted from the certificate. */ function getSerialNumberFromCert(cert) { if (!cert.subject || !cert.subject.attributes) { throw new Error("Subject or attributes are missing in JWT P12 certificate"); } const serialNumberAttr = cert.subject.attributes.find(attr => attr.name === 'serialNumber'); if (serialNumberAttr) { return serialNumberAttr.value; } throw new Error("Serial number attribute not found in cert subject of JWT P12 certificate"); } /** * Generates a JWT token for authentication * * @param {Object} merchantConfig - Configuration containing merchant details * @param {boolean} isResponseMLEForApi - Flag indicating if MLE is enabled for this API * @param {Object} logger - Logger instance * @returns {string} The generated JWT token * @throws {Error} If token generation fails */ exports.getToken = function (merchantConfig, isResponseMLEForApi, logger) { try { const claimSetJson = getPayloadClaimSet(merchantConfig, isResponseMLEForApi, logger); let jwtToken; if (merchantConfig.isSharedSecretKeyType()) { // Shared Secret (HMAC-SHA256) signing logger.debug('Generating JWT token using shared secret (HS256)'); // Base64 decode the merchant secret key const secretKey = merchantConfig.getMerchantsecretKey(); if (!secretKey) { throw new Error('Merchant secret key is not configured'); } const secretKeyBuffer = Buffer.from(secretKey, 'base64'); if (secretKeyBuffer.length === 0 || secretKeyBuffer.toString('base64') !== secretKey) { throw new Error('Invalid base64 encoded merchant secret key'); } const customHeader = getHeaderClaimSet({ merchantKeyId: merchantConfig.getMerchantKeyID() }); // Generate JWT token using HMAC-SHA256 jwtToken = Jwt.encode(claimSetJson, secretKeyBuffer, JWT_ALGORITHM_HMAC, customHeader); } else { // P12 Certificate (RSA-SHA256) signing - existing behavior logger.debug('Generating JWT token using P12 certificate (RS256)'); const rsaPrivateKey = KeyCertificate.getRSAPrivateKey(merchantConfig, logger); const certificate = KeyCertificate.getX509CertificateInCert(merchantConfig, logger, merchantConfig.getKeyAlias()); const customHeader = getHeaderClaimSet({ certificate: certificate }); // Generate JWT token using RSA-SHA256 jwtToken = Jwt.encode(claimSetJson, rsaPrivateKey, JWT_ALGORITHM_RSA, customHeader); } return jwtToken; } catch (err) { logger.error(`JWT token generation failed: ${err.message}`); throw new Error(`Failed to generate JWT token: ${err.message}`); } };