@adobe/aio-commerce-lib-auth 0.1.0 → 0.2.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.
@@ -1,39 +1,88 @@
1
- import OAuth1a from "oauth-1.0a";
1
+ import { ErrorType, Result } from "@adobe/aio-commerce-lib-core/result";
2
+ import * as valibot0 from "valibot";
3
+ import { InferInput, InferIssue, InferOutput } from "valibot";
4
+ import { ValidationErrorType } from "@adobe/aio-commerce-lib-core/validation";
5
+ import * as url39 from "url";
2
6
 
3
- //#region source/lib/ims-auth.d.ts
4
- declare const IMS_AUTH_HEADERS: readonly ["Authorization", "x-api-key"];
5
- declare const IMS_AUTH_PARAMS: readonly ["AIO_COMMERCE_IMS_CLIENT_ID", "AIO_COMMERCE_IMS_CLIENT_SECRETS", "AIO_COMMERCE_IMS_TECHNICAL_ACCOUNT_ID", "AIO_COMMERCE_IMS_TECHNICAL_ACCOUNT_EMAIL", "AIO_COMMERCE_IMS_IMS_ORG_ID", "AIO_COMMERCE_IMS_SCOPES", "AIO_COMMERCE_IMS_ENV", "AIO_COMMERCE_IMS_CTX"];
6
- type ImsAuthParam = (typeof IMS_AUTH_PARAMS)[number];
7
- type ImsAuthParams = Partial<Record<ImsAuthParam, string>>;
8
- type ImsAuthHeader = (typeof IMS_AUTH_HEADERS)[number];
9
- type ImsAuthHeaders = Record<ImsAuthHeader, string>;
7
+ //#region source/lib/ims-auth/schema.d.ts
8
+ declare const IMS_AUTH_ENV: {
9
+ readonly PROD: "prod";
10
+ readonly STAGE: "stage";
11
+ };
12
+ declare const ImsAuthEnvSchema: valibot0.EnumSchema<{
13
+ readonly PROD: "prod";
14
+ readonly STAGE: "stage";
15
+ }, undefined>;
16
+ declare const ImsAuthParamsSchema: valibot0.ObjectSchema<{
17
+ readonly AIO_COMMERCE_IMS_CLIENT_ID: valibot0.SchemaWithPipe<readonly [valibot0.StringSchema<`Expected a string value for the IMS auth parameter ${string}`>, valibot0.NonEmptyAction<string, `Expected a non-empty string value for the IMS auth parameter ${string}`>]>;
18
+ readonly AIO_COMMERCE_IMS_CLIENT_SECRETS: valibot0.SchemaWithPipe<readonly [valibot0.SchemaWithPipe<readonly [valibot0.StringSchema<`Expected a string value for the IMS auth parameter ${string}`>, valibot0.NonEmptyAction<string, `Expected a non-empty string value for the IMS auth parameter ${string}`>, valibot0.ParseJsonAction<string, undefined, undefined>]>, valibot0.ArraySchema<valibot0.StringSchema<undefined>, `Expected a stringified JSON array value for the IMS auth parameter ${string}`>]>;
19
+ readonly AIO_COMMERCE_IMS_TECHNICAL_ACCOUNT_ID: valibot0.SchemaWithPipe<readonly [valibot0.StringSchema<`Expected a string value for the IMS auth parameter ${string}`>, valibot0.NonEmptyAction<string, `Expected a non-empty string value for the IMS auth parameter ${string}`>]>;
20
+ readonly AIO_COMMERCE_IMS_TECHNICAL_ACCOUNT_EMAIL: valibot0.SchemaWithPipe<readonly [valibot0.StringSchema<`Expected a string value for the IMS auth parameter ${string}`>, valibot0.NonEmptyAction<string, `Expected a non-empty string value for the IMS auth parameter ${string}`>]>;
21
+ readonly AIO_COMMERCE_IMS_IMS_ORG_ID: valibot0.SchemaWithPipe<readonly [valibot0.StringSchema<`Expected a string value for the IMS auth parameter ${string}`>, valibot0.NonEmptyAction<string, `Expected a non-empty string value for the IMS auth parameter ${string}`>]>;
22
+ readonly AIO_COMMERCE_IMS_ENV: valibot0.SchemaWithPipe<readonly [valibot0.OptionalSchema<valibot0.EnumSchema<{
23
+ readonly PROD: "prod";
24
+ readonly STAGE: "stage";
25
+ }, undefined>, "prod">]>;
26
+ readonly AIO_COMMERCE_IMS_CTX: valibot0.SchemaWithPipe<readonly [valibot0.OptionalSchema<valibot0.StringSchema<undefined>, "aio-commerce-sdk-creds">]>;
27
+ readonly AIO_COMMERCE_IMS_SCOPES: valibot0.SchemaWithPipe<readonly [valibot0.SchemaWithPipe<readonly [valibot0.StringSchema<`Expected a string value for the IMS auth parameter ${string}`>, valibot0.NonEmptyAction<string, `Expected a non-empty string value for the IMS auth parameter ${string}`>, valibot0.ParseJsonAction<string, undefined, undefined>]>, valibot0.ArraySchema<valibot0.StringSchema<undefined>, `Expected a stringified JSON array value for the IMS auth parameter ${string}`>]>;
28
+ }, undefined>;
29
+ type ImsAuthParams = InferOutput<typeof ImsAuthParamsSchema>;
30
+ type ImsAuthEnv = InferOutput<typeof ImsAuthEnvSchema>;
31
+ //#endregion
32
+ //#region source/lib/ims-auth/provider.d.ts
10
33
  type ImsAccessToken = string;
