netsuite-oauth-client
Version:
NetSuite OAuth 1.0 client library for Node.js with TypeScript support
245 lines • 9.01 kB
JavaScript
;
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