@scayle/storefront-core 7.33.0 → 7.34.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @scayle/storefront-core
2
2
 
3
+ ## 7.34.0
4
+
5
+ ### Minor Changes
6
+
7
+ - We added support for Identity Provider (IDP), enhancing our authentication and identity management capabilities.
8
+
3
9
  ## 7.33.0
4
10
 
5
11
  ### 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
@@ -64,4 +65,9 @@ export declare class OAuthClient {
64
65
  * @param accessToken
65
66
  */
66
67
  revokeToken(accessToken: string): Promise<void>;
68
+ /**
69
+ * Revoke all tokens for a user
70
+ * @param userId
71
+ */
72
+ generateToken(code: string): Promise<Oauth>;
67
73
  }
@@ -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
+ };
@@ -69,4 +69,14 @@ export interface Oauth {
69
69
  access_token: string;
70
70
  refresh_token: string;
71
71
  }
72
+ /**
73
+ * This adds `string` so it's flexible enough to support
74
+ * multiple IDPs since we don't know what they are.
75
+ * More narrow type can be defined in the nuxt modules.
76
+ */
77
+ export interface IDPConfig {
78
+ enabled: boolean;
79
+ idpKeys: string[];
80
+ idpRedirectURL: string;
81
+ }
72
82
  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.0",
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
  },
@@ -52,8 +53,9 @@
52
53
  "lint": "eslint . --format gitlab",
53
54
  "lint:fix": "eslint . --fix",
54
55
  "package:lint": "publint",
55
- "test": "jest --passWithNoTests --maxWorkers=50%",
56
- "test:ci": "jest --passWithNoTests --runInBand --coverage --reporters=default --reporters=jest-junit"
56
+ "test:watch": "vitest --passWithNoTests",
57
+ "test": "vitest --run --passWithNoTests",
58
+ "test:ci": "vitest --run --passWithNoTests --coverage --reporter=default --reporter=junit"
57
59
  },
58
60
  "dependencies": {
59
61
  "@aboutyou/backbone": "16.0.1",
@@ -69,21 +71,19 @@
69
71
  "@scayle/eslint-config-storefront": "3.2.6",
70
72
  "@scayle/prettier-config-storefront": "2.0.2",
71
73
  "@types/crypto-js": "4.2.1",
72
- "@types/jest": "29.5.11",
73
74
  "@types/node": "20.10.5",
74
75
  "@types/webpack-env": "1.18.4",
75
- "unbuild": "2.0.0",
76
+ "@vitest/coverage-v8": "1.1.0",
76
77
  "eslint": "8.56.0",
77
78
  "eslint-formatter-gitlab": "5.1.0",
78
- "jest": "29.7.0",
79
- "jest-junit": "16.0.0",
80
79
  "prettier": "3.0.0",
81
80
  "publint": "0.2.6",
82
81
  "rimraf": "5.0.5",
83
- "ts-jest": "29.1.1",
84
82
  "ts-node": "10.9.2",
85
83
  "typescript": "5.3.3",
86
- "unstorage": "1.10.1"
84
+ "unbuild": "2.0.0",
85
+ "unstorage": "1.10.1",
86
+ "vitest": "1.1.0"
87
87
  },
88
88
  "optionalDependencies": {
89
89
  "redis": "4"