34
+ type ImsAuthHeader = "Authorization" | "x-api-key";
35
+ type ImsAuthHeaders = Record<ImsAuthHeader, string>;
36
+ type ImsAuthValidationError = ValidationErrorType<"ImsAuthValidationError", InferIssue<typeof ImsAuthParamsSchema>[]>;
37
+ type ImsAuthError<TError = unknown> = ErrorType<"ImsAuthError", {
38
+ message: string;
39
+ error: TError;
40
+ }>;
41
+ interface ImsAuthConfig {
42
+ clientId: string;
43
+ clientSecrets: string[];
44
+ technicalAccountId: string;
45
+ technicalAccountEmail: string;
46
+ imsOrgId: string;
47
+ scopes: string[];
48
+ environment: ImsAuthEnv;
49
+ context: string;
50
+ }
11
51
  interface ImsAuthProvider {
12
- getHeaders: () => Promise<ImsAuthHeaders>;
13
- getAccessToken: () => Promise<ImsAccessToken>;
52
+ getAccessToken: () => Promise<Result<ImsAccessToken, ImsAuthError>>;
53
+ getHeaders: () => Promise<Result<ImsAuthHeaders, ImsAuthError>>;
14
54
  }
15
- declare function getImsAuthProvider(params: ImsAuthParams): Promise<{
16
- getAccessToken: () => Promise<string>;
17
- getHeaders: () => Promise<{
18
- Authorization: string;
19
- "x-api-key": string;
20
- }>;
21
- } | undefined>;
55
+ declare function getImsAuthProvider(config: ImsAuthConfig): ImsAuthProvider;
56
+ declare function tryGetImsAuthProvider(params: InferInput<typeof ImsAuthParamsSchema>): Result<ImsAuthProvider, ImsAuthValidationError>;
57
+ //#endregion
58
+ //#region source/lib/integration-auth/schema.d.ts
59
+ declare const HttpMethodSchema: valibot0.PicklistSchema<readonly ["GET", "POST", "PUT", "PATCH", "DELETE"], undefined>;
60
+ type HttpMethodInput = InferInput<typeof HttpMethodSchema>;
61
+ declare const UrlSchema: valibot0.SchemaWithPipe<readonly [valibot0.UnionSchema<[valibot0.SchemaWithPipe<readonly [valibot0.StringSchema<"Expected a string for the Adobe Commerce endpoint">, valibot0.NonEmptyAction<string, "Expected a non-empty string for the Adobe Commerce endpoint">, valibot0.UrlAction<string, "Expected a valid url for the Adobe Commerce endpoint">]>, valibot0.InstanceSchema<typeof url39.URL, undefined>], undefined>, valibot0.TransformAction<string | url39.URL, string>]>;
62
+ type AdobeCommerceUri = InferInput<typeof UrlSchema>;
63
+ declare const IntegrationAuthParamsSchema: valibot0.NonOptionalSchema<valibot0.ObjectSchema<{
64
+ readonly AIO_COMMERCE_INTEGRATIONS_CONSUMER_KEY: valibot0.SchemaWithPipe<readonly [valibot0.StringSchema<`Expected a string value for the Commerce Integration parameter ${string}`>, valibot0.NonEmptyAction<string, `Expected a non-empty string value for the Commerce Integration parameter ${string}`>]>;
65
+ readonly AIO_COMMERCE_INTEGRATIONS_CONSUMER_SECRET: valibot0.SchemaWithPipe<readonly [valibot0.StringSchema<`Expected a string value for the Commerce Integration parameter ${string}`>, valibot0.NonEmptyAction<string, `Expected a non-empty string value for the Commerce Integration parameter ${string}`>]>;
66
+ readonly AIO_COMMERCE_INTEGRATIONS_ACCESS_TOKEN: valibot0.SchemaWithPipe<readonly [valibot0.StringSchema<`Expected a string value for the Commerce Integration parameter ${string}`>, valibot0.NonEmptyAction<string, `Expected a non-empty string value for the Commerce Integration parameter ${string}`>]>;
67
+ readonly AIO_COMMERCE_INTEGRATIONS_ACCESS_TOKEN_SECRET: valibot0.SchemaWithPipe<readonly [valibot0.StringSchema<`Expected a string value for the Commerce Integration parameter ${string}`>, valibot0.NonEmptyAction<string, `Expected a non-empty string value for the Commerce Integration parameter ${string}`>]>;
68
+ }, undefined>, undefined>;
69
+ type IntegrationAuthParams = InferInput<typeof IntegrationAuthParamsSchema>;
22
70
  //#endregion
