@scayle/storefront-core 7.33.0 → 7.34.1

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @scayle/storefront-core
2
2
 
3
+ ## 7.34.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Fix type errors
8
+
9
+ ## 7.34.0
10
+
11
+ ### Minor Changes
12
+
13
+ - We added support for Identity Provider (IDP), enhancing our authentication and identity management capabilities.
14
+
3
15
  ## 7.33.0
4
16
 
5
17
  ### Minor Changes
@@ -40,6 +40,7 @@ class OAuthClient {
40
40
  headers;
41
41
  baseURL;
42
42
  logger;
43
+ clientId;
43
44
  constructor(options, logger) {
44
45
  const {
45
46
  clientId,
@@ -49,6 +50,7 @@ class OAuthClient {
49
50
  if (!clientId || !clientSecret) {
50
51
  throw new MissingCredentialsError();
51
52
  }
53
+ this.clientId = clientId;
52
54
  this.baseURL = `${apiHost}/v1`;
53
55
  this.logger = logger ? logger.space("auth-client") : void 0;
54
56
  const basicAuthHash = (0, _hash.encodeBase64)(`${clientId}:${clientSecret}`);
@@ -174,5 +176,19 @@ class OAuthClient {
174
176
  }
175
177
  }).then(emptyOAuthResponseHandler);
176
178
  }
179
+ /**
180
+ * Revoke all tokens for a user
181
+ * @param userId
182
+ */
183
+ async generateToken(code) {
184
+ return await fetch(`${this.baseURL}/oauth/token`, {
185
+ method: "POST",
186
+ headers: this.headers,
187
+ body: JSON.stringify({
188
+ grant_type: "authorization_code",
189
+ code
190
+ })
191
+ }).then(oauthResponseHandler);
192
+ }
177
193
  }
178
194
  exports.OAuthClient = OAuthClient;
@@ -15,6 +15,7 @@ export declare class OAuthClient {
15
15
  headers: HeadersInit;
16
16
  baseURL: string;
17
17
  logger?: Log;
18
+ clientId?: string;
18
19
  constructor(options: OAuthOptions, logger?: Log);
19
20
  /**
20
21
  * Register a user and retrieve a token set
@@ -25,9 +26,7 @@ export declare class OAuthClient {
25
26
  * Execute a user login on the OAuth API and receive a token set
26
27
  * @param payload
27
28
  */
28
- login(payload: LoginRequest & {
29
- shopId: string;
30
- }): Promise<Oauth>;
29
+ login(payload: LoginRequest): Promise<Oauth>;
31
30
  /**
32
31
  * Execute a guest user login on the OAuth API and receive a token set
33
32
  * @param payload
@@ -64,4 +63,9 @@ export declare class OAuthClient {
64
63
  * @param accessToken
65
64
  */
66
65
  revokeToken(accessToken: string): Promise<void>;
66
+ /**
67
+ * Revoke all tokens for a user
68
+ * @param userId
69
+ */
70
+ generateToken(code: string): Promise<Oauth>;
67
71
  }
@@ -29,11 +29,13 @@ export class OAuthClient {
29
29
  headers;
30
30
  baseURL;
31
31
  logger;
32
+ clientId;
32
33
  constructor(options, logger) {
33
34
  const { clientId, clientSecret, apiHost } = options;
34
35
  if (!clientId || !clientSecret) {
35
36
  throw new MissingCredentialsError();
36
37
  }
38
+ this.clientId = clientId;
37
39
  this.baseURL = `${apiHost}/v1`;
38
40
  this.logger = logger ? logger.space("auth-client") : void 0;
39
41
  const basicAuthHash = encodeBase64(`${clientId}:${clientSecret}`);
@@ -159,4 +161,18 @@ export class OAuthClient {
159
161
  }
160
162
  }).then(emptyOAuthResponseHandler);
161
163
  }
164
+ /**
165
+ * Revoke all tokens for a user
166
+ * @param userId
167
+ */
168
+ async generateToken(code) {
169
+ return await fetch(`${this.baseURL}/oauth/token`, {
170
+ method: "POST",
171
+ headers: this.headers,
172
+ body: JSON.stringify({
173
+ grant_type: "authorization_code",
174
+ code
175
+ })
176
+ }).then(oauthResponseHandler);
177
+ }
162
178
  }
