UNPKG

netsuite-oauth-client

Version:

NetSuite OAuth 1.0 client library for Node.js with TypeScript support

245 lines 9.01 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.NetSuiteOAuthClient = exports.SignatureError = exports.ValidationError = exports.OAuthError = void 0; exports.createNetSuiteOAuthClient = createNetSuiteOAuthClient; exports.generateNetSuiteOAuthHeader = generateNetSuiteOAuthHeader; /* eslint-disable camelcase */ const crypto = __importStar(require("crypto")); // Constants const SIGNATURE_METHOD = 'HMAC-SHA256'; const OAUTH_VERSION = '1.0'; // Custom error classes class OAuthError extends Error { constructor(message, code) { super(message); this.code = code; this.name = 'OAuthError'; } } exports.OAuthError = OAuthError; class ValidationError extends OAuthError { constructor(message) { super(message, 'VALIDATION_ERROR'); } } exports.ValidationError = ValidationError; class SignatureError extends OAuthError { constructor(message) { super(message, 'SIGNATURE_ERROR'); } } exports.SignatureError = SignatureError; // Utility functions function validateCredentials(credentials) { const requiredFields = ['accountid', 'consumerkey', 'consumersecret', 'tokenkey', 'tokensecret']; for (const field of requiredFields) { if (!credentials[field] || typeof credentials[field] !== 'string') { throw new ValidationError(`Missing or invalid credential: ${field}`); } } } function validateRequestParams(requestUrl, httpMethod) { if (!requestUrl || typeof requestUrl !== 'string') { throw new ValidationError('Invalid or missing requestUrl'); } if (!httpMethod || typeof httpMethod !== 'string') { throw new ValidationError('Invalid or missing httpMethod'); } try { new URL(requestUrl); } catch { throw new ValidationError('Invalid URL format'); } const validMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']; if (!validMethods.includes(httpMethod.toUpperCase())) { throw new ValidationError(`Invalid HTTP method: ${httpMethod}`); } } function generateOAuthNonce() { return crypto.randomBytes(16).toString('hex'); } function percentEncode(str) { return encodeURIComponent(str) .replace(/[!'()*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase()); } function generateSignatureBaseString(credentials, requestUrl, oauthNonce, oauthTimestamp) { // 1) Always use “POST” for SuiteQL const method = "POST"; // 2) Parse out path vs. query const urlObj = new URL(requestUrl); // Base URL (no query string) const baseUrl = `${urlObj.protocol}//${urlObj.host}${urlObj.pathname}`; // 3) Build a params map containing: // • All OAuth keys // • All query-string keys from urlObj.searchParams const params = { oauth_consumer_key: credentials.consumerkey, oauth_nonce: oauthNonce, oauth_signature_method: SIGNATURE_METHOD, oauth_timestamp: oauthTimestamp, oauth_token: credentials.tokenkey, oauth_version: OAUTH_VERSION, }; // 4) Copy each query-param (e.g. “limit=1”) into params urlObj.searchParams.forEach((value, key) => { params[key] = value; }); // 5) Sort all keys alphabetically, percent-encode, and concatenate with “&” const normalized = Object.keys(params) .sort() .map((key) => `${percentEncode(key)}=${percentEncode(params[key])}`) .join("&"); // 6) Build the signature‐base string: // UPPERCASE(method) & percentEncode(baseUrl) & percentEncode(normalized) return [ method.toUpperCase(), percentEncode(baseUrl), percentEncode(normalized), ].join("&"); } function generateOAuthSignatureKey(credentials) { const consumerSecret = percentEncode(credentials.consumersecret); const tokenSecret = percentEncode(credentials.tokensecret); return `${consumerSecret}&${tokenSecret}`; } function computeHmacSha256Signature(signingKey, baseString) { try { const hmac = crypto.createHmac('sha256', signingKey); hmac.update(baseString); return hmac.digest('base64'); } catch (error) { throw new SignatureError(`Failed to compute HMAC-SHA256 signature: ${error}`); } } function concatenateOAuthHeaderParams(params) { const headerParts = Object.entries(params) .map(([key, value]) => `${key}="${percentEncode(value)}"`) .join(', '); return `OAuth ${headerParts}`; } /** * Main function to generate NetSuite OAuth header */ function generateNetSuiteOAuthHeader(request) { try { const { credentials } = request; const requestUrl = request.url || credentials.apibaseurl; const httpMethod = request.method || 'POST'; // Validate inputs validateCredentials(credentials); validateRequestParams(requestUrl, httpMethod); const oauthNonce = generateOAuthNonce(); const oauthTimestamp = Math.floor(Date.now() / 1000).toString(); const signatureBaseString = generateSignatureBaseString(credentials, requestUrl, oauthNonce, oauthTimestamp); const signingKey = generateOAuthSignatureKey(credentials); const signature = computeHmacSha256Signature(signingKey, signatureBaseString); const oauthParameters = { realm: credentials.accountid, oauth_consumer_key: credentials.consumerkey, oauth_token: credentials.tokenkey, oauth_nonce: oauthNonce, oauth_timestamp: oauthTimestamp, oauth_signature_method: SIGNATURE_METHOD, oauth_version: OAUTH_VERSION, oauth_signature: signature }; return concatenateOAuthHeaderParams(oauthParameters); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; throw new OAuthError(`Failed to generate OAuth header: ${errorMessage}`); } } // Main OAuth Client Class class NetSuiteOAuthClient { constructor(credentials) { validateCredentials(credentials); this.credentials = credentials; } /** * Generate OAuth authorization header for a request */ generateAuthHeader(url, method = 'POST') { const request = { credentials: this.credentials, url, method }; return generateNetSuiteOAuthHeader(request); } /** * Create request options with OAuth header */ createRequestOptions(options) { const authHeader = this.generateAuthHeader(options.url, options.method); return { ...options, headers: { 'Authorization': authHeader, 'Content-Type': 'application/json', ...options.headers } }; } /** * Update credentials */ updateCredentials(newCredentials) { this.credentials = { ...this.credentials, ...newCredentials }; validateCredentials(this.credentials); } /** * Get current credentials (without secrets) */ getCredentialsSummary() { return { accountid: this.credentials.accountid, consumerkey: this.credentials.consumerkey, apibaseurl: this.credentials.apibaseurl }; } } exports.NetSuiteOAuthClient = NetSuiteOAuthClient; // Factory function for easy instantiation function createNetSuiteOAuthClient(credentials) { return new NetSuiteOAuthClient(credentials); } // Default export exports.default = NetSuiteOAuthClient; //# sourceMappingURL=client.js.map