23
- //#region source/lib/integration-auth.d.ts
24
- declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "PATCH", "DELETE"];
25
- declare const INTEGRATION_AUTH_HEADERS: readonly ["Authorization"];
26
- declare const INTEGRATION_AUTH_PARAMS: readonly ["AIO_COMMERCE_INTEGRATIONS_CONSUMER_KEY", "AIO_COMMERCE_INTEGRATIONS_CONSUMER_SECRET", "AIO_COMMERCE_INTEGRATIONS_ACCESS_TOKEN", "AIO_COMMERCE_INTEGRATIONS_ACCESS_TOKEN_SECRET"];
27
- type IntegrationAuthParam = (typeof INTEGRATION_AUTH_PARAMS)[number];
28
- type IntegrationAuthParams = Partial<Record<IntegrationAuthParam, string>>;
29
- type IntegrationAuthHeader = (typeof INTEGRATION_AUTH_HEADERS)[number];
71
+ //#region source/lib/integration-auth/provider.d.ts
72
+ type IntegrationAuthHeader = "Authorization";
30
73
  type IntegrationAuthHeaders = Record<IntegrationAuthHeader, string>;
31
- type HttpMethod = (typeof HTTP_METHODS)[number];
74
+ interface IntegrationConfig {
75
+ consumerKey: string;
76
+ consumerSecret: string;
77
+ accessToken: string;
78
+ accessTokenSecret: string;
79
+ }
80
+ type ValidationIssues = InferIssue<typeof IntegrationAuthParamsSchema>[] | InferIssue<typeof UrlSchema>[];
81
+ type IntegrationAuthError = ValidationErrorType<"IntegrationAuthValidationError", ValidationIssues>;
32
82
  interface IntegrationAuthProvider {
33
- getHeaders: (method: HttpMethod, url: string) => IntegrationAuthHeaders;
83
+ getHeaders: (method: HttpMethodInput, url: AdobeCommerceUri) => Result<IntegrationAuthHeaders, IntegrationAuthError>;
34
84
  }
35
- declare function getIntegrationAuthProvider(params: IntegrationAuthParams): {
36
- getHeaders(method: HttpMethod, url: string): OAuth1a.Header;
37
- } | undefined;
85
+ declare function getIntegrationAuthProvider(config: IntegrationConfig): IntegrationAuthProvider;
86
+ declare function tryGetIntegrationAuthProvider(params: IntegrationAuthParams): Result<IntegrationAuthProvider, IntegrationAuthError>;
38
87
  //#endregion
39
- export { ImsAuthHeader, ImsAuthHeaders, ImsAuthParam, ImsAuthParams, ImsAuthProvider, IntegrationAuthHeader, IntegrationAuthHeaders, IntegrationAuthParam, IntegrationAuthParams, IntegrationAuthProvider, getImsAuthProvider, getIntegrationAuthProvider };
88
+ export { IMS_AUTH_ENV, ImsAuthConfig, ImsAuthEnv, ImsAuthError, ImsAuthParams, ImsAuthProvider, IntegrationAuthError, IntegrationAuthParams, IntegrationAuthProvider, IntegrationConfig, getImsAuthProvider, getIntegrationAuthProvider, tryGetImsAuthProvider, tryGetIntegrationAuthProvider };
package/dist/es/index.js CHANGED
@@ -1,111 +1,201 @@
1
+ import { err, map, ok } from "@adobe/aio-commerce-lib-core/result";
1
2
  import { context, getToken } from "@adobe/aio-lib-ims";
3
+ import { array, enum as enum$1, instance, message, nonEmpty, nonOptional, object, optional, parseJson, picklist, pipe, safeParse, string, transform, union, url } from "valibot";
2
4
  import crypto from "node:crypto";
3
5
  import OAuth1a from "oauth-1.0a";
4
6
 
