diff --git a/cjs/index.cjs b/cjs/index.cjs new file mode 100644 index 0000000..431ca97 --- /dev/null +++ b/cjs/index.cjs @@ -0,0 +1,189 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/index.js +var index_exports = {}; +__export(index_exports, { + generateAccessToken: () => generateAccessToken, + invalidateCache: () => invalidateCache +}); +module.exports = __toCommonJS(index_exports); + +// src/errors.js +var import_aio_lib_core_errors = require("@adobe/aio-lib-core-errors"); +var { ErrorWrapper, createUpdater } = import_aio_lib_core_errors.AioCoreSDKErrorWrapper; +var codes = {}; +var messages = /* @__PURE__ */ new Map(); +var Updater = createUpdater( + // object that stores the error classes (to be exported) + codes, + // Map that stores the error strings (to be exported) + messages +); +var E = ErrorWrapper( + // The class name for your SDK Error. Your Error objects will be these objects + "AuthSDKError", + // The name of your SDK. This will be a property in your Error objects + "AuthSDK", + // the object returned from the CreateUpdater call above + Updater +); +E("IMS_TOKEN_ERROR", "Error calling IMS to get access token: %s"); +E("MISSING_PARAMETERS", "Missing required parameters: %s"); +E("BAD_CREDENTIALS_FORMAT", "Credentials must be either an object or a stringified object"); +E("BAD_SCOPES_FORMAT", "Scopes must be an array"); +E("GENERIC_ERROR", "An unexpected error occurred: %s"); + +// src/ims.js +var IMS_BASE_URL_PROD = "https://ims-na1.adobelogin.com"; +var IMS_BASE_URL_STAGE = "https://ims-na1-stg1.adobelogin.com"; +function getImsUrl(env) { + return env === "stage" ? IMS_BASE_URL_STAGE : IMS_BASE_URL_PROD; +} +function getAndValidateCredentials(params) { + if (!(typeof params === "object" && params !== null && !Array.isArray(params))) { + throw new codes.BAD_CREDENTIALS_FORMAT({ + sdkDetails: { paramsType: typeof params } + }); + } + if (params.scopes && !Array.isArray(params.scopes)) { + throw new codes.BAD_SCOPES_FORMAT({ + sdkDetails: { scopesType: typeof params.scopes } + }); + } + const credentials = {}; + credentials.clientId = params.clientId || params.client_id; + credentials.clientSecret = params.clientSecret || params.client_secret; + credentials.orgId = params.orgId || params.org_id; + credentials.scopes = params.scopes || []; + const { clientId, clientSecret, orgId, scopes } = credentials; + const missingParams = []; + if (!clientId) { + missingParams.push("clientId"); + } + if (!clientSecret) { + missingParams.push("clientSecret"); + } + if (!orgId) { + missingParams.push("orgId"); + } + if (missingParams.length > 0) { + throw new codes.MISSING_PARAMETERS({ + messageValues: missingParams.join(", "), + sdkDetails: { clientId, orgId, scopes } + }); + } + return credentials; +} +async function getAccessTokenByClientCredentials({ clientId, clientSecret, orgId, scopes = [], env }) { + const imsBaseUrl = getImsUrl(env); + const formData = new URLSearchParams(); + formData.append("grant_type", "client_credentials"); + formData.append("client_id", clientId); + formData.append("client_secret", clientSecret); + formData.append("org_id", orgId); + if (scopes.length > 0) { + formData.append("scope", scopes.join(",")); + } + try { + const response = await fetch(`${imsBaseUrl}/ims/token/v2`, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + body: formData.toString() + }); + const data = await response.json(); + if (!response.ok) { + const errorMessage = data.error_description || data.error || `HTTP ${response.status}`; + const xDebugId = response.headers.get("x-debug-id"); + throw new codes.IMS_TOKEN_ERROR({ + messageValues: errorMessage, + sdkDetails: { + statusCode: response.status, + statusText: response.statusText, + error: data.error, + errorDescription: data.error_description, + xDebugId, + clientId, + orgId, + scopes, + imsEnv: env + } + }); + } + return data; + } catch (error) { + if (error.name === "AuthSDKError") { + throw error; + } + throw new codes.GENERIC_ERROR({ + messageValues: error.message, + sdkDetails: { + originalError: error.message, + clientId, + orgId, + scopes, + imsEnv: env + } + }); + } +} + +// src/index.js +var import_ttlcache = require("@isaacs/ttlcache"); +var import_crypto = __toESM(require("crypto"), 1); +var tokenCache = new import_ttlcache.TTLCache({ ttl: 5 * 60 * 1e3 }); +function getCacheKey({ clientId, orgId, env, scopes, clientSecret }) { + const scopeKey = scopes.length > 0 ? scopes.sort().join(",") : "none"; + return import_crypto.default.createHash("sha1").update(`${clientId}:${orgId}:${scopeKey}:${clientSecret}:${env}`).digest("hex"); +} +function invalidateCache() { + tokenCache.clear(); +} +async function generateAccessToken(params, imsEnv) { + imsEnv = imsEnv || (ioRuntimeStageNamespace() ? "stage" : "prod"); + const credentials = getAndValidateCredentials(params); + const credAndEnv = { ...credentials, env: imsEnv }; + const cacheKey = getCacheKey(credAndEnv); + const cachedToken = tokenCache.get(cacheKey); + if (cachedToken) { + return cachedToken; + } + const token = await getAccessTokenByClientCredentials(credAndEnv); + tokenCache.set(cacheKey, token); + return token; +} +function ioRuntimeStageNamespace() { + return process.env.__OW_NAMESPACE && process.env.__OW_NAMESPACE.startsWith("development-"); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + generateAccessToken, + invalidateCache +}); diff --git a/package.json b/package.json index 6276074..45771d0 100644 --- a/package.json +++ b/package.json @@ -3,17 +3,23 @@ "version": "1.0.0", "description": "Adobe I/O Core Authentication Library", "type": "module", - "main": "src/index.js", + "main": "cjs/index.cjs", "types": "types.d.ts", "exports": { ".": { "import": "./src/index.js", - "types": "./types.d.ts", - "require": "./src/index.js", - "default": "./src/index.js" + "require": "./cjs/index.cjs" } }, + "files": [ + "src", + "cjs", + "types.d.ts" + ], + "pre-commit": "pre-commit", "scripts": { + "build": "node scripts/build-cjs.js", + "pre-commit": "npm run build && git add cjs", "test": "vitest run --coverage", "lint": "eslint src test", "generate-docs": "jsdoc2md src/*.js > doc/api.md" @@ -42,6 +48,8 @@ "node": ">=20" }, "devDependencies": { + "esbuild": "^0.24.0", + "@fastify/pre-commit": "^2.2.0", "@adobe/eslint-config-aio-lib-config": "^3.0.0", "@vitest/coverage-v8": "^2.1.8", "eslint": "^8.57.0", diff --git a/scripts/build-cjs.js b/scripts/build-cjs.js new file mode 100644 index 0000000..ee1e971 --- /dev/null +++ b/scripts/build-cjs.js @@ -0,0 +1,33 @@ +/* +Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +import * as esbuild from 'esbuild' +import { mkdirSync, existsSync } from 'fs' +import { dirname, join } from 'path' +import { fileURLToPath } from 'url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const root = join(__dirname, '..') +const outDir = join(root, 'cjs') + +if (!existsSync(outDir)) { + mkdirSync(outDir, { recursive: true }) +} + +await esbuild.build({ + entryPoints: [join(root, 'src', 'index.js')], + bundle: true, + format: 'cjs', + platform: 'node', + target: 'node20', + outfile: join(outDir, 'index.cjs'), + external: ['@adobe/aio-lib-core-errors', '@isaacs/ttlcache'] +})