@@ -156,4 +156,15 @@ Object.keys(_promotion).forEach(function (key) {
156
156
  return _promotion[key];
157
157
  }
158
158
  });
159
+ });
160
+ var _idp = require("./oauth/idp.cjs");
161
+ Object.keys(_idp).forEach(function (key) {
162
+ if (key === "default" || key === "__esModule") return;
163
+ if (key in exports && exports[key] === _idp[key]) return;
164
+ Object.defineProperty(exports, key, {
165
+ enumerable: true,
166
+ get: function () {
167
+ return _idp[key];
168
+ }
169
+ });
159
170
  });
@@ -12,3 +12,4 @@ export * from './variants';
12
12
  export * from './navigationTrees';
13
13
  export * from './session';
14
14
  export * from './promotion';
15
+ export * from './oauth/idp';
@@ -12,3 +12,4 @@ export * from "./variants.mjs";
12
12
  export * from "./navigationTrees.mjs";
13
13
  export * from "./session.mjs";
14
14
  export * from "./promotion.mjs";
15
+ export * from "./oauth/idp.mjs";
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.handleIDPLoginCallback = exports.getExternalIdpRedirect = void 0;
7
+ var _jose = require("jose");
8
+ var _oauth = require("../../../api/oauth.cjs");
9
+ const getExternalIdpRedirect = exports.getExternalIdpRedirect = async function getExternalIdpRedirect2(context) {
10
+ const shopId = context.shopId.toString();
11
+ const OAuthClient = (0, _oauth.getOAuthClient)(context);
12
+ const checkoutSecret = context.checkout.secret;
13
+ const isIDPEnabled = context.idp.enabled;
14
+ const IDPKeys = context.idp.idpKeys;
15
+ const IDPRedirectURL = context.idp.idpRedirectURL;
16
+ if (!isIDPEnabled) {
17
+ throw new Error("IDP disabled");
18
+ }
19
+ if (!IDPKeys.length) {
20
+ throw new Error("No IDP keys configured");
21
+ }
22
+ if (!IDPRedirectURL) {
23
+ throw new Error("No redirect URL");
24
+ }
25
+ if (!shopId) {
26
+ throw new Error("Missing shopId");
27
+ }
28
+ const secret = new TextEncoder().encode(checkoutSecret);
29
+ const results = await Promise.all(IDPKeys.map(async idpKey => {
30
+ const jwtPayload = await new _jose.SignJWT({
31
+ idpKey,
32
+ callbackUrl: IDPRedirectURL,
33
+ clientId: OAuthClient.clientId.toString()
34
+ }).setProtectedHeader({
35
+ alg: "HS256",
36
+ typ: "JWT"
37
+ }).setIssuedAt().setExpirationTime("2h").sign(secret);
38
+ return [idpKey, `${OAuthClient.baseURL}/auth/external/redirect?shopId=${shopId}&jwt=${jwtPayload}`];
39
+ }));
40
+ return Object.fromEntries(results);
41
+ };
42
+ const handleIDPLoginCallback = exports.handleIDPLoginCallback = async function handleIDPLoginCallback2(code, context) {
43
+ const OAuthClient = (0, _oauth.getOAuthClient)(context);
44
+ const {
45
+ access_token: accessToken,
46
+ refresh_token: refreshToken
47
+ } = await OAuthClient.generateToken(code);
48
+ context.updateTokens({
49
+ accessToken,
50
+ refreshToken
51
+ });
52
+ return {
53
+ message: "success"
54
+ };
55
+ };
@@ -0,0 +1,7 @@
1
+ import type { RpcContext } from '../../../types';
2
+ export declare const getExternalIdpRedirect: (context: RpcContext) => Promise<{
3
+ [k: string]: string;
4
+ }>;
5
+ export declare const handleIDPLoginCallback: (code: string, context: RpcContext) => Promise<{
6
+ message: string;
7
+ }>;
@@ -0,0 +1,48 @@
1
+ import { SignJWT } from "jose";
2
+ import { getOAuthClient } from "../../../api/oauth.mjs";
3
+ export const getExternalIdpRedirect = async function getExternalIdpRedirect2(context) {
4
+ const shopId = context.shopId.toString();
5
+ const OAuthClient = getOAuthClient(context);
6
+ const checkoutSecret = context.checkout.secret;
7
+ const isIDPEnabled = context.idp.enabled;
8
+ const IDPKeys = context.idp.idpKeys;
9
+ const IDPRedirectURL = context.idp.idpRedirectURL;
10
+ if (!isIDPEnabled) {
11
+ throw new Error("IDP disabled");
12
+ }
13
+ if (!IDPKeys.length) {
14
+ throw new Error("No IDP keys configured");
15
+ }
16
+ if (!IDPRedirectURL) {
17
+ throw new Error("No redirect URL");
18
+ }
19
+ if (!shopId) {
20
+ throw new Error("Missing shopId");
21
+ }
22
+ const secret = new TextEncoder().encode(checkoutSecret);
23
+ const results = await Promise.all(
24
+ IDPKeys.map(async (idpKey) => {
25
+ const jwtPayload = await new SignJWT({
26
+ idpKey,
27
+ callbackUrl: IDPRedirectURL,
28
+ clientId: OAuthClient.clientId.toString()
29
+ }).setProtectedHeader({ alg: "HS256", typ: "JWT" }).setIssuedAt().setExpirationTime("2h").sign(secret);
30
+ return [
31
+ idpKey,
32
+ `${OAuthClient.baseURL}/auth/external/redirect?shopId=${shopId}&jwt=${jwtPayload}`
33
+ ];
34
+ })
35
+ );
36
+ return Object.fromEntries(results);
37
+ };
38
+ export const handleIDPLoginCallback = async function handleIDPLoginCallback2(code, context) {
39
+ const OAuthClient = getOAuthClient(context);
40
+ const { access_token: accessToken, refresh_token: refreshToken } = await OAuthClient.generateToken(code);
41
+ context.updateTokens({
42
+ accessToken,
43
+ refreshToken
44
+ });
45
+ return {
46
+ message: "success"
47
+ };
48
+ };
@@ -76,7 +76,7 @@ const oauthGuestLogin = async (guest, context) => {
76
76
  const shopId = context.shopId;
77
77
  const client = (0, _oauth.getOAuthClient)(context);
78
78
  try {
79
- const tokens = client.guestLogin({
79
+ const tokens = await client.guestLogin({
80
80
  ...guest,
81
81
  shop_id: shopId
82
82
  });
@@ -105,7 +105,7 @@ const refreshAccessToken = async context => {
105
105
  refreshToken: tokens?.refresh_token
106
106
  });
107
107
  return {
108
- success: !!response.data
108
+ success: !!tokens
109
109
  };
110
110
  } catch (error) {
111
111
  const err = convertErrorForRpcCall(error, [_constants.HttpStatusCode.BAD_REQUEST, _constants.HttpStatusCode.UNAUTHORIZED]);
@@ -83,7 +83,7 @@ export const oauthGuestLogin = async (guest, context) => {
83
83
  const shopId = context.shopId;
84
84
  const client = getOAuthClient(context);
85
85
  try {
86
- const tokens = client.guestLogin({
86
+ const tokens = await client.guestLogin({
87
87
  ...guest,
88
88
  shop_id: shopId
89
89
  });
@@ -114,7 +114,7 @@ export const refreshAccessToken = async (context) => {
114
114
  accessToken: tokens?.access_token,
115
115
  refreshToken: tokens?.refresh_token
116
116
  });
117
- return { success: !!response.data };
117
+ return { success: !!tokens };
118
118
  } catch (error) {
119
119
  const err = convertErrorForRpcCall(error, [
120
120
  HttpStatusCode.BAD_REQUEST,
@@ -1,4 +1,5 @@
1
- import type { ProductWith, VariantDetail } from '@aboutyou/backbone';
1
+ import type { ProductWith } from '@aboutyou/backbone';
2
+ import type { VariantDetail } from '@aboutyou/backbone/endpoints/variants/variantsByIds';
2
3
  import { RpcHandler } from '../../types';
3
4
  export interface FetchVariantsParams {
4
5
  ids: number[];
@@ -49,6 +49,8 @@ export interface GuestRequest {
49
49
  }
50
50
  export interface SendResetPasswordEmailRequest {
51
51
  email: string;
52
+ reset_url: string;
53
+ shop_id: number;
52
54
  }
53
55
  export interface UpdatePasswordByHashRequest {
54
56
  password: string;
@@ -69,4 +71,14 @@ export interface Oauth {
69
71
  access_token: string;
70
72
  refresh_token: string;
71
73
  }
74
+ /**
75
+ * This adds `string` so it's flexible enough to support
76
+ * multiple IDPs since we don't know what they are.
77
+ * More narrow type can be defined in the nuxt modules.
78
+ */
79
+ export interface IDPConfig {
80
+ enabled: boolean;
81
+ idpKeys: string[];
82
+ idpRedirectURL: string;
83
+ }
72
84
  export {};
@@ -3,7 +3,7 @@ import { Log } from '../../utils';
3
3
  import { CachedType } from '../../cache/cached';
4
4
  import { ShopUser } from '../user';
5
5
  import type { WishlistWithOptions, BasketWithOptions, ProductWith, VariantWith, SearchWith, CategoryWith, ProductCategoryWith } from '../';
6
- import { OAuthTokens } from './auth';
6
+ import { OAuthTokens, IDPConfig } from './auth';
7
7
  export type WithParams = Partial<{
8
8
  basket: BasketWithOptions;
9
9
  wishlist: WishlistWithOptions;
@@ -62,4 +62,5 @@ export interface RpcContext {
62
62
  clientSecret: string;
63
63
  };
64
64
  runtimeConfiguration: RuntimeConfiguration;
65
+ idp?: IDPConfig;
65
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scayle/storefront-core",
3
- "version": "7.33.0",
3
+ "version": "7.34.1",
4
4
  "description": "Collection of essential utilities to work with the Storefront API",
5
5
  "author": "SCAYLE Commerce Engine",
6
6
  "license": "MIT",
@@ -40,6 +40,7 @@
40
40
  "dist",
41
41
  "utils"
42
42
  ],
43
+ "type": "module",
43
44
  "engines": {
44
45
  "node": ">= 18.15.0"
45
46
  },
@@ -47,13 +48,15 @@
47
48
  "clean": "rimraf ./dist",
48
49
  "build:legacy": "tsc -p tsconfig.legacy.json",
49
50
  "build": "unbuild",
51
+ "typecheck": "tsc --noEmit -p tsconfig.json",
50
52
  "format": "prettier --ignore-unknown --log-level warn --check .",
51
53
  "format:fix": "prettier --ignore-unknown --log-level warn --write .",
52
54
  "lint": "eslint . --format gitlab",
53
55
  "lint:fix": "eslint . --fix",
54
56
  "package:lint": "publint",
55
- "test": "jest --passWithNoTests --maxWorkers=50%",
56
- "test:ci": "jest --passWithNoTests --runInBand --coverage --reporters=default --reporters=jest-junit"
57
+ "test:watch": "vitest --passWithNoTests",
58
+ "test": "vitest --run --passWithNoTests",
59
+ "test:ci": "vitest --run --passWithNoTests --coverage --reporter=default --reporter=junit"
57
60
  },
58
61
  "dependencies": {
59
62
  "@aboutyou/backbone": "16.0.1",
@@ -69,21 +72,19 @@
69
72
  "@scayle/eslint-config-storefront": "3.2.6",
70
73
  "@scayle/prettier-config-storefront": "2.0.2",
71
74
  "@types/crypto-js": "4.2.1",
72
- "@types/jest": "29.5.11",
73
- "@types/node": "20.10.5",
75
+ "@types/node": "20.10.6",
74
76
  "@types/webpack-env": "1.18.4",
75
- "unbuild": "2.0.0",
77
+ "@vitest/coverage-v8": "1.1.1",
76
78
  "eslint": "8.56.0",
77
79
  "eslint-formatter-gitlab": "5.1.0",
78
- "jest": "29.7.0",
79
- "jest-junit": "16.0.0",
80
80
  "prettier": "3.0.0",
81
- "publint": "0.2.6",
81
+ "publint": "0.2.7",
82
82
  "rimraf": "5.0.5",
83
- "ts-jest": "29.1.1",
84
83
  "ts-node": "10.9.2",
85
84
  "typescript": "5.3.3",
86
- "unstorage": "1.10.1"
85
+ "unbuild": "2.0.0",
86
+ "unstorage": "1.10.1",
87
+ "vitest": "1.1.1"
87
88
  },
88
89
  "optionalDependencies": {
89
90
  "redis": "4"