5
- //#region source/lib/params.ts
7
+ //#region source/lib/ims-auth/schema.ts
8
+ 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}`));
9
+ const jsonStringArray = (name) => {
10
+ const jsonStringArraySchema = message(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}`), parseJson()), `An error occurred while parsing the JSON string array parameter ${name}`);
11
+ return pipe(jsonStringArraySchema, array(string(), `Expected a stringified JSON array value for the IMS auth parameter ${name}`));
12
+ };
13
+ /** The environments accepted by the IMS auth service. */
14
+ const IMS_AUTH_ENV = {
15
+ PROD: "prod",
16
+ STAGE: "stage"
17
+ };
18
+ const ImsAuthEnvSchema = enum$1(IMS_AUTH_ENV);
19
+ /** Defines the schema to validate the necessary parameters for the IMS auth service. */
20
+ const ImsAuthParamsSchema = object({
21
+ AIO_COMMERCE_IMS_CLIENT_ID: imsAuthParameter("AIO_COMMERCE_IMS_CLIENT_ID"),
22
+ AIO_COMMERCE_IMS_CLIENT_SECRETS: jsonStringArray("AIO_COMMERCE_IMS_CLIENT_SECRETS"),
23
+ AIO_COMMERCE_IMS_TECHNICAL_ACCOUNT_ID: imsAuthParameter("AIO_COMMERCE_IMS_TECHNICAL_ACCOUNT_ID"),
24
+ AIO_COMMERCE_IMS_TECHNICAL_ACCOUNT_EMAIL: imsAuthParameter("AIO_COMMERCE_IMS_TECHNICAL_ACCOUNT_EMAIL"),
25
+ AIO_COMMERCE_IMS_IMS_ORG_ID: imsAuthParameter("AIO_COMMERCE_IMS_IMS_ORG_ID"),
26
+ AIO_COMMERCE_IMS_ENV: pipe(optional(ImsAuthEnvSchema, IMS_AUTH_ENV.PROD)),
27
+ AIO_COMMERCE_IMS_CTX: pipe(optional(string(), "aio-commerce-sdk-creds")),
28
+ AIO_COMMERCE_IMS_SCOPES: jsonStringArray("AIO_COMMERCE_IMS_SCOPES")
29
+ });
30
+
31
+ //#endregion
32
+ //#region source/lib/ims-auth/provider.ts
33
+ function snakeCaseImsAuthConfig(config) {
34
+ return {
35
+ ...config,
36
+ client_id: config.clientId,
37
+ client_secrets: config.clientSecrets,
38
+ technical_account_id: config.technicalAccountId,
39
+ technical_account_email: config.technicalAccountEmail,
40
+ ims_org_id: config.imsOrgId
41
+ };
42
+ }
43
+ function makeImsAuthValidationError(message$1, issues) {
44
+ return {
45
+ _tag: "ImsAuthValidationError",
46
+ message: message$1,
47
+ issues
48
+ };
49
+ }
50
+ function makeImsAuthError(message$1, error) {
51
+ return {
52
+ _tag: "ImsAuthError",
53
+ message: message$1,
54
+ error
55
+ };
56
+ }
57
+ function fromParams$1(params) {
58
+ return {
59
+ clientId: params.AIO_COMMERCE_IMS_CLIENT_ID,
60
+ clientSecrets: params.AIO_COMMERCE_IMS_CLIENT_SECRETS,
61
+ technicalAccountId: params.AIO_COMMERCE_IMS_TECHNICAL_ACCOUNT_ID,
62
+ technicalAccountEmail: params.AIO_COMMERCE_IMS_TECHNICAL_ACCOUNT_EMAIL,
63
+ imsOrgId: params.AIO_COMMERCE_IMS_IMS_ORG_ID,
64
+ scopes: params.AIO_COMMERCE_IMS_SCOPES,
65
+ environment: params.AIO_COMMERCE_IMS_ENV,
66
+ context: params.AIO_COMMERCE_IMS_CTX
67
+ };
68
+ }
69
+ async function tryGetAccessToken(contextName) {
70
+ try {
71
+ const accessToken = await getToken(contextName, {});
72
+ return ok(accessToken);
73
+ } catch (error) {
74
+ return err(makeImsAuthError("Failed to retrieve IMS access token", error));
75
+ }
76
+ }
6
77
  /**
7
- * Checks if the given value is non-empty.
8
- *
9
- * @param name of the parameter. Required because of `aio app dev` compatibility: inputs mapped to undefined env vars come as $<input_name> in dev mode, but as '' in prod mode.
10
- * @param value of the parameter.
78
+ * Creates an {@link ImsAuthProvider} based on the provided configuration.
79
+ * @param config The configuration for the IMS Auth Provider.
80
+ * @returns An {@link ImsAuthProvider} instance that can be used to get access token and auth headers.
11
81
  */
