@adobe/aio-commerce-lib-auth 0.8.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +1 -4
- package/bin/cli.mjs +24 -0
- package/dist/cjs/chunk-C0xms8kb.cjs +34 -0
- package/dist/cjs/commands/index.cjs +162 -84
- package/dist/cjs/index.cjs +750 -1
- package/dist/es/commands/index.mjs +157 -84
- package/dist/es/index.d.mts +0 -2
- package/dist/es/index.mjs +737 -1
- package/package.json +19 -8
- package/dist/cjs/chunk-YD6SZpHm.cjs +0 -1
package/dist/es/index.mjs
CHANGED
|
@@ -1 +1,737 @@
|
|
|
1
|
-
import{createHeaderAccessor,getHeader,getHeadersFromParams,parseBearerToken}from"@adobe/aio-commerce-lib-core/headers";import*as v from"valibot";import{array,email,instance,minLength,nonEmpty,nonOptional,object,optional,parse,picklist,pipe,rawTransform,safeParse,string,transform,union,url}from"valibot";import{CommerceSdkValidationError}from"@adobe/aio-commerce-lib-core/error";import aioLibIms from"@adobe/aio-lib-ims";import crypto from"crypto";import OAuth1a from"oauth-1.0a";import{allNonEmpty}from"@adobe/aio-commerce-lib-core/params";function stringValueSchema(name){return v.string(`Expected a string value for '${name}'`)}function parseOrThrow(schema,input,message){let result=v.safeParse(schema,input);if(!result.success)throw new CommerceSdkValidationError(message??`Invalid input`,{issues:result.issues});return result.output}const imsAuthParameter=name=>pipe(string(`Expected a string value for the IMS auth parameter ${name}`),nonEmpty(`Expected a non-empty string value for the IMS auth parameter ${name}`)),stringArray=(name,minimumLength)=>pipe(array(string(),`Expected a string array value for the IMS auth parameter ${name}`),minLength(minimumLength,`Expected at least ${minimumLength} items for the IMS auth parameter ${name}`)),StringArrayTransformSchema=name=>pipe(union([stringArray(name,1),pipe(string(),rawTransform(({dataset:{value:v$1},addIssue,NEVER})=>{if(v$1.startsWith(`[`)&&v$1.endsWith(`]`))try{let parsed=JSON.parse(v$1);return Array.isArray(parsed)?parsed:(addIssue({received:v$1,message:`Expected a valid JSON array for the IMS auth parameter ${name}: ${v$1}`}),NEVER)}catch(error){let errorMessage=error.message;return addIssue({received:v$1,message:`Expected a valid JSON array for the IMS auth parameter ${name}: ${errorMessage}`}),NEVER}return[v$1]}))]),stringArray(`value`,1)),ImsAuthEnvSchema=picklist([`prod`,`stage`]),ImsAuthParamsSchema=object({clientId:imsAuthParameter(`clientId`),clientSecrets:stringArray(`clientSecrets`,1),technicalAccountId:imsAuthParameter(`technicalAccountId`),technicalAccountEmail:pipe(string(`Expected a string value for the IMS auth parameter technicalAccountEmail`),email(`Expected a valid email format for technicalAccountEmail`)),imsOrgId:imsAuthParameter(`imsOrgId`),environment:pipe(optional(ImsAuthEnvSchema)),context:pipe(optional(string())),scopes:stringArray(`scopes`,1)});function __transformStringArray(name,value){if(value===void 0)return;let result=safeParse(StringArrayTransformSchema(name),value);if(!result.success)throw new CommerceSdkValidationError(`Invalid ImsAuthProvider configuration`,{issues:result.issues});return result.output}function __parseImsAuthParams(config){let result=safeParse(ImsAuthParamsSchema,config);if(!result.success)throw new CommerceSdkValidationError(`Invalid ImsAuthProvider configuration`,{issues:result.issues});return result.output}function assertImsAuthParams(config){__parseImsAuthParams(config)}function resolveImsAuthParams(params){return __parseImsAuthParams({clientId:params.AIO_COMMERCE_AUTH_IMS_CLIENT_ID,clientSecrets:__transformStringArray(`AIO_COMMERCE_AUTH_IMS_CLIENT_SECRETS`,params.AIO_COMMERCE_AUTH_IMS_CLIENT_SECRETS),technicalAccountId:params.AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_ID,technicalAccountEmail:params.AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_EMAIL,imsOrgId:params.AIO_COMMERCE_AUTH_IMS_ORG_ID,scopes:__transformStringArray(`AIO_COMMERCE_AUTH_IMS_SCOPES`,params.AIO_COMMERCE_AUTH_IMS_SCOPES),environment:params.AIO_COMMERCE_AUTH_IMS_ENVIRONMENT,context:params.AIO_COMMERCE_AUTH_IMS_CONTEXT})}function buildImsHeaders(accessToken,apiKey){let imsHeaders={Authorization:`Bearer ${accessToken}`};return apiKey&&(imsHeaders[`x-api-key`]=apiKey),imsHeaders}const IMS_AUTH_TOKEN_PARAM=`AIO_COMMERCE_AUTH_IMS_TOKEN`,IMS_AUTH_API_KEY_PARAM=`AIO_COMMERCE_AUTH_IMS_API_KEY`,ImsAuthParamsInputSchema=v.looseObject({[IMS_AUTH_TOKEN_PARAM]:stringValueSchema(IMS_AUTH_TOKEN_PARAM),[IMS_AUTH_API_KEY_PARAM]:v.optional(stringValueSchema(IMS_AUTH_API_KEY_PARAM))}),ForwardedImsAuthSourceSchema=v.variant(`from`,[v.object({from:v.literal(`headers`),headers:v.record(v.string(),v.optional(v.string()))}),v.object({from:v.literal(`getter`),getHeaders:v.custom(input=>typeof input==`function`,`Expected a function for getHeaders`)}),v.object({from:v.literal(`params`),params:ImsAuthParamsInputSchema})]);function getForwardedImsAuthProvider(source){let validatedSource=parseOrThrow(ForwardedImsAuthSourceSchema,source,`Invalid forwarded IMS auth source`);switch(validatedSource.from){case`headers`:{let{authorization}=createHeaderAccessor(validatedSource.headers,[`Authorization`]),apiKey=getHeader(validatedSource.headers,`x-api-key`),{token}=parseBearerToken(authorization);return{getAccessToken:()=>token,getHeaders:()=>buildImsHeaders(token,apiKey)}}case`getter`:return{getHeaders:validatedSource.getHeaders,getAccessToken:async()=>{let{token}=parseBearerToken((await validatedSource.getHeaders()).Authorization);return token}};case`params`:{let{params}=validatedSource,accessToken=params[IMS_AUTH_TOKEN_PARAM],apiKey=params[IMS_AUTH_API_KEY_PARAM];return{getAccessToken:()=>accessToken,getHeaders:()=>buildImsHeaders(accessToken,apiKey)}}}}function forwardImsAuthProviderFromRequest(params){return getForwardedImsAuthProvider({from:`headers`,headers:getHeadersFromParams(params)})}function forwardImsAuthProviderFromParams(params){return getForwardedImsAuthProvider({from:`params`,params:parseOrThrow(ImsAuthParamsInputSchema,params,`Missing AIO_COMMERCE_AUTH_IMS_TOKEN in params`)})}function forwardImsAuthProvider(params){try{return forwardImsAuthProviderFromParams(params)}catch{}try{return forwardImsAuthProviderFromRequest(params)}catch{}throw Error(`Can't forward IMS authentication from the given params. Make sure your params contain an AIO_COMMERCE_AUTH_IMS_TOKEN input or an Authorization header with an IMS token.`)}const{context,getToken}=aioLibIms;function toImsAuthConfig(config){return{scopes:config.scopes,env:config?.environment??`prod`,context:config.context??`aio-commerce-lib-auth-creds`,client_id:config.clientId,client_secrets:config.clientSecrets,technical_account_id:config.technicalAccountId,technical_account_email:config.technicalAccountEmail,ims_org_id:config.imsOrgId}}function isImsAuthProvider(provider){return typeof provider==`object`&&!!provider&&`getAccessToken`in provider&&`getHeaders`in provider&&typeof provider.getAccessToken==`function`&&typeof provider.getHeaders==`function`}function getImsAuthProvider(authParams){let getAccessToken=async()=>{let imsAuthConfig=toImsAuthConfig(authParams);return await context.set(imsAuthConfig.context,imsAuthConfig),getToken(imsAuthConfig.context,{})};return{getAccessToken,getHeaders:async()=>buildImsHeaders(await getAccessToken(),authParams.clientId)}}const integrationAuthParameter=name=>pipe(string(`Expected a string value for the Commerce Integration parameter ${name}`),nonEmpty(`Expected a non-empty string value for the Commerce Integration parameter ${name}`)),UrlSchema=pipe(union([pipe(string(`Expected a string for the Adobe Commerce endpoint`),nonEmpty(`Expected a non-empty string for the Adobe Commerce endpoint`),url(`Expected a valid url for the Adobe Commerce endpoint`)),instance(URL)]),transform(url$1=>url$1 instanceof URL?url$1.toString():url$1)),IntegrationAuthParamsSchema=nonOptional(object({consumerKey:integrationAuthParameter(`consumerKey`),consumerSecret:integrationAuthParameter(`consumerSecret`),accessToken:integrationAuthParameter(`accessToken`),accessTokenSecret:integrationAuthParameter(`accessTokenSecret`)}));function isIntegrationAuthProvider(provider){return typeof provider==`object`&&!!provider&&`getHeaders`in provider&&typeof provider.getHeaders==`function`}function getIntegrationAuthProvider(authParams){let oauth=new OAuth1a({consumer:{key:authParams.consumerKey,secret:authParams.consumerSecret},signature_method:`HMAC-SHA256`,hash_function:(baseString,key)=>crypto.createHmac(`sha256`,key).update(baseString).digest(`base64`)}),oauthToken={key:authParams.accessToken,secret:authParams.accessTokenSecret};return{getHeaders:(method,url$1)=>{let urlString=parse(UrlSchema,url$1);return oauth.toHeader(oauth.authorize({url:urlString,method},oauthToken))}}}function __parseIntegrationAuthParams(config){let result=safeParse(IntegrationAuthParamsSchema,config);if(!result.success)throw new CommerceSdkValidationError(`Invalid IntegrationAuthProvider configuration`,{issues:result.issues});return result.output}function assertIntegrationAuthParams(config){__parseIntegrationAuthParams(config)}function resolveIntegrationAuthParams(params){return __parseIntegrationAuthParams({consumerKey:params.AIO_COMMERCE_AUTH_INTEGRATION_CONSUMER_KEY,consumerSecret:params.AIO_COMMERCE_AUTH_INTEGRATION_CONSUMER_SECRET,accessToken:params.AIO_COMMERCE_AUTH_INTEGRATION_ACCESS_TOKEN,accessTokenSecret:params.AIO_COMMERCE_AUTH_INTEGRATION_ACCESS_TOKEN_SECRET})}const IMS_AUTH_PARAMS=[`AIO_COMMERCE_AUTH_IMS_CLIENT_ID`,`AIO_COMMERCE_AUTH_IMS_CLIENT_SECRETS`,`AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_ID`,`AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_EMAIL`,`AIO_COMMERCE_AUTH_IMS_ORG_ID`,`AIO_COMMERCE_AUTH_IMS_SCOPES`],INTEGRATION_AUTH_PARAMS=[`AIO_COMMERCE_AUTH_INTEGRATION_CONSUMER_KEY`,`AIO_COMMERCE_AUTH_INTEGRATION_CONSUMER_SECRET`,`AIO_COMMERCE_AUTH_INTEGRATION_ACCESS_TOKEN`,`AIO_COMMERCE_AUTH_INTEGRATION_ACCESS_TOKEN_SECRET`];function resolveAuthParams(params){if(allNonEmpty(params,IMS_AUTH_PARAMS))return{...resolveImsAuthParams(params),strategy:`ims`};if(allNonEmpty(params,INTEGRATION_AUTH_PARAMS))return{...resolveIntegrationAuthParams(params),strategy:`integration`};throw Error(`Can't resolve authentication options for the given params. Please provide either IMS options (${IMS_AUTH_PARAMS.join(`, `)}) or Commerce integration options (${INTEGRATION_AUTH_PARAMS.join(`, `)}).`)}export{assertImsAuthParams,assertIntegrationAuthParams,forwardImsAuthProvider,getForwardedImsAuthProvider,getImsAuthProvider,getIntegrationAuthProvider,isImsAuthProvider,isIntegrationAuthProvider,resolveAuthParams};
|
|
1
|
+
import { createHeaderAccessor, getHeader, getHeadersFromParams, parseBearerToken } from "@adobe/aio-commerce-lib-core/headers";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
import { array, email, instance, minLength, nonEmpty, nonOptional, object, optional, parse, picklist, pipe, rawTransform, safeParse, string, transform, union, url } from "valibot";
|
|
4
|
+
import { CommerceSdkValidationError } from "@adobe/aio-commerce-lib-core/error";
|
|
5
|
+
import aioLibIms from "@adobe/aio-lib-ims";
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
import OAuth1a from "oauth-1.0a";
|
|
8
|
+
import { allNonEmpty } from "@adobe/aio-commerce-lib-core/params";
|
|
9
|
+
|
|
10
|
+
//#region ../../packages-private/common-utils/source/valibot/schemas.ts
|
|
11
|
+
/**
|
|
12
|
+
* A schema for a string value.
|
|
13
|
+
* @param name The name of the field this schema refers to.
|
|
14
|
+
*/
|
|
15
|
+
function stringValueSchema(name) {
|
|
16
|
+
return v.string(`Expected a string value for '${name}'`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region ../../packages-private/common-utils/source/valibot/utils.ts
|
|
21
|
+
/**
|
|
22
|
+
* Parses the input using the provided schema and throws a {@link CommerceSdkValidationError} error if the input is invalid.
|
|
23
|
+
* @param schema - The schema to use for parsing.
|
|
24
|
+
* @param input - The input to parse.
|
|
25
|
+
* @param message - Optional custom error message for the validation error.
|
|
26
|
+
*/
|
|
27
|
+
function parseOrThrow(schema, input, message) {
|
|
28
|
+
const result = v.safeParse(schema, input);
|
|
29
|
+
if (!result.success) throw new CommerceSdkValidationError(message ?? "Invalid input", { issues: result.issues });
|
|
30
|
+
return result.output;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region source/lib/ims-auth/schema.ts
|
|
35
|
+
/**
|
|
36
|
+
* Creates a validation schema for a required IMS auth string parameter.
|
|
37
|
+
* @param name The name of the parameter for error messages.
|
|
38
|
+
* @returns A validation pipeline that ensures the parameter is a non-empty string.
|
|
39
|
+
*/
|
|
40
|
+
const imsAuthParameter = (name) => pipe(string(`Expected a string value for the IMS auth parameter ${name}`), nonEmpty(`Expected a non-empty string value for the IMS auth parameter ${name}`));
|
|
41
|
+
/**
|
|
42
|
+
* Creates a validation schema for an IMS auth string array parameter.
|
|
43
|
+
* @param name The name of the parameter for error messages.
|
|
44
|
+
* @param minimumLength The minimum length of the array.
|
|
45
|
+
* @returns A validation pipeline that ensures the parameter is an array of strings.
|
|
46
|
+
*/
|
|
47
|
+
const stringArray = (name, minimumLength) => {
|
|
48
|
+
return pipe(array(string(), `Expected a string array value for the IMS auth parameter ${name}`), minLength(minimumLength, `Expected at least ${minimumLength} items for the IMS auth parameter ${name}`));
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Schema for transforming a value that may be a JSON string array or a single string into an array.
|
|
52
|
+
* This schema handles the transformation of App Builder action inputs that may come as JSON strings.
|
|
53
|
+
*/
|
|
54
|
+
const StringArrayTransformSchema = (name) => pipe(union([stringArray(name, 1), pipe(string(), rawTransform(({ dataset: { value: v }, addIssue, NEVER }) => {
|
|
55
|
+
if (v.startsWith("[") && v.endsWith("]")) try {
|
|
56
|
+
const parsed = JSON.parse(v);
|
|
57
|
+
if (!Array.isArray(parsed)) {
|
|
58
|
+
addIssue({
|
|
59
|
+
received: v,
|
|
60
|
+
message: `Expected a valid JSON array for the IMS auth parameter ${name}: ${v}`
|
|
61
|
+
});
|
|
62
|
+
return NEVER;
|
|
63
|
+
}
|
|
64
|
+
return parsed;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
const errorMessage = error.message;
|
|
67
|
+
addIssue({
|
|
68
|
+
received: v,
|
|
69
|
+
message: `Expected a valid JSON array for the IMS auth parameter ${name}: ${errorMessage}`
|
|
70
|
+
});
|
|
71
|
+
return NEVER;
|
|
72
|
+
}
|
|
73
|
+
return [v];
|
|
74
|
+
}))]), stringArray("value", 1));
|
|
75
|
+
/** Validation schema for IMS auth environment values. */
|
|
76
|
+
const ImsAuthEnvSchema = picklist(["prod", "stage"]);
|
|
77
|
+
/** Defines the schema to validate the necessary parameters for the IMS auth service. */
|
|
78
|
+
const ImsAuthParamsSchema = object({
|
|
79
|
+
clientId: imsAuthParameter("clientId"),
|
|
80
|
+
clientSecrets: stringArray("clientSecrets", 1),
|
|
81
|
+
technicalAccountId: imsAuthParameter("technicalAccountId"),
|
|
82
|
+
technicalAccountEmail: pipe(string("Expected a string value for the IMS auth parameter technicalAccountEmail"), email("Expected a valid email format for technicalAccountEmail")),
|
|
83
|
+
imsOrgId: imsAuthParameter("imsOrgId"),
|
|
84
|
+
environment: pipe(optional(ImsAuthEnvSchema)),
|
|
85
|
+
context: pipe(optional(string())),
|
|
86
|
+
scopes: stringArray("scopes", 1)
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region source/lib/ims-auth/utils.ts
|
|
91
|
+
/**
|
|
92
|
+
* Transforms a value using the string array transformation schema.
|
|
93
|
+
* @param value - The value to transform (may be a JSON string, single string, or array).
|
|
94
|
+
* @throws {CommerceSdkValidationError} If the transformation fails.
|
|
95
|
+
*
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
function __transformStringArray(name, value) {
|
|
99
|
+
if (value === void 0) return;
|
|
100
|
+
const result = safeParse(StringArrayTransformSchema(name), value);
|
|
101
|
+
if (!result.success) throw new CommerceSdkValidationError("Invalid ImsAuthProvider configuration", { issues: result.issues });
|
|
102
|
+
return result.output;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Parses the provided configuration for an {@link ImsAuthProvider}.
|
|
106
|
+
* @param config - The configuration to parse.
|
|
107
|
+
* @throws {CommerceSdkValidationError} If the configuration is invalid.
|
|
108
|
+
*
|
|
109
|
+
* @internal
|
|
110
|
+
*/
|
|
111
|
+
function __parseImsAuthParams(config) {
|
|
112
|
+
const result = safeParse(ImsAuthParamsSchema, config);
|
|
113
|
+
if (!result.success) throw new CommerceSdkValidationError("Invalid ImsAuthProvider configuration", { issues: result.issues });
|
|
114
|
+
return result.output;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Asserts the provided configuration for an {@link ImsAuthProvider}.
|
|
118
|
+
* @param config The configuration to validate.
|
|
119
|
+
* @throws {CommerceSdkValidationError} If the configuration is invalid.
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* const config = {
|
|
123
|
+
* clientId: "your-client-id",
|
|
124
|
+
* clientSecrets: ["your-client-secret"],
|
|
125
|
+
* technicalAccountId: "your-technical-account-id",
|
|
126
|
+
* technicalAccountEmail: "your-account@example.com",
|
|
127
|
+
* imsOrgId: "your-ims-org-id@AdobeOrg",
|
|
128
|
+
* scopes: ["AdobeID", "openid"],
|
|
129
|
+
* environment: "prod", // or "stage"
|
|
130
|
+
* context: "my-app-context"
|
|
131
|
+
* };
|
|
132
|
+
*
|
|
133
|
+
* // This will validate the config and throw if invalid
|
|
134
|
+
* assertImsAuthParams(config);
|
|
135
|
+
*```
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* // Example of a failing assert:
|
|
139
|
+
* try {
|
|
140
|
+
* assertImsAuthParams({
|
|
141
|
+
* clientId: "valid-client-id",
|
|
142
|
+
* // Missing required fields like clientSecrets, technicalAccountId, etc.
|
|
143
|
+
* });
|
|
144
|
+
* } catch (error) {
|
|
145
|
+
* console.error(error.message); // "Invalid ImsAuthProvider configuration"
|
|
146
|
+
* console.error(error.issues); // Array of validation issues
|
|
147
|
+
* }
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
function assertImsAuthParams(config) {
|
|
151
|
+
__parseImsAuthParams(config);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Resolves an {@link ImsAuthParams} from the given App Builder action inputs.
|
|
155
|
+
* @param params The App Builder action inputs to resolve the IMS authentication parameters from.
|
|
156
|
+
* @throws {CommerceSdkValidationError} If the parameters are invalid and cannot be resolved.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```typescript
|
|
160
|
+
* // Some App Builder runtime action that needs IMS authentication
|
|
161
|
+
* export function main(params) {
|
|
162
|
+
* const imsAuthProvider = getImsAuthProvider(resolveImsAuthParams(params));
|
|
163
|
+
*
|
|
164
|
+
* // Get headers for API requests
|
|
165
|
+
* const headers = await authProvider.getHeaders();
|
|
166
|
+
* const response = await fetch('https://api.adobe.io/some-endpoint', {
|
|
167
|
+
* headers: await authProvider.getHeaders()
|
|
168
|
+
* });
|
|
169
|
+
* }
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
function resolveImsAuthParams(params) {
|
|
173
|
+
return __parseImsAuthParams({
|
|
174
|
+
clientId: params.AIO_COMMERCE_AUTH_IMS_CLIENT_ID,
|
|
175
|
+
clientSecrets: __transformStringArray("AIO_COMMERCE_AUTH_IMS_CLIENT_SECRETS", params.AIO_COMMERCE_AUTH_IMS_CLIENT_SECRETS),
|
|
176
|
+
technicalAccountId: params.AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_ID,
|
|
177
|
+
technicalAccountEmail: params.AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_EMAIL,
|
|
178
|
+
imsOrgId: params.AIO_COMMERCE_AUTH_IMS_ORG_ID,
|
|
179
|
+
scopes: __transformStringArray("AIO_COMMERCE_AUTH_IMS_SCOPES", params.AIO_COMMERCE_AUTH_IMS_SCOPES),
|
|
180
|
+
environment: params.AIO_COMMERCE_AUTH_IMS_ENVIRONMENT,
|
|
181
|
+
context: params.AIO_COMMERCE_AUTH_IMS_CONTEXT
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Shorthand to build IMS authentication headers.
|
|
186
|
+
* @param accessToken - The token to use in the Authorization header.
|
|
187
|
+
* @param apiKey - The API key to include in the x-api-key header (optional).
|
|
188
|
+
*/
|
|
189
|
+
function buildImsHeaders(accessToken, apiKey) {
|
|
190
|
+
const imsHeaders = { Authorization: `Bearer ${accessToken}` };
|
|
191
|
+
if (apiKey) imsHeaders["x-api-key"] = apiKey;
|
|
192
|
+
return imsHeaders;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region source/lib/ims-auth/forwarding.ts
|
|
197
|
+
const IMS_AUTH_TOKEN_PARAM = "AIO_COMMERCE_AUTH_IMS_TOKEN";
|
|
198
|
+
const IMS_AUTH_API_KEY_PARAM = "AIO_COMMERCE_AUTH_IMS_API_KEY";
|
|
199
|
+
const ImsAuthParamsInputSchema = v.looseObject({
|
|
200
|
+
[IMS_AUTH_TOKEN_PARAM]: stringValueSchema(IMS_AUTH_TOKEN_PARAM),
|
|
201
|
+
[IMS_AUTH_API_KEY_PARAM]: v.optional(stringValueSchema(IMS_AUTH_API_KEY_PARAM))
|
|
202
|
+
});
|
|
203
|
+
const ForwardedImsAuthSourceSchema = v.variant("from", [
|
|
204
|
+
v.object({
|
|
205
|
+
from: v.literal("headers"),
|
|
206
|
+
headers: v.record(v.string(), v.optional(v.string()))
|
|
207
|
+
}),
|
|
208
|
+
v.object({
|
|
209
|
+
from: v.literal("getter"),
|
|
210
|
+
getHeaders: v.custom((input) => typeof input === "function", "Expected a function for getHeaders")
|
|
211
|
+
}),
|
|
212
|
+
v.object({
|
|
213
|
+
from: v.literal("params"),
|
|
214
|
+
params: ImsAuthParamsInputSchema
|
|
215
|
+
})
|
|
216
|
+
]);
|
|
217
|
+
/**
|
|
218
|
+
* Creates an {@link ImsAuthProvider} by forwarding authentication credentials from various sources.
|
|
219
|
+
*
|
|
220
|
+
* @param source The source of the credentials to forward, as a {@link ForwardedImsAuthSource}.
|
|
221
|
+
* @returns An {@link ImsAuthProvider} instance that returns the forwarded access token and headers.
|
|
222
|
+
*
|
|
223
|
+
* @throws {CommerceSdkValidationError} If the source object is invalid.
|
|
224
|
+
* @throws {CommerceSdkValidationError} If `from: "headers"` is used and the `Authorization` header is missing.
|
|
225
|
+
* @throws {CommerceSdkValidationError} If `from: "headers"` is used and the `Authorization` header is not in Bearer token format.
|
|
226
|
+
* @throws {CommerceSdkValidationError} If `from: "params"` is used and `AIO_COMMERCE_AUTH_IMS_TOKEN` is missing or empty.
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```typescript
|
|
230
|
+
* import { getForwardedImsAuthProvider } from "@adobe/aio-commerce-lib-auth";
|
|
231
|
+
*
|
|
232
|
+
* // From raw headers (e.g. from an HTTP request).
|
|
233
|
+
* const provider1 = getForwardedImsAuthProvider({
|
|
234
|
+
* from: "headers",
|
|
235
|
+
* headers: params.__ow_headers,
|
|
236
|
+
* });
|
|
237
|
+
*
|
|
238
|
+
* // From async getter (e.g. fetch from secret manager)
|
|
239
|
+
* const provider2 = getForwardedImsAuthProvider({
|
|
240
|
+
* from: "getter",
|
|
241
|
+
* getHeaders: async () => {
|
|
242
|
+
* const token = await secretManager.getSecret("ims-token");
|
|
243
|
+
* return { Authorization: `Bearer ${token}` };
|
|
244
|
+
* },
|
|
245
|
+
* });
|
|
246
|
+
*
|
|
247
|
+
* // From a params object (using AIO_COMMERCE_AUTH_IMS_TOKEN and AIO_COMMERCE_AUTH_IMS_API_KEY keys)
|
|
248
|
+
* const provider3 = getForwardedImsAuthProvider({
|
|
249
|
+
* from: "params",
|
|
250
|
+
* params: actionParams,
|
|
251
|
+
* });
|
|
252
|
+
*
|
|
253
|
+
* // Use the provider
|
|
254
|
+
* const token = await provider1.getAccessToken();
|
|
255
|
+
* const headers = await provider1.getHeaders();
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
function getForwardedImsAuthProvider(source) {
|
|
259
|
+
const validatedSource = parseOrThrow(ForwardedImsAuthSourceSchema, source, "Invalid forwarded IMS auth source");
|
|
260
|
+
switch (validatedSource.from) {
|
|
261
|
+
case "headers": {
|
|
262
|
+
const { authorization } = createHeaderAccessor(validatedSource.headers, ["Authorization"]);
|
|
263
|
+
const apiKey = getHeader(validatedSource.headers, "x-api-key");
|
|
264
|
+
const { token } = parseBearerToken(authorization);
|
|
265
|
+
return {
|
|
266
|
+
getAccessToken: () => token,
|
|
267
|
+
getHeaders: () => buildImsHeaders(token, apiKey)
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
case "getter": return {
|
|
271
|
+
getHeaders: validatedSource.getHeaders,
|
|
272
|
+
getAccessToken: async () => {
|
|
273
|
+
const { token } = parseBearerToken((await validatedSource.getHeaders()).Authorization);
|
|
274
|
+
return token;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
case "params": {
|
|
278
|
+
const { params } = validatedSource;
|
|
279
|
+
const accessToken = params[IMS_AUTH_TOKEN_PARAM];
|
|
280
|
+
const apiKey = params[IMS_AUTH_API_KEY_PARAM];
|
|
281
|
+
return {
|
|
282
|
+
getAccessToken: () => accessToken,
|
|
283
|
+
getHeaders: () => buildImsHeaders(accessToken, apiKey)
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Creates an {@link ImsAuthProvider} by forwarding authentication credentials from incoming
|
|
290
|
+
* runtime action request headers.
|
|
291
|
+
*
|
|
292
|
+
* This is a convenience wrapper around {@link getForwardedImsAuthProvider} for the common case
|
|
293
|
+
* of forwarding credentials from Adobe I/O Runtime action parameters.
|
|
294
|
+
*
|
|
295
|
+
* @param params The runtime action parameters containing the `__ow_headers` object with authentication headers.
|
|
296
|
+
* @returns An {@link ImsAuthProvider} instance that returns the forwarded access token and headers.
|
|
297
|
+
*
|
|
298
|
+
* @throws {Error} If the `Authorization` header is missing from the request headers.
|
|
299
|
+
* @throws {Error} If the `Authorization` header is not in the correct Bearer token format.
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```typescript
|
|
303
|
+
* import { forwardImsAuthProviderFromRequest } from "@adobe/aio-commerce-lib-auth";
|
|
304
|
+
*
|
|
305
|
+
* // In an Adobe I/O Runtime action
|
|
306
|
+
* async function main(params) {
|
|
307
|
+
* // params.__ow_headers contains: { Authorization: "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."}
|
|
308
|
+
*
|
|
309
|
+
* // Forward the authentication from the incoming request
|
|
310
|
+
* const authProvider = forwardImsAuthProviderFromRequest(params);
|
|
311
|
+
*
|
|
312
|
+
* // Get the forwarded access token
|
|
313
|
+
* const token = await authProvider.getAccessToken();
|
|
314
|
+
* console.log(token); // "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."
|
|
315
|
+
*
|
|
316
|
+
* // Get headers for downstream API requests
|
|
317
|
+
* const headers = await authProvider.getHeaders();
|
|
318
|
+
* console.log(headers);
|
|
319
|
+
* // {
|
|
320
|
+
* // Authorization: "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
|
|
321
|
+
* // "x-api-key": "client-id-from-request" // Only if present in original request
|
|
322
|
+
* // }
|
|
323
|
+
*
|
|
324
|
+
* // Use the forwarded credentials in downstream API calls
|
|
325
|
+
* const response = await fetch('...', {
|
|
326
|
+
* headers: await authProvider.getHeaders()
|
|
327
|
+
* });
|
|
328
|
+
*
|
|
329
|
+
* return { statusCode: 200, body: await response.json() };
|
|
330
|
+
* }
|
|
331
|
+
* ```
|
|
332
|
+
*/
|
|
333
|
+
function forwardImsAuthProviderFromRequest(params) {
|
|
334
|
+
return getForwardedImsAuthProvider({
|
|
335
|
+
from: "headers",
|
|
336
|
+
headers: getHeadersFromParams(params)
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Creates an {@link ImsAuthProvider} by forwarding authentication credentials from a params object.
|
|
341
|
+
*
|
|
342
|
+
* This is a convenience wrapper around {@link getForwardedImsAuthProvider} for the common case
|
|
343
|
+
* of forwarding credentials from runtime action parameters. It reads:
|
|
344
|
+
* - `AIO_COMMERCE_AUTH_IMS_TOKEN` for the access token (required)
|
|
345
|
+
* - `AIO_COMMERCE_AUTH_IMS_API_KEY` for the API key (optional)
|
|
346
|
+
*
|
|
347
|
+
* @param params The params object containing the authentication credentials.
|
|
348
|
+
* @returns An {@link ImsAuthProvider} instance that returns the access token and headers from the params.
|
|
349
|
+
*
|
|
350
|
+
* @throws {CommerceSdkValidationError} If `AIO_COMMERCE_AUTH_IMS_TOKEN` is not set or is empty.
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```typescript
|
|
354
|
+
* import { forwardImsAuthProviderFromParams } from "@adobe/aio-commerce-lib-auth";
|
|
355
|
+
*
|
|
356
|
+
* // In an Adobe I/O Runtime action
|
|
357
|
+
* async function main(params) {
|
|
358
|
+
* // params contains: { AIO_COMMERCE_AUTH_IMS_TOKEN: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..." }
|
|
359
|
+
* const authProvider = forwardImsAuthProviderFromParams(params);
|
|
360
|
+
*
|
|
361
|
+
* // Get the access token
|
|
362
|
+
* const token = await authProvider.getAccessToken();
|
|
363
|
+
*
|
|
364
|
+
* // Get headers for downstream API requests
|
|
365
|
+
* const headers = await authProvider.getHeaders();
|
|
366
|
+
* // {
|
|
367
|
+
* // Authorization: "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
|
|
368
|
+
* // "x-api-key": "my-api-key" // Only if AIO_COMMERCE_AUTH_IMS_API_KEY is set
|
|
369
|
+
* // }
|
|
370
|
+
* }
|
|
371
|
+
* ```
|
|
372
|
+
*/
|
|
373
|
+
function forwardImsAuthProviderFromParams(params) {
|
|
374
|
+
return getForwardedImsAuthProvider({
|
|
375
|
+
from: "params",
|
|
376
|
+
params: parseOrThrow(ImsAuthParamsInputSchema, params, "Missing AIO_COMMERCE_AUTH_IMS_TOKEN in params")
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Creates an {@link ImsAuthProvider} by forwarding authentication credentials from runtime action parameters.
|
|
381
|
+
*
|
|
382
|
+
* This function automatically detects the source of credentials by trying multiple strategies in order:
|
|
383
|
+
* 1. **Params token** - Looks for `AIO_COMMERCE_AUTH_IMS_TOKEN` (and optionally `AIO_COMMERCE_AUTH_IMS_API_KEY`) in the params object
|
|
384
|
+
* 2. **HTTP headers** - Falls back to extracting the `Authorization` header from `__ow_headers`
|
|
385
|
+
*
|
|
386
|
+
* Use this function when building actions that receive authenticated requests and need to forward
|
|
387
|
+
* those credentials to downstream services (proxy pattern).
|
|
388
|
+
*
|
|
389
|
+
* @param params The runtime action parameters object. Can contain either:
|
|
390
|
+
* - `AIO_COMMERCE_AUTH_IMS_TOKEN` and optionally `AIO_COMMERCE_AUTH_IMS_API_KEY` for direct token forwarding
|
|
391
|
+
* - `__ow_headers` with an `Authorization` header for HTTP request forwarding
|
|
392
|
+
* @returns An {@link ImsAuthProvider} instance that returns the forwarded access token and headers.
|
|
393
|
+
*
|
|
394
|
+
* @throws {Error} If neither a valid token param nor Authorization header is found.
|
|
395
|
+
*
|
|
396
|
+
* @example
|
|
397
|
+
* ```typescript
|
|
398
|
+
* import { forwardImsAuthProvider } from "@adobe/aio-commerce-lib-auth";
|
|
399
|
+
*
|
|
400
|
+
* export async function main(params: Record<string, unknown>) {
|
|
401
|
+
* // Automatically detects credentials from params or headers
|
|
402
|
+
* const authProvider = forwardImsAuthProvider(params);
|
|
403
|
+
*
|
|
404
|
+
* // Get the access token
|
|
405
|
+
* const token = await authProvider.getAccessToken();
|
|
406
|
+
*
|
|
407
|
+
* // Get headers for downstream API requests
|
|
408
|
+
* const headers = await authProvider.getHeaders();
|
|
409
|
+
* // {
|
|
410
|
+
* // Authorization: "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
|
|
411
|
+
* // "x-api-key": "my-api-key" // Only if available
|
|
412
|
+
* // }
|
|
413
|
+
*
|
|
414
|
+
* // Use the forwarded credentials in downstream API calls
|
|
415
|
+
* const response = await fetch("https://api.adobe.io/some-endpoint", {
|
|
416
|
+
* headers,
|
|
417
|
+
* });
|
|
418
|
+
*
|
|
419
|
+
* return { statusCode: 200, body: await response.json() };
|
|
420
|
+
* }
|
|
421
|
+
* ```
|
|
422
|
+
*/
|
|
423
|
+
function forwardImsAuthProvider(params) {
|
|
424
|
+
try {
|
|
425
|
+
return forwardImsAuthProviderFromParams(params);
|
|
426
|
+
} catch {}
|
|
427
|
+
try {
|
|
428
|
+
return forwardImsAuthProviderFromRequest(params);
|
|
429
|
+
} catch {}
|
|
430
|
+
throw new Error("Can't forward IMS authentication from the given params. Make sure your params contain an AIO_COMMERCE_AUTH_IMS_TOKEN input or an Authorization header with an IMS token.");
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
//#endregion
|
|
434
|
+
//#region source/lib/ims-auth/provider.ts
|
|
435
|
+
const { context, getToken } = aioLibIms;
|
|
436
|
+
/**
|
|
437
|
+
* Converts IMS auth configuration properties to snake_case format.
|
|
438
|
+
* @param config The IMS auth configuration with camelCase properties.
|
|
439
|
+
* @returns The configuration with snake_case properties.
|
|
440
|
+
*/
|
|
441
|
+
function toImsAuthConfig(config) {
|
|
442
|
+
return {
|
|
443
|
+
scopes: config.scopes,
|
|
444
|
+
env: config?.environment ?? "prod",
|
|
445
|
+
context: config.context ?? "aio-commerce-lib-auth-creds",
|
|
446
|
+
client_id: config.clientId,
|
|
447
|
+
client_secrets: config.clientSecrets,
|
|
448
|
+
technical_account_id: config.technicalAccountId,
|
|
449
|
+
technical_account_email: config.technicalAccountEmail,
|
|
450
|
+
ims_org_id: config.imsOrgId
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Type guard to check if a value is an ImsAuthProvider instance.
|
|
455
|
+
*
|
|
456
|
+
* @param provider The value to check.
|
|
457
|
+
* @returns `true` if the value is an ImsAuthProvider, `false` otherwise.
|
|
458
|
+
*
|
|
459
|
+
* @example
|
|
460
|
+
* ```typescript
|
|
461
|
+
* import { getImsAuthProvider, isImsAuthProvider } from "@adobe/aio-commerce-lib-auth";
|
|
462
|
+
*
|
|
463
|
+
* // Imagine you have an object that it's not strictly typed as ImsAuthProvider.
|
|
464
|
+
* const provider = getImsAuthProvider({ ... }) as unknown;
|
|
465
|
+
*
|
|
466
|
+
* if (isImsAuthProvider(provider)) {
|
|
467
|
+
* // TypeScript knows provider is ImsAuthProvider
|
|
468
|
+
* const token = await provider.getAccessToken();
|
|
469
|
+
* }
|
|
470
|
+
* ```
|
|
471
|
+
*/
|
|
472
|
+
function isImsAuthProvider(provider) {
|
|
473
|
+
return typeof provider === "object" && provider !== null && "getAccessToken" in provider && "getHeaders" in provider && typeof provider.getAccessToken === "function" && typeof provider.getHeaders === "function";
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Creates an {@link ImsAuthProvider} based on the provided configuration.
|
|
477
|
+
* @param authParams An {@link ImsAuthParams} parameter that contains the configuration for the {@link ImsAuthProvider}.
|
|
478
|
+
* @returns An {@link ImsAuthProvider} instance that can be used to get access token and auth headers.
|
|
479
|
+
* @example
|
|
480
|
+
* ```typescript
|
|
481
|
+
* const config = {
|
|
482
|
+
* clientId: "your-client-id",
|
|
483
|
+
* clientSecrets: ["your-client-secret"],
|
|
484
|
+
* technicalAccountId: "your-technical-account-id",
|
|
485
|
+
* technicalAccountEmail: "your-account@example.com",
|
|
486
|
+
* imsOrgId: "your-ims-org-id@AdobeOrg",
|
|
487
|
+
* scopes: ["AdobeID", "openid"],
|
|
488
|
+
* environment: "prod",
|
|
489
|
+
* context: "my-app-context"
|
|
490
|
+
* };
|
|
491
|
+
*
|
|
492
|
+
* const authProvider = getImsAuthProvider(config);
|
|
493
|
+
*
|
|
494
|
+
* // Get access token
|
|
495
|
+
* const token = await authProvider.getAccessToken();
|
|
496
|
+
* console.log(token); // "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."
|
|
497
|
+
*
|
|
498
|
+
* // Get headers for API requests
|
|
499
|
+
* const headers = await authProvider.getHeaders();
|
|
500
|
+
* console.log(headers);
|
|
501
|
+
* // {
|
|
502
|
+
* // Authorization: "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
|
|
503
|
+
* // "x-api-key": "your-client-id"
|
|
504
|
+
* // }
|
|
505
|
+
*
|
|
506
|
+
* // Use headers in API calls
|
|
507
|
+
* const response = await fetch('https://api.adobe.io/some-endpoint', {
|
|
508
|
+
* headers: await authProvider.getHeaders()
|
|
509
|
+
* });
|
|
510
|
+
* ```
|
|
511
|
+
*/
|
|
512
|
+
function getImsAuthProvider(authParams) {
|
|
513
|
+
const getAccessToken = async () => {
|
|
514
|
+
const imsAuthConfig = toImsAuthConfig(authParams);
|
|
515
|
+
await context.set(imsAuthConfig.context, imsAuthConfig);
|
|
516
|
+
return getToken(imsAuthConfig.context, {});
|
|
517
|
+
};
|
|
518
|
+
const getHeaders = async () => {
|
|
519
|
+
return buildImsHeaders(await getAccessToken(), authParams.clientId);
|
|
520
|
+
};
|
|
521
|
+
return {
|
|
522
|
+
getAccessToken,
|
|
523
|
+
getHeaders
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
//#endregion
|
|
528
|
+
//#region source/lib/integration-auth/schema.ts
|
|
529
|
+
/**
|
|
530
|
+
* Creates a validation schema for a required Commerce Integration string parameter.
|
|
531
|
+
* @param name The name of the parameter for error messages.
|
|
532
|
+
* @returns A validation pipeline that ensures the parameter is a non-empty string.
|
|
533
|
+
*/
|
|
534
|
+
const integrationAuthParameter = (name) => pipe(string(`Expected a string value for the Commerce Integration parameter ${name}`), nonEmpty(`Expected a non-empty string value for the Commerce Integration parameter ${name}`));
|
|
535
|
+
/** Validation schema for the Adobe Commerce endpoint base URL. */
|
|
536
|
+
const BaseUrlSchema = pipe(string("Expected a string for the Adobe Commerce endpoint"), nonEmpty("Expected a non-empty string for the Adobe Commerce endpoint"), url("Expected a valid url for the Adobe Commerce endpoint"));
|
|
537
|
+
/** Validation schema that accepts either a URL string or URL instance and normalizes to string. */
|
|
538
|
+
const UrlSchema = pipe(union([BaseUrlSchema, instance(URL)]), transform((url) => {
|
|
539
|
+
if (url instanceof URL) return url.toString();
|
|
540
|
+
return url;
|
|
541
|
+
}));
|
|
542
|
+
/**
|
|
543
|
+
* The schema for the Commerce Integration parameters.
|
|
544
|
+
* This is used to validate the parameters passed to the Commerce Integration provider.
|
|
545
|
+
*/
|
|
546
|
+
const IntegrationAuthParamsSchema = nonOptional(object({
|
|
547
|
+
consumerKey: integrationAuthParameter("consumerKey"),
|
|
548
|
+
consumerSecret: integrationAuthParameter("consumerSecret"),
|
|
549
|
+
accessToken: integrationAuthParameter("accessToken"),
|
|
550
|
+
accessTokenSecret: integrationAuthParameter("accessTokenSecret")
|
|
551
|
+
}));
|
|
552
|
+
|
|
553
|
+
//#endregion
|
|
554
|
+
//#region source/lib/integration-auth/provider.ts
|
|
555
|
+
/**
|
|
556
|
+
* Type guard to check if a value is an IntegrationAuthProvider instance.
|
|
557
|
+
*
|
|
558
|
+
* @param provider The value to check.
|
|
559
|
+
* @returns `true` if the value is an IntegrationAuthProvider, `false` otherwise.
|
|
560
|
+
*
|
|
561
|
+
* @example
|
|
562
|
+
* ```typescript
|
|
563
|
+
* import { getIntegrationAuthProvider, isIntegrationAuthProvider } from "@adobe/aio-commerce-lib-auth";
|
|
564
|
+
*
|
|
565
|
+
* // Imagine you have an object that it's not strictly typed as IntegrationAuthProvider.
|
|
566
|
+
* const provider = getIntegrationAuthProvider({ ... }) as unknown;
|
|
567
|
+
*
|
|
568
|
+
* if (isIntegrationAuthProvider(provider)) {
|
|
569
|
+
* // TypeScript knows provider is IntegrationAuthProvider
|
|
570
|
+
* const headers = provider.getHeaders("GET", "https://api.example.com");
|
|
571
|
+
* }
|
|
572
|
+
* ```
|
|
573
|
+
*/
|
|
574
|
+
function isIntegrationAuthProvider(provider) {
|
|
575
|
+
return typeof provider === "object" && provider !== null && "getHeaders" in provider && typeof provider.getHeaders === "function";
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Creates an {@link IntegrationAuthProvider} based on the provided configuration.
|
|
579
|
+
* @param authParams The configuration for the integration.
|
|
580
|
+
* @returns An {@link IntegrationAuthProvider} instance that can be used to get auth headers.
|
|
581
|
+
* @example
|
|
582
|
+
* ```typescript
|
|
583
|
+
* const config = {
|
|
584
|
+
* consumerKey: "your-consumer-key",
|
|
585
|
+
* consumerSecret: "your-consumer-secret",
|
|
586
|
+
* accessToken: "your-access-token",
|
|
587
|
+
* accessTokenSecret: "your-access-token-secret"
|
|
588
|
+
* };
|
|
589
|
+
*
|
|
590
|
+
* const authProvider = getIntegrationAuthProvider(config);
|
|
591
|
+
*
|
|
592
|
+
* // Get OAuth headers for a REST API call
|
|
593
|
+
* const headers = authProvider.getHeaders("GET", "https://your-store.com/rest/V1/products");
|
|
594
|
+
* console.log(headers); // { Authorization: "OAuth oauth_consumer_key=..., oauth_signature=..." }
|
|
595
|
+
*
|
|
596
|
+
* // Can also be used with URL objects
|
|
597
|
+
* const url = new URL("https://your-store.com/rest/V1/customers");
|
|
598
|
+
* const postHeaders = authProvider.getHeaders("POST", url);
|
|
599
|
+
* ```
|
|
600
|
+
*/
|
|
601
|
+
function getIntegrationAuthProvider(authParams) {
|
|
602
|
+
const oauth = new OAuth1a({
|
|
603
|
+
consumer: {
|
|
604
|
+
key: authParams.consumerKey,
|
|
605
|
+
secret: authParams.consumerSecret
|
|
606
|
+
},
|
|
607
|
+
signature_method: "HMAC-SHA256",
|
|
608
|
+
hash_function: (baseString, key) => crypto.createHmac("sha256", key).update(baseString).digest("base64")
|
|
609
|
+
});
|
|
610
|
+
const oauthToken = {
|
|
611
|
+
key: authParams.accessToken,
|
|
612
|
+
secret: authParams.accessTokenSecret
|
|
613
|
+
};
|
|
614
|
+
return { getHeaders: (method, url) => {
|
|
615
|
+
const urlString = parse(UrlSchema, url);
|
|
616
|
+
return oauth.toHeader(oauth.authorize({
|
|
617
|
+
url: urlString,
|
|
618
|
+
method
|
|
619
|
+
}, oauthToken));
|
|
620
|
+
} };
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
//#endregion
|
|
624
|
+
//#region source/lib/integration-auth/utils.ts
|
|
625
|
+
/**
|
|
626
|
+
* Parses the provided configuration for an {@link IntegrationAuthProvider}.
|
|
627
|
+
* @param config - The configuration to parse.
|
|
628
|
+
* @throws {CommerceSdkValidationError} If the configuration is invalid.
|
|
629
|
+
*
|
|
630
|
+
* @internal
|
|
631
|
+
*/
|
|
632
|
+
function __parseIntegrationAuthParams(config) {
|
|
633
|
+
const result = safeParse(IntegrationAuthParamsSchema, config);
|
|
634
|
+
if (!result.success) throw new CommerceSdkValidationError("Invalid IntegrationAuthProvider configuration", { issues: result.issues });
|
|
635
|
+
return result.output;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Asserts the provided configuration for an Adobe Commerce {@link IntegrationAuthProvider}.
|
|
639
|
+
* @param config The configuration to validate.
|
|
640
|
+
* @throws {CommerceSdkValidationError} If the configuration is invalid.
|
|
641
|
+
* @example
|
|
642
|
+
* ```typescript
|
|
643
|
+
* const config = {
|
|
644
|
+
* consumerKey: "your-consumer-key",
|
|
645
|
+
* consumerSecret: "your-consumer-secret",
|
|
646
|
+
* accessToken: "your-access-token",
|
|
647
|
+
* accessTokenSecret: "your-access-token-secret"
|
|
648
|
+
* };
|
|
649
|
+
*
|
|
650
|
+
* // This will validate the config and throw if invalid
|
|
651
|
+
* assertIntegrationAuthParams(config);
|
|
652
|
+
* ```
|
|
653
|
+
* @example
|
|
654
|
+
* ```typescript
|
|
655
|
+
* // Example of a failing assert:
|
|
656
|
+
* try {
|
|
657
|
+
* assertIntegrationAuthParams({
|
|
658
|
+
* consumerKey: "valid-consumer-key",
|
|
659
|
+
* // Missing required fields like consumerSecret, accessToken, accessTokenSecret
|
|
660
|
+
* });
|
|
661
|
+
* } catch (error) {
|
|
662
|
+
* console.error(error.message); // "Invalid IntegrationAuthProvider configuration"
|
|
663
|
+
* console.error(error.issues); // Array of validation issues
|
|
664
|
+
* }
|
|
665
|
+
* ```
|
|
666
|
+
*/
|
|
667
|
+
function assertIntegrationAuthParams(config) {
|
|
668
|
+
__parseIntegrationAuthParams(config);
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Resolves an {@link IntegrationAuthParams} from the given App Builder action inputs.
|
|
672
|
+
* @param params The App Builder action inputs to resolve the Integration authentication parameters from.
|
|
673
|
+
* @throws {CommerceSdkValidationError} If the parameters are invalid and cannot be resolved.
|
|
674
|
+
*
|
|
675
|
+
* @example
|
|
676
|
+
* ```typescript
|
|
677
|
+
* export function main(params) {
|
|
678
|
+
* const resolvedParams = resolveIntegrationAuthParams(params);
|
|
679
|
+
* console.log(resolvedParams); // { consumerKey: "your-consumer-key", consumerSecret: "your-consumer-secret", accessToken: "your-access-token", accessTokenSecret: "your-access-token-secret" }
|
|
680
|
+
* }
|
|
681
|
+
* ```
|
|
682
|
+
*/
|
|
683
|
+
function resolveIntegrationAuthParams(params) {
|
|
684
|
+
return __parseIntegrationAuthParams({
|
|
685
|
+
consumerKey: params.AIO_COMMERCE_AUTH_INTEGRATION_CONSUMER_KEY,
|
|
686
|
+
consumerSecret: params.AIO_COMMERCE_AUTH_INTEGRATION_CONSUMER_SECRET,
|
|
687
|
+
accessToken: params.AIO_COMMERCE_AUTH_INTEGRATION_ACCESS_TOKEN,
|
|
688
|
+
accessTokenSecret: params.AIO_COMMERCE_AUTH_INTEGRATION_ACCESS_TOKEN_SECRET
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
//#endregion
|
|
693
|
+
//#region source/lib/utils.ts
|
|
694
|
+
const IMS_AUTH_PARAMS = [
|
|
695
|
+
"AIO_COMMERCE_AUTH_IMS_CLIENT_ID",
|
|
696
|
+
"AIO_COMMERCE_AUTH_IMS_CLIENT_SECRETS",
|
|
697
|
+
"AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_ID",
|
|
698
|
+
"AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_EMAIL",
|
|
699
|
+
"AIO_COMMERCE_AUTH_IMS_ORG_ID",
|
|
700
|
+
"AIO_COMMERCE_AUTH_IMS_SCOPES"
|
|
701
|
+
];
|
|
702
|
+
const INTEGRATION_AUTH_PARAMS = [
|
|
703
|
+
"AIO_COMMERCE_AUTH_INTEGRATION_CONSUMER_KEY",
|
|
704
|
+
"AIO_COMMERCE_AUTH_INTEGRATION_CONSUMER_SECRET",
|
|
705
|
+
"AIO_COMMERCE_AUTH_INTEGRATION_ACCESS_TOKEN",
|
|
706
|
+
"AIO_COMMERCE_AUTH_INTEGRATION_ACCESS_TOKEN_SECRET"
|
|
707
|
+
];
|
|
708
|
+
/**
|
|
709
|
+
* Automatically detects and resolves authentication parameters from App Builder action inputs.
|
|
710
|
+
* Attempts to resolve IMS authentication first, then falls back to Integration authentication.
|
|
711
|
+
*
|
|
712
|
+
* @param params The App Builder action inputs containing authentication parameters.
|
|
713
|
+
* @throws {CommerceSdkValidationError} If the parameters are invalid.
|
|
714
|
+
* @throws {Error} If neither IMS nor Integration authentication parameters can be resolved.
|
|
715
|
+
* @example
|
|
716
|
+
* ```typescript
|
|
717
|
+
* // Automatic detection (will use IMS if IMS params are present, otherwise Integration)
|
|
718
|
+
* export function main(params) {
|
|
719
|
+
* const authProvider = resolveAuthParams(params);
|
|
720
|
+
* console.log(authProvider.strategy); // "ims" or "integration"
|
|
721
|
+
* }
|
|
722
|
+
* ```
|
|
723
|
+
*/
|
|
724
|
+
function resolveAuthParams(params) {
|
|
725
|
+
if (allNonEmpty(params, IMS_AUTH_PARAMS)) return {
|
|
726
|
+
...resolveImsAuthParams(params),
|
|
727
|
+
strategy: "ims"
|
|
728
|
+
};
|
|
729
|
+
if (allNonEmpty(params, INTEGRATION_AUTH_PARAMS)) return {
|
|
730
|
+
...resolveIntegrationAuthParams(params),
|
|
731
|
+
strategy: "integration"
|
|
732
|
+
};
|
|
733
|
+
throw new Error(`Can't resolve authentication options for the given params. Please provide either IMS options (${IMS_AUTH_PARAMS.join(", ")}) or Commerce integration options (${INTEGRATION_AUTH_PARAMS.join(", ")}).`);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
//#endregion
|
|
737
|
+
export { assertImsAuthParams, assertIntegrationAuthParams, forwardImsAuthProvider, getForwardedImsAuthProvider, getImsAuthProvider, getIntegrationAuthProvider, isImsAuthProvider, isIntegrationAuthProvider, resolveAuthParams };
|