12
- function nonEmpty(name, value) {
13
- const v = value?.trim();
14
- return v !== void 0 && v !== `$${name}`;
82
+ function getImsAuthProvider(config) {
83
+ const getAccessToken = async () => {
84
+ const snakeCasedConfig = snakeCaseImsAuthConfig(config);
85
+ await context.set(config.context, snakeCasedConfig);
86
+ return tryGetAccessToken(config.context);
87
+ };
88
+ const getHeaders = async () => {
89
+ const result = await getAccessToken();
90
+ return map(result, (accessToken) => ({
91
+ Authorization: `Bearer ${accessToken}`,
92
+ "x-api-key": config.clientId
93
+ }));
94
+ };
95
+ return {
96
+ getAccessToken,
97
+ getHeaders
98
+ };
15
99
  }
16
100
  /**
17
- * Checks if all required parameters are non-empty.
18
- * @param params action input parameters.
19
- * @param required list of required parameter names.
101
+ * Tries to create an {@link ImsAuthProvider} based on the provided parameters.
102
+ * @param params The parameters required to create the IMS Auth Provider.
103
+ * @returns An {@link ImsAuthProvider} instance that can be used to get access token and auth headers.
20
104
  */
21
- function allNonEmpty(params, required) {
22
- return required.every((name) => nonEmpty(name, params[name]));
105
+ function tryGetImsAuthProvider(params) {
106
+ const validation = safeParse(ImsAuthParamsSchema, params);
107
+ if (!validation.success) return err(makeImsAuthValidationError("Failed to validate the provided IMS parameters", validation.issues));
108
+ return ok(getImsAuthProvider(fromParams$1(validation.output)));
23
109
  }
24
110
 
25
111
  //#endregion
26
- //#region source/lib/ims-auth.ts
112
+ //#region source/lib/integration-auth/schema.ts
27
113
  /**
28
- * If the required IMS parameters are present, this function returns an {@link ImsAuthProvider}.
29
- * @param params includes IMS parameters
114
+ * The HTTP methods supported by Commerce.
115
+ * This is used to determine which headers to include in the signing of the authorization header.
30
116
  */
31
- async function getImsAuthProvider(params) {
32
- const config = resolveImsConfig(params);
33
- if (config) {
34
- const contextName = params.AIO_COMMERCE_IMS_CTX ?? "aio-commerce-sdk-creds";
35
- await context.set(contextName, config);
36
- return {
37
- getAccessToken: async () => getToken(contextName, {}),
38
- getHeaders: async () => {
39
- const accessToken = await getToken(contextName, {});
40
- return {
41
- Authorization: `Bearer ${accessToken}`,
42
- "x-api-key": config.client_id
43
- };
44
- }
45
- };
46
- }
47
- }
48
- function resolveImsConfig(params) {
49
- if (allNonEmpty(params, [
50
- "AIO_COMMERCE_IMS_CLIENT_ID",
51
- "AIO_COMMERCE_IMS_CLIENT_SECRETS",
52
- "AIO_COMMERCE_IMS_TECHNICAL_ACCOUNT_ID",
53
- "AIO_COMMERCE_IMS_TECHNICAL_ACCOUNT_EMAIL",
54
- "AIO_COMMERCE_IMS_IMS_ORG_ID",
55
- "AIO_COMMERCE_IMS_SCOPES"
56
- ])) return {
57
- client_id: params.AIO_COMMERCE_IMS_CLIENT_ID,
58
- client_secrets: JSON.parse(params.AIO_COMMERCE_IMS_CLIENT_SECRETS ?? "[]"),
59
- technical_account_id: params.AIO_COMMERCE_IMS_TECHNICAL_ACCOUNT_ID,
60
- technical_account_email: params.AIO_COMMERCE_IMS_TECHNICAL_ACCOUNT_EMAIL,
61
- ims_org_id: params.AIO_COMMERCE_IMS_IMS_ORG_ID,
62
- scopes: JSON.parse(params.AIO_COMMERCE_IMS_SCOPES ?? "[]"),
63
- environment: params.AIO_COMMERCE_IMS_ENV ?? "prod"
64
- };
65
- }
66
-
67
- //#endregion
68
- //#region source/lib/integration-auth.ts
117
+ const AllowedHttpMethod = [
118
+ "GET",
119
+ "POST",
120
+ "PUT",
121
+ "PATCH",
122
+ "DELETE"
123
+ ];
124
+ const HttpMethodSchema = picklist(AllowedHttpMethod);
125
+ 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}`));
126
+ 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"));
127
+ const UrlSchema = pipe(union([BaseUrlSchema, instance(URL)]), transform((url$1) => {
128
+ if (url$1 instanceof URL) return url$1.toString();
129
+ return url$1;
130
+ }));
69
131
  /**
70
- * If the required integration parameters are present, this function returns an {@link IntegrationAuthProvider}.
71
- * @param params includes integration parameters
132
+ * The schema for the Commerce Integration parameters.
133
+ * This is used to validate the parameters passed to the Commerce Integration provider.
72
134
  */
73
- function getIntegrationAuthProvider(params) {
74
- const config = resolveIntegrationConfig(params);
75
- if (config) {
76
- const oauth = new OAuth1a({
77
- consumer: {
78
- key: config.consumerKey,
79
- secret: config.consumerSecret
80
- },
81
- signature_method: "HMAC-SHA256",
82
- hash_function: (baseString, key) => crypto.createHmac("sha256", key).update(baseString).digest("base64")
83
- });
84
- const oauthToken = {
85
- key: config.accessToken,
86
- secret: config.accessTokenSecret
87
- };
88
- return { getHeaders(method, url) {
89
- return oauth.toHeader(oauth.authorize({
90
- url,
91
- method
92
- }, oauthToken));
93
- } };
94
- }
135
+ const IntegrationAuthParamsSchema = nonOptional(object({
136
+ AIO_COMMERCE_INTEGRATIONS_CONSUMER_KEY: integrationAuthParameter("AIO_COMMERCE_INTEGRATIONS_CONSUMER_KEY"),
137
+ AIO_COMMERCE_INTEGRATIONS_CONSUMER_SECRET: integrationAuthParameter("AIO_COMMERCE_INTEGRATIONS_CONSUMER_SECRET"),
138
+ AIO_COMMERCE_INTEGRATIONS_ACCESS_TOKEN: integrationAuthParameter("AIO_COMMERCE_INTEGRATIONS_ACCESS_TOKEN"),
139
+ AIO_COMMERCE_INTEGRATIONS_ACCESS_TOKEN_SECRET: integrationAuthParameter("AIO_COMMERCE_INTEGRATIONS_ACCESS_TOKEN_SECRET")
140
+ }));
141
+
142
+ //#endregion
143
+ //#region source/lib/integration-auth/provider.ts
144
+ function makeIntegrationAuthValidationError(message$1, issues) {
145
+ return {
146
+ _tag: "IntegrationAuthValidationError",
147
+ message: message$1,
148
+ issues
149
+ };
95
150
  }
96
- function resolveIntegrationConfig(params) {
97
- if (allNonEmpty(params, [
98
- "AIO_COMMERCE_INTEGRATIONS_CONSUMER_KEY",
99
- "AIO_COMMERCE_INTEGRATIONS_CONSUMER_SECRET",
100
- "AIO_COMMERCE_INTEGRATIONS_ACCESS_TOKEN",
101
- "AIO_COMMERCE_INTEGRATIONS_ACCESS_TOKEN_SECRET"
102
- ])) return {
151
+ function fromParams(params) {
152
+ return {
103
153
  consumerKey: params.AIO_COMMERCE_INTEGRATIONS_CONSUMER_KEY,
104
154
  consumerSecret: params.AIO_COMMERCE_INTEGRATIONS_CONSUMER_SECRET,
105
155
  accessToken: params.AIO_COMMERCE_INTEGRATIONS_ACCESS_TOKEN,
106
156
  accessTokenSecret: params.AIO_COMMERCE_INTEGRATIONS_ACCESS_TOKEN_SECRET
107
157
  };
108
158
  }
159
+ /**
160
+ * Creates an {@link IntegrationAuthProvider} based on the provided configuration.
161
+ * @param config The configuration for the integration.
162
+ * @returns An {@link IntegrationAuthProvider} instance that can be used to get auth headers.
163
+ */
164
+ function getIntegrationAuthProvider(config) {
165
+ const oauth = new OAuth1a({
166
+ consumer: {
167
+ key: config.consumerKey,
168
+ secret: config.consumerSecret
169
+ },
170
+ signature_method: "HMAC-SHA256",
171
+ hash_function: (baseString, key) => crypto.createHmac("sha256", key).update(baseString).digest("base64")
172
+ });
173
+ const oauthToken = {
174
+ key: config.accessToken,
175
+ secret: config.accessTokenSecret
176
+ };
177
+ const getHeaders = (method, url$1) => {
178
+ const uriValidation = safeParse(UrlSchema, url$1);
179
+ if (!uriValidation.success) return err(makeIntegrationAuthValidationError("Failed to validate the provided Adobe Commerce URL", uriValidation.issues));
180
+ const finalUrl = uriValidation.output;
181
+ const headers = oauth.toHeader(oauth.authorize({
182
+ url: finalUrl,
183
+ method
184
+ }, oauthToken));
185
+ return ok(headers);
186
+ };
187
+ return { getHeaders };
188
+ }
189
+ /**
190
+ * Tries to create an {@link IntegrationAuthProvider} based on the provided parameters.
191
+ * @param params The parameters required for integration authentication.
192
+ * @returns An {@link IntegrationAuthProvider} instance that can be used to get auth headers.
193
+ */
194
+ function tryGetIntegrationAuthProvider(params) {
195
+ const validation = safeParse(IntegrationAuthParamsSchema, params);
196
+ if (!validation.success) return err(makeIntegrationAuthValidationError("Failed to validate the provided integration parameters", validation.issues));
197
+ return ok(getIntegrationAuthProvider(fromParams(validation.output)));
198
+ }
109
199
 
110
200
  //#endregion
111
- export { getImsAuthProvider, getIntegrationAuthProvider };
201
+ export { IMS_AUTH_ENV, getImsAuthProvider, getIntegrationAuthProvider, tryGetImsAuthProvider, tryGetIntegrationAuthProvider };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@adobe/aio-commerce-lib-auth",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "private": false,
6
6
  "author": "Adobe Inc.",
7
7
  "engines": {
@@ -42,7 +42,10 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@adobe/aio-lib-ims": "^8.1.0",
45
- "oauth-1.0a": "^2.2.6"
45
+ "ansis": "^4.1.0",
46
+ "oauth-1.0a": "^2.2.6",
47
+ "valibot": "^1.1.0",
48
+ "@adobe/aio-commerce-lib-core": "0.2.0"
46
49
  },
47
50
  "devDependencies": {
48
51
  "vitest": "^3.2.4",
package/source/index.ts CHANGED
@@ -10,5 +10,26 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- export * from "./lib/ims-auth";
14
- export * from "./lib/integration-auth";
13
+ export {
14
+ getImsAuthProvider,
15
+ type ImsAuthConfig,
16
+ type ImsAuthError,
17
+ type ImsAuthProvider,
18
+ tryGetImsAuthProvider,
19
+ } from "./lib/ims-auth/provider";
20
+
21
+ export {
22
+ IMS_AUTH_ENV,
23
+ type ImsAuthEnv,
24
+ type ImsAuthParams,
25
+ } from "./lib/ims-auth/schema";
26
+
27
+ export {
28
+ getIntegrationAuthProvider,
29
+ type IntegrationAuthError,
30
+ type IntegrationAuthProvider,
31
+ type IntegrationConfig,
32
+ tryGetIntegrationAuthProvider,
33
+ } from "./lib/integration-auth/provider";
34
+
35
+ export type { IntegrationAuthParams } from "./lib/integration-auth/schema";
@@ -0,0 +1,160 @@
1
+ import {
2
+ type ErrorType,
3
+ err,
4
+ map,
5
+ ok,
6
+ type Result,
7
+ } from "@adobe/aio-commerce-lib-core/result";
8
+
9
+ import type { ValidationErrorType } from "@adobe/aio-commerce-lib-core/validation";
10
+ import { context, getToken } from "@adobe/aio-lib-ims";
11
+ import type { SnakeCasedProperties } from "type-fest";
12
+ import { type InferInput, type InferIssue, safeParse } from "valibot";
13
+
14
+ import {
15
+ type ImsAuthEnv,
16
+ type ImsAuthParams,
17
+ ImsAuthParamsSchema,
18
+ } from "./schema";
19
+
20
+ type ImsAccessToken = string;
21
+ type ImsAuthHeader = "Authorization" | "x-api-key";
22
+ type ImsAuthHeaders = Record<ImsAuthHeader, string>;
23
+
24
+ /** Defines a validation error type for the IMS auth service. */
25
+ export type ImsAuthValidationError = ValidationErrorType<
26
+ "ImsAuthValidationError",
27
+ InferIssue<typeof ImsAuthParamsSchema>[]
28
+ >;
29
+
30
+ /** Defines an error type for the IMS auth service. */
31
+ export type ImsAuthError<TError = unknown> = ErrorType<
32
+ "ImsAuthError",
33
+ {
34
+ message: string;
35
+ error: TError;
36
+ }
37
+ >;
38
+
39
+ /** Defines the configuration options to create an {@link ImsAuthProvider}. */
40
+ export interface ImsAuthConfig {
41
+ clientId: string;
42
+ clientSecrets: string[];
43
+ technicalAccountId: string;
44
+ technicalAccountEmail: string;
45
+ imsOrgId: string;
46
+ scopes: string[];
47
+ environment: ImsAuthEnv;
48
+ context: string;
49
+ }
50
+
51
+ /** Defines an authentication provider for Adobe IMS. */
52
+ export interface ImsAuthProvider {
53
+ getAccessToken: () => Promise<Result<ImsAccessToken, ImsAuthError>>;
54
+ getHeaders: () => Promise<Result<ImsAuthHeaders, ImsAuthError>>;
55
+ }
56
+
57
+ function snakeCaseImsAuthConfig(
58
+ config: ImsAuthConfig,
59
+ ): SnakeCasedProperties<ImsAuthConfig> {
60
+ return {
61
+ ...config,
62
+ client_id: config.clientId,
63
+ client_secrets: config.clientSecrets,
64
+ technical_account_id: config.technicalAccountId,
65
+ technical_account_email: config.technicalAccountEmail,
66
+ ims_org_id: config.imsOrgId,
67
+ };
68
+ }
69
+
70
+ function makeImsAuthValidationError(
71
+ message: string,
72
+ issues: InferIssue<typeof ImsAuthParamsSchema>[],
73
+ ) {
74
+ return {
75
+ _tag: "ImsAuthValidationError",
76
+ message,
77
+ issues,
78
+ } satisfies ImsAuthValidationError;
79
+ }
80
+
81
+ function makeImsAuthError(message: string, error: unknown) {
82
+ return {
83
+ _tag: "ImsAuthError",
84
+ message,
85
+ error,
86
+ } satisfies ImsAuthError;
87
+ }
88
+
89
+ function fromParams(params: ImsAuthParams) {
90
+ return {
91
+ clientId: params.AIO_COMMERCE_IMS_CLIENT_ID,
92
+ clientSecrets: params.AIO_COMMERCE_IMS_CLIENT_SECRETS,
93
+ technicalAccountId: params.AIO_COMMERCE_IMS_TECHNICAL_ACCOUNT_ID,
94
+ technicalAccountEmail: params.AIO_COMMERCE_IMS_TECHNICAL_ACCOUNT_EMAIL,
95
+ imsOrgId: params.AIO_COMMERCE_IMS_IMS_ORG_ID,
96
+ scopes: params.AIO_COMMERCE_IMS_SCOPES,
97
+ environment: params.AIO_COMMERCE_IMS_ENV,
98
+ context: params.AIO_COMMERCE_IMS_CTX,
99
+ } satisfies ImsAuthConfig;
100
+ }
101
+
102
+ async function tryGetAccessToken(
103
+ contextName: string,
104
+ ): Promise<Result<ImsAccessToken, ImsAuthError>> {
105
+ try {
106
+ const accessToken = await getToken(contextName, {});
107
+ return ok(accessToken);
108
+ } catch (error) {
109
+ return err(makeImsAuthError("Failed to retrieve IMS access token", error));
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Creates an {@link ImsAuthProvider} based on the provided configuration.
115
+ * @param config The configuration for the IMS Auth Provider.
116
+ * @returns An {@link ImsAuthProvider} instance that can be used to get access token and auth headers.
117
+ */
118
+ export function getImsAuthProvider(config: ImsAuthConfig): ImsAuthProvider {
119
+ const getAccessToken = async () => {
120
+ const snakeCasedConfig = snakeCaseImsAuthConfig(config);
121
+
122
+ await context.set(config.context, snakeCasedConfig);
123
+ return tryGetAccessToken(config.context);
124
+ };
125
+
126
+ const getHeaders = async () => {
127
+ const result = await getAccessToken();
128
+ return map(result, (accessToken) => ({
129
+ Authorization: `Bearer ${accessToken}`,
130
+ "x-api-key": config.clientId,
131
+ }));
132
+ };
133
+
134
+ return {
135
+ getAccessToken,
136
+ getHeaders,
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Tries to create an {@link ImsAuthProvider} based on the provided parameters.
142
+ * @param params The parameters required to create the IMS Auth Provider.
143
+ * @returns An {@link ImsAuthProvider} instance that can be used to get access token and auth headers.
144
+ */
145
+ export function tryGetImsAuthProvider(
146
+ params: InferInput<typeof ImsAuthParamsSchema>,
147
+ ): Result<ImsAuthProvider, ImsAuthValidationError> {
148
+ const validation = safeParse(ImsAuthParamsSchema, params);
149
+
150
+ if (!validation.success) {
151
+ return err(
152
+ makeImsAuthValidationError(
153
+ "Failed to validate the provided IMS parameters",
154
+ validation.issues,
155
+ ),
156
+ );
157
+ }
158
+
159
+ return ok(getImsAuthProvider(fromParams(validation.output)));
160
+ }