@sitecore-content-sdk/core 0.2.0-beta.2 → 0.2.0-beta.21

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.
Files changed (88) hide show
  1. package/content.d.ts +1 -0
  2. package/content.js +1 -0
  3. package/dist/cjs/client/graphql-edge-proxy.js +3 -3
  4. package/dist/cjs/client/sitecore-client.js +34 -17
  5. package/dist/cjs/config/define-config.js +6 -5
  6. package/dist/cjs/constants.js +12 -1
  7. package/dist/cjs/content/content-client.js +148 -0
  8. package/dist/cjs/content/index.js +13 -0
  9. package/dist/cjs/content/locales.js +32 -0
  10. package/dist/cjs/content/taxonomies.js +78 -0
  11. package/dist/cjs/content/utils.js +16 -0
  12. package/dist/cjs/debug.js +1 -0
  13. package/dist/cjs/editing/design-library.js +2 -1
  14. package/dist/cjs/editing/rest-component-layout-service.js +26 -45
  15. package/dist/cjs/index.js +3 -1
  16. package/dist/cjs/layout/content-styles.js +2 -1
  17. package/dist/cjs/layout/themes.js +2 -1
  18. package/dist/cjs/site/graphql-robots-service.js +3 -2
  19. package/dist/cjs/tools/auth/encryption.js +141 -0
  20. package/dist/cjs/tools/auth/fetcher.js +34 -0
  21. package/dist/cjs/tools/auth/flow.js +123 -0
  22. package/dist/cjs/tools/auth/index.js +27 -0
  23. package/dist/cjs/tools/auth/models.js +2 -0
  24. package/dist/cjs/tools/auth/renewal.js +130 -0
  25. package/dist/cjs/tools/auth/tenant-state.js +110 -0
  26. package/dist/cjs/tools/auth/tenant-store.js +250 -0
  27. package/dist/cjs/tools/index.js +26 -1
  28. package/dist/cjs/utils/normalize-url.js +5 -0
  29. package/dist/cjs/utils/utils.js +5 -3
  30. package/dist/esm/client/graphql-edge-proxy.js +1 -1
  31. package/dist/esm/client/sitecore-client.js +34 -17
  32. package/dist/esm/config/define-config.js +6 -5
  33. package/dist/esm/constants.js +11 -0
  34. package/dist/esm/content/content-client.js +141 -0
  35. package/dist/esm/content/index.js +4 -0
  36. package/dist/esm/content/locales.js +29 -0
  37. package/dist/esm/content/taxonomies.js +75 -0
  38. package/dist/esm/content/utils.js +13 -0
  39. package/dist/esm/debug.js +1 -0
  40. package/dist/esm/editing/design-library.js +2 -1
  41. package/dist/esm/editing/rest-component-layout-service.js +23 -45
  42. package/dist/esm/index.js +2 -0
  43. package/dist/esm/layout/content-styles.js +2 -1
  44. package/dist/esm/layout/themes.js +2 -1
  45. package/dist/esm/site/graphql-robots-service.js +3 -2
  46. package/dist/esm/tools/auth/encryption.js +101 -0
  47. package/dist/esm/tools/auth/fetcher.js +31 -0
  48. package/dist/esm/tools/auth/flow.js +118 -0
  49. package/dist/esm/tools/auth/index.js +5 -0
  50. package/dist/esm/tools/auth/models.js +1 -0
  51. package/dist/esm/tools/auth/renewal.js +124 -0
  52. package/dist/esm/tools/auth/tenant-state.js +73 -0
  53. package/dist/esm/tools/auth/tenant-store.js +213 -0
  54. package/dist/esm/tools/index.js +3 -0
  55. package/dist/esm/utils/normalize-url.js +1 -0
  56. package/dist/esm/utils/utils.js +5 -3
  57. package/package.json +19 -18
  58. package/types/client/index.d.ts +1 -1
  59. package/types/client/models.d.ts +17 -1
  60. package/types/client/sitecore-client.d.ts +50 -22
  61. package/types/config/index.d.ts +1 -1
  62. package/types/config/models.d.ts +12 -2
  63. package/types/constants.d.ts +10 -0
  64. package/types/content/content-client.d.ts +92 -0
  65. package/types/content/index.d.ts +4 -0
  66. package/types/content/locales.d.ts +38 -0
  67. package/types/content/taxonomies.d.ts +125 -0
  68. package/types/content/utils.d.ts +15 -0
  69. package/types/debug.d.ts +1 -0
  70. package/types/editing/rest-component-layout-service.d.ts +23 -58
  71. package/types/index.d.ts +2 -0
  72. package/types/native-fetcher.d.ts +0 -7
  73. package/types/site/graphql-robots-service.d.ts +3 -2
  74. package/types/tools/auth/encryption.d.ts +34 -0
  75. package/types/tools/auth/fetcher.d.ts +13 -0
  76. package/types/tools/auth/flow.d.ts +40 -0
  77. package/types/tools/auth/index.d.ts +5 -0
  78. package/types/tools/auth/models.d.ts +233 -0
  79. package/types/tools/auth/renewal.d.ts +36 -0
  80. package/types/tools/auth/tenant-state.d.ts +21 -0
  81. package/types/tools/auth/tenant-store.d.ts +63 -0
  82. package/types/tools/index.d.ts +3 -0
  83. package/types/utils/normalize-url.d.ts +1 -0
  84. package/dist/cjs/data-fetcher.js +0 -22
  85. package/dist/esm/data-fetcher.js +0 -17
  86. package/form.d.ts +0 -1
  87. package/form.js +0 -1
  88. package/types/data-fetcher.d.ts +0 -34
@@ -0,0 +1,29 @@
1
+ /**
2
+ * GraphQL query to retrieve a specific locale by its ID.
3
+ *
4
+ * Variables:
5
+ * - id: The ID of the locale to retrieve.
6
+ */
7
+ export const GET_LOCALE_QUERY = `
8
+ query GetLocaleById($id: ID!) {
9
+ locale(id: $id) {
10
+ system {
11
+ id
12
+ label
13
+ }
14
+ }
15
+ }
16
+ `;
17
+ /**
18
+ * GraphQL query to retrieve all available locales.
19
+ */
20
+ export const GET_LOCALES_QUERY = `
21
+ query GetAllLocales {
22
+ manyLocale {
23
+ system {
24
+ id
25
+ label
26
+ }
27
+ }
28
+ }
29
+ `;
@@ -0,0 +1,75 @@
1
+ // --- GraphQL queries ---
2
+ /**
3
+ * GraphQL query to retrieve all taxonomies with optional pagination for taxonomies only.
4
+ *
5
+ * Variables:
6
+ * - pageSize: The number of taxonomies to retrieve per page.
7
+ * - after: The cursor for fetching the next page of taxonomies.
8
+ */
9
+ export const GET_TAXONOMIES_QUERY = `
10
+ query GetAllTaxonomies(
11
+ $pageSize: Int
12
+ $after: String
13
+ ) {
14
+ manyTaxonomy(minimumPageSize: $pageSize, after: $after) {
15
+ cursor
16
+ hasMore
17
+ results {
18
+ terms {
19
+ cursor
20
+ hasMore
21
+ results {
22
+ id
23
+ name
24
+ label
25
+ }
26
+ }
27
+ system {
28
+ id
29
+ name
30
+ version
31
+ label
32
+ createdAt
33
+ createdBy
34
+ updatedAt
35
+ updatedBy
36
+ publishStatus
37
+ }
38
+ }
39
+ }
40
+ }
41
+ `;
42
+ /**
43
+ * GraphQL query to retrieve a specific taxonomy by its ID, with optional pagination for its terms.
44
+ *
45
+ * Variables:
46
+ * - id: The unique ID of the taxonomy to retrieve.
47
+ * - termsPageSize: The number of terms to retrieve per page.
48
+ * - termsAfter: The cursor for fetching the next page of terms.
49
+ */
50
+ export const GET_TAXONOMY_QUERY = `
51
+ query GetTaxonomyById($id: ID!, $termsPageSize: Int, $termsAfter: String) {
52
+ taxonomy(id: $id) {
53
+ terms(minimumPageSize: $termsPageSize, after: $termsAfter) {
54
+ cursor
55
+ hasMore
56
+ results {
57
+ id
58
+ name
59
+ label
60
+ }
61
+ }
62
+ system {
63
+ id
64
+ name
65
+ version
66
+ label
67
+ createdAt
68
+ createdBy
69
+ updatedAt
70
+ updatedBy
71
+ publishStatus
72
+ }
73
+ }
74
+ }
75
+ `;
@@ -0,0 +1,13 @@
1
+ import { normalizeUrl } from '../utils/normalize-url';
2
+ /**
3
+ * Get the Content graphql endpoint url
4
+ * @param {object} params Parameters
5
+ * @param {string} [params.url] Content base graphql endpoint url
6
+ * @param {string} params.tenant Tenant name
7
+ * @param {string} params.environment Environment name
8
+ * @param {boolean} params.preview Indicates if preview mode is enabled
9
+ * @returns {string} Content graphql endpoint url
10
+ */
11
+ export function getContentUrl({ url = 'https://edge-platform.sitecorecloud.io', tenant, environment, preview, }) {
12
+ return `${normalizeUrl(url)}/cs/api/v2/graphql/${tenant}/${environment}?preview=${preview}`;
13
+ }
package/dist/esm/debug.js CHANGED
@@ -22,6 +22,7 @@ export const enableDebug = (namespaces) => debug.enable(namespaces);
22
22
  */
23
23
  export default {
24
24
  common: debug(`${rootNamespace}:common`),
25
+ content: debug(`${rootNamespace}:content`),
25
26
  form: debug(`${rootNamespace}:form`),
26
27
  http: debug(`${rootNamespace}:http`),
27
28
  layout: debug(`${rootNamespace}:layout`),
@@ -1,4 +1,5 @@
1
1
  import { SITECORE_EDGE_URL_DEFAULT } from '../constants';
2
+ import { normalizeUrl } from '../utils/normalize-url';
2
3
  /**
3
4
  * Event to be sent when report status to design library
4
5
  */
@@ -103,5 +104,5 @@ export function getDesignLibraryStatusEvent(status, uid) {
103
104
  * @returns The full URL to the design library script.
104
105
  */
105
106
  export function getDesignLibraryScriptLink(sitecoreEdgeUrl = SITECORE_EDGE_URL_DEFAULT) {
106
- return `${sitecoreEdgeUrl}/v1/files/designlibrary/lib/rh-lib-script.js`;
107
+ return `${normalizeUrl(sitecoreEdgeUrl)}/v1/files/designlibrary/lib/rh-lib-script.js`;
107
108
  }
@@ -1,44 +1,24 @@
1
- import { debug, NativeDataFetcher } from '..';
2
- import { fetchData } from '../data-fetcher';
1
+ import { NativeDataFetcher } from '../native-fetcher';
2
+ import debug from '../debug';
3
+ import { SITECORE_EDGE_URL_DEFAULT } from '../constants';
4
+ import { resolveUrl } from '../utils';
3
5
  /**
4
- * REST service that enables Design Library functioality
5
- * Makes a request to /sitecore/api/layout/component in 'library' mode in Pages.
6
+ * REST service that enables design Library functionality
6
7
  * Returns layoutData for one single rendered component
7
8
  */
8
9
  export class RestComponentLayoutService {
9
10
  constructor(config) {
10
11
  this.config = config;
11
- this.getFetcher = (req, res) => {
12
- return this.config.dataFetcherResolver
13
- ? this.config.dataFetcherResolver(req, res)
14
- : this.getDefaultFetcher(req);
15
- };
16
- /**
17
- * Provides default @see NativeDataFetcher data fetcher
18
- * @param {IncomingMessage} [req] Request instance
19
- * @returns default fetcher
20
- */
21
- this.getDefaultFetcher = (req) => {
22
- var _a;
23
- const config = {
24
- debugger: debug.editing,
25
- };
26
- const nativeFetcher = new NativeDataFetcher(config);
27
- const headers = req && Object.assign(Object.assign({}, req.headers), (((_a = req.socket) === null || _a === void 0 ? void 0 : _a.remoteAddress) ? { 'X-Forwarded-For': req.socket.remoteAddress } : {}));
28
- const fetcher = (url, data) => {
29
- data = Object.assign(Object.assign({}, data), { headers: headers });
30
- return nativeFetcher.fetch(url, data);
31
- };
32
- return fetcher;
33
- };
34
12
  }
35
- fetchComponentData(params, req, res) {
36
- params.siteName = params.siteName || this.config.siteName;
37
- const querystringParams = this.getComponentFetchParams(params);
38
- debug.layout('fetching component with uid %s for %s %s %s', params.componentUid, params.itemId, params.language, params.siteName);
39
- const fetcher = this.getFetcher(req, res);
40
- const fetchUrl = this.resolveLayoutServiceUrl('component');
41
- return fetchData(fetchUrl, fetcher, querystringParams).catch((error) => {
13
+ fetchComponentData(params) {
14
+ const config = { debugger: debug.layout };
15
+ const fetcher = new NativeDataFetcher(config);
16
+ debug.layout('fetching component with uid %s for %s %s %s %s', params.componentUid, params.itemId, params.language, params.siteName, params.dataSourceId);
17
+ const fetchUrl = this.getFetchUrl(params);
18
+ return fetcher
19
+ .get(fetchUrl)
20
+ .then((response) => response.data)
21
+ .catch((error) => {
42
22
  var _a;
43
23
  if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 404) {
44
24
  return error.response.data;
@@ -46,19 +26,10 @@ export class RestComponentLayoutService {
46
26
  throw error;
47
27
  });
48
28
  }
49
- /**
50
- * Resolves layout service url
51
- * @param {string} apiType which layout service API to call ('render' or 'placeholder')
52
- * @returns the layout service url
53
- */
54
- resolveLayoutServiceUrl(apiType) {
55
- const { apiHost = '', configurationName = 'jss' } = this.config;
56
- return `${apiHost}/sitecore/api/layout/${apiType}/${configurationName}`;
57
- }
58
29
  getComponentFetchParams(params) {
59
30
  // exclude undefined params with this one simple trick
60
31
  return JSON.parse(JSON.stringify({
61
- sc_apikey: this.config.apiKey,
32
+ sitecoreContextId: this.config.contextId,
62
33
  item: params.itemId,
63
34
  uid: params.componentUid,
64
35
  dataSourceId: params.dataSourceId,
@@ -66,7 +37,14 @@ export class RestComponentLayoutService {
66
37
  version: params.version,
67
38
  sc_site: params.siteName,
68
39
  sc_lang: params.language || 'en',
69
- sc_mode: params.editMode,
70
40
  }));
71
41
  }
42
+ /**
43
+ * Get the fetch URL for the partial layout data endpoint
44
+ * @param {ComponentLayoutRequestParams} params - The parameters for the request
45
+ * @returns {string} The fetch URL for the component data
46
+ */
47
+ getFetchUrl(params) {
48
+ return resolveUrl(`${this.config.edgeUrl || SITECORE_EDGE_URL_DEFAULT}/layout/component`, this.getComponentFetchParams(params));
49
+ }
72
50
  }
package/dist/esm/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // NOTE: all imports are now named as to not make breaking changes
2
2
  // and to keep react-native working with cjs modules.
3
3
  import * as constants from './constants';
4
+ import * as form from './form';
4
5
  export { default as debug, enableDebug } from './debug';
5
6
  export { GraphQLRequestClient, } from './graphql-request-client';
6
7
  export { DefaultRetryStrategy } from './retries';
@@ -8,4 +9,5 @@ export { MemoryCacheClient } from './cache-client';
8
9
  export { ClientError } from 'graphql-request';
9
10
  export { NativeDataFetcher, } from './native-fetcher';
10
11
  export { constants };
12
+ export { form };
11
13
  export { defineConfig } from './config';
@@ -1,4 +1,5 @@
1
1
  import { SITECORE_EDGE_URL_DEFAULT } from '../constants';
2
+ import { normalizeUrl } from '../utils/normalize-url';
2
3
  /**
3
4
  * Regular expression to check if the content styles are used in the field value
4
5
  */
@@ -22,7 +23,7 @@ export const getContentStylesheetLink = (layoutData, sitecoreEdgeContextId, site
22
23
  rel: 'stylesheet',
23
24
  };
24
25
  };
25
- export const getContentStylesheetUrl = (sitecoreEdgeContextId, sitecoreEdgeUrl = SITECORE_EDGE_URL_DEFAULT) => `${sitecoreEdgeUrl}/v1/files/pages/styles/content-styles.css?sitecoreContextId=${sitecoreEdgeContextId}`;
26
+ export const getContentStylesheetUrl = (sitecoreEdgeContextId, sitecoreEdgeUrl = SITECORE_EDGE_URL_DEFAULT) => `${normalizeUrl(sitecoreEdgeUrl)}/v1/files/pages/styles/content-styles.css?sitecoreContextId=${sitecoreEdgeContextId}`;
26
27
  export const traversePlaceholder = (components, config) => {
27
28
  if (config.loadStyles)
28
29
  return;
@@ -1,5 +1,6 @@
1
1
  import { getFieldValue } from '.';
2
2
  import { SITECORE_EDGE_URL_DEFAULT } from '../constants';
3
+ import { normalizeUrl } from '../utils/normalize-url';
3
4
  /**
4
5
  * Pattern for library ids
5
6
  * @example -library--foo
@@ -23,7 +24,7 @@ export function getDesignLibraryStylesheetLinks(layoutData, sitecoreEdgeContextI
23
24
  }));
24
25
  }
25
26
  export const getStylesheetUrl = (id, sitecoreEdgeContextId, sitecoreEdgeUrl = SITECORE_EDGE_URL_DEFAULT) => {
26
- return `${sitecoreEdgeUrl}/v1/files/components/styles/${id}.css?sitecoreContextId=${sitecoreEdgeContextId}`;
27
+ return `${normalizeUrl(sitecoreEdgeUrl)}/v1/files/components/styles/${id}.css?sitecoreContextId=${sitecoreEdgeContextId}`;
27
28
  };
28
29
  /**
29
30
  * Traverse placeholder and components to add library ids
@@ -27,17 +27,18 @@ export class GraphQLRobotsService {
27
27
  }
28
28
  /**
29
29
  * Fetch a data of robots.txt from API
30
+ * @param {FetchOptions} fetchOptions - The fetch options to be used for the request.
30
31
  * @returns text of robots.txt
31
32
  * @throws {Error} if the siteName is empty.
32
33
  */
33
- async fetchRobots() {
34
+ async fetchRobots(fetchOptions) {
34
35
  const siteName = this.options.siteName;
35
36
  if (!siteName) {
36
37
  throw new Error(siteNameError);
37
38
  }
38
39
  const robotsResult = this.graphQLClient.request(this.query, {
39
40
  siteName,
40
- });
41
+ }, fetchOptions);
41
42
  try {
42
43
  return robotsResult.then((result) => {
43
44
  var _a, _b;
@@ -0,0 +1,101 @@
1
+ /* eslint-disable jsdoc/require-jsdoc */
2
+ import keytar from 'keytar';
3
+ import * as crypto from 'crypto';
4
+ import { deleteTenantAuthInfo } from './tenant-store';
5
+ import { clearActiveTenant } from './tenant-state';
6
+ const algorithm = 'aes-256-gcm';
7
+ const SERVICE_NAME = 'sitecore-tools-cli';
8
+ /**
9
+ * Encrypts plaintext using AES-256-GCM for a given tenant.
10
+ * @param {string} plaintext
11
+ * @param {string} tenantId
12
+ */
13
+ export let encryptData = _encryptData;
14
+ /**
15
+ * Decrypts encrypted payload using AES-256-GCM for a specific tenant.
16
+ * If key is corrupted or invalid, optionally clears both key and tenant data.
17
+ * @param {EncryptedPayload} payload
18
+ * @param {string} tenantId
19
+ * @param {string} cleanupOnFailure
20
+ */
21
+ export let decryptData = _decryptData;
22
+ /**
23
+ * Deletes the encryption key for a tenant (useful for cleanup).
24
+ * @param {string} tenantId
25
+ */
26
+ export let deleteKey = _deleteKey;
27
+ // mock setup for unit tests to make sinon happy and mock-able with esbuild/tsx
28
+ // https://sinonjs.org/how-to/typescript-swc/
29
+ // This, plus the `_` names make the exports writable for sinon
30
+ export const unitMocks = {
31
+ set encryptData(mockImplementation) {
32
+ encryptData = mockImplementation;
33
+ },
34
+ get encryptData() {
35
+ return _encryptData;
36
+ },
37
+ set decryptData(mockImplementation) {
38
+ decryptData = mockImplementation;
39
+ },
40
+ get decryptData() {
41
+ return _decryptData;
42
+ },
43
+ set deleteKey(mockImplementation) {
44
+ deleteKey = mockImplementation;
45
+ },
46
+ get deleteKey() {
47
+ return _deleteKey;
48
+ },
49
+ };
50
+ /**
51
+ * Generates or retrieves a 32-byte AES key for a specific tenant.
52
+ * @param {string} tenantId
53
+ */
54
+ export async function getKey(tenantId) {
55
+ const account = `encryptionKey-${tenantId}`;
56
+ const key = await keytar.getPassword(SERVICE_NAME, account);
57
+ if (!key) {
58
+ const keyBuffer = crypto.randomBytes(32);
59
+ await keytar.setPassword(SERVICE_NAME, account, keyBuffer.toString('base64'));
60
+ return keyBuffer;
61
+ }
62
+ return Buffer.from(key, 'base64');
63
+ }
64
+ async function _encryptData(plaintext, tenantId) {
65
+ const key = await getKey(tenantId);
66
+ const iv = crypto.randomBytes(12);
67
+ const cipher = crypto.createCipheriv(algorithm, key, iv);
68
+ let encrypted = cipher.update(plaintext, 'utf8', 'base64');
69
+ encrypted += cipher.final('base64');
70
+ const authTag = cipher.getAuthTag().toString('base64');
71
+ return {
72
+ iv: iv.toString('base64'),
73
+ authTag,
74
+ encryptedData: encrypted,
75
+ };
76
+ }
77
+ async function _decryptData(payload, tenantId, cleanupOnFailure = true) {
78
+ try {
79
+ const key = await getKey(tenantId);
80
+ const decipher = crypto.createDecipheriv(algorithm, key, Buffer.from(payload.iv, 'base64'));
81
+ decipher.setAuthTag(Buffer.from(payload.authTag, 'base64'));
82
+ let decrypted = decipher.update(payload.encryptedData, 'base64', 'utf8');
83
+ decrypted += decipher.final('utf8');
84
+ return decrypted;
85
+ }
86
+ catch (err) {
87
+ console.error(`\nFailed to decrypt data for tenant '${tenantId}':`, err);
88
+ if (cleanupOnFailure) {
89
+ console.warn(`\nCleaning up key and auth data for corrupted tenant '${tenantId}'...`);
90
+ await deleteTenantAuthInfo(tenantId);
91
+ await deleteKey(`encryptionKey-${tenantId}`);
92
+ clearActiveTenant();
93
+ console.warn(`\nCleanup completed for tenant '${tenantId}'.`);
94
+ return null;
95
+ }
96
+ throw err;
97
+ }
98
+ }
99
+ async function _deleteKey(tenantId) {
100
+ await keytar.deletePassword(SERVICE_NAME, `encryptionKey-${tenantId}`);
101
+ }
@@ -0,0 +1,31 @@
1
+ /* eslint-disable jsdoc/require-jsdoc */
2
+ export const unitMocks = {
3
+ set sendPostRequest(mockImplementation) {
4
+ sendPostRequest = mockImplementation;
5
+ },
6
+ get sendPostRequest() {
7
+ return _sendPostRequest;
8
+ },
9
+ };
10
+ /**
11
+ * Performs a POST request with application/x-www-form-urlencoded headers.
12
+ * @param {string} url - The endpoint to post to.
13
+ * @param { URLSearchParams} params - A URLSearchParams instance representing the body.
14
+ * @returns Parsed JSON response.
15
+ * @throws Error if response is not OK.
16
+ */
17
+ export let sendPostRequest = _sendPostRequest;
18
+ async function _sendPostRequest(url, params, throwOnError = true) {
19
+ const response = await fetch(url, {
20
+ method: 'POST',
21
+ headers: {
22
+ 'Content-Type': 'application/x-www-form-urlencoded',
23
+ },
24
+ body: params.toString(),
25
+ });
26
+ const data = await response.json();
27
+ if (throwOnError && !response.ok) {
28
+ throw new Error(data.error_description || data.error || 'Unknown error occurred');
29
+ }
30
+ return data;
31
+ }
@@ -0,0 +1,118 @@
1
+ import { decodeJwtPayload } from './tenant-store';
2
+ import { sendPostRequest } from './fetcher';
3
+ import { DEFAULT_SITECORE_AUTH_DOMAIN, DEFAULT_SITECORE_AUTH_AUDIENCE, DEFAULT_SITECORE_AUTH_BASE_URL, DEVICE_GRANT_TYPE, CLIENT_GRANT_TYPE, SCOPE, TIMEOUT, DEFAULT_INTERVAL, } from '../../constants';
4
+ /**
5
+ * Performs the OAuth 2.0 client credentials flow to obtain a JWT access token
6
+ * from the Sitecore Identity Provider using the provided client credentials.
7
+ * @param {TenantArgs} params - Parameters including clientId, clientSecret, organizationId, tenantId, audience, authority, and baseUrl.
8
+ * @returns A Promise that resolves to the access token response (including access token, token type, expiry, etc.)
9
+ * @throws Will log and exit the process if the request fails or returns a non-OK status
10
+ */
11
+ export let clientCredentialsFlow = _clientCredentialsFlow;
12
+ /**
13
+ * Initiates the OAuth 2.0 Device Authorization flow by requesting a device and user code.
14
+ * This flow is typically used by devices or CLI apps that cannot input credentials directly.
15
+ * @param {DeviceAuthRequest} params - Parameters including clientId, audience, authority, and baseUrl.
16
+ * @returns {Promise<DeviceAuthResponse>} A promise resolving to device authorization metadata needed for polling.
17
+ * @throws {Error} If the device authorization request fails or returns an error response.
18
+ */
19
+ export let startDeviceAuthFlow = _startDeviceAuthFlow;
20
+ /**
21
+ * Polls the OAuth 2.0 device token endpoint to retrieve the access token once the user has authorized the device.
22
+ * This is typically used to continue the device authorization process after a user enters a code on a browser.
23
+ * @param {DeviceTokenPollRequest} params - Parameters for polling including clientId, deviceCode, interval, and authority.
24
+ * @returns {Promise<any>} A promise resolving to the device token response including access token and refresh token.
25
+ * @throws {Error} If polling fails or exceeds the timeout period.
26
+ */
27
+ export let pollForDeviceToken = _pollForDeviceToken;
28
+ // mock setup for unit tests to make sinon happy and mock-able with esbuild/tsx
29
+ // https://sinonjs.org/how-to/typescript-swc/
30
+ // This, plus the `_` names make the exports writable for sinon
31
+ export const unitMocks = {
32
+ set clientCredentialsFlow(mockImplementation) {
33
+ clientCredentialsFlow = mockImplementation;
34
+ },
35
+ get clientCredentialsFlow() {
36
+ return _clientCredentialsFlow;
37
+ },
38
+ set startDeviceAuthFlow(mockImplementation) {
39
+ startDeviceAuthFlow = mockImplementation;
40
+ },
41
+ get startDeviceAuthFlow() {
42
+ return _startDeviceAuthFlow;
43
+ },
44
+ set pollForDeviceToken(mockImplementation) {
45
+ pollForDeviceToken = mockImplementation;
46
+ },
47
+ get pollForDeviceToken() {
48
+ return _pollForDeviceToken;
49
+ },
50
+ };
51
+ async function _clientCredentialsFlow({ clientId, clientSecret, organizationId, tenantId, audience = DEFAULT_SITECORE_AUTH_AUDIENCE, authority = DEFAULT_SITECORE_AUTH_DOMAIN, baseUrl = DEFAULT_SITECORE_AUTH_BASE_URL, }) {
52
+ const params = new URLSearchParams({
53
+ client_id: clientId,
54
+ client_secret: clientSecret !== null && clientSecret !== void 0 ? clientSecret : '',
55
+ organization_id: organizationId !== null && organizationId !== void 0 ? organizationId : '',
56
+ tenant_id: tenantId !== null && tenantId !== void 0 ? tenantId : '',
57
+ audience,
58
+ grant_type: CLIENT_GRANT_TYPE,
59
+ baseUrl: baseUrl !== null && baseUrl !== void 0 ? baseUrl : '',
60
+ });
61
+ const url = `${authority}/oauth/token`;
62
+ const data = await sendPostRequest(url, params);
63
+ const decodedPayload = decodeJwtPayload(data.access_token) || {};
64
+ if (!(decodedPayload === null || decodedPayload === void 0 ? void 0 : decodedPayload.tokenTenantId) || !decodedPayload.tokenOrgId) {
65
+ throw new Error('\n Token is missing required claims tenant_id or org_id.');
66
+ }
67
+ const { tokenTenantId, tokenOrgId, tokenTenantName } = decodedPayload;
68
+ if (tenantId && tenantId !== tokenTenantId) {
69
+ throw new Error('\n Mismatch: Provided tenant ID does not match claims tenant ID.');
70
+ }
71
+ if (organizationId && organizationId !== tokenOrgId) {
72
+ throw new Error('\n Mismatch: Provided organization ID does not match claims organization ID.');
73
+ }
74
+ return { data, tokenOrgId, tokenTenantId, tokenTenantName, accessToken: data.access_token };
75
+ }
76
+ export async function _startDeviceAuthFlow({ clientId, audience, authority, baseUrl, }) {
77
+ const params = new URLSearchParams({
78
+ client_id: clientId,
79
+ scope: SCOPE,
80
+ audience,
81
+ baseUrl,
82
+ });
83
+ const url = `${authority}/oauth/device/code`;
84
+ const responseBody = (await sendPostRequest(url, params));
85
+ return responseBody;
86
+ }
87
+ export async function _pollForDeviceToken({ clientId, device_code, interval = DEFAULT_INTERVAL, authority = DEFAULT_SITECORE_AUTH_DOMAIN, }) {
88
+ const startTime = Date.now();
89
+ while (Date.now() - startTime < TIMEOUT * 1000) {
90
+ const params = new URLSearchParams({
91
+ grant_type: DEVICE_GRANT_TYPE,
92
+ device_code,
93
+ client_id: clientId,
94
+ });
95
+ const url = `${authority}/oauth/token`;
96
+ const responseBody = await sendPostRequest(url, params, false);
97
+ if ('error' in responseBody) {
98
+ switch (responseBody.error) {
99
+ case 'authorization_pending':
100
+ console.log('\n ⌛ Waiting for user authorization...');
101
+ break;
102
+ case 'slow_down':
103
+ console.log('🐢 Slowing down polling interval...');
104
+ interval += 5;
105
+ break;
106
+ default:
107
+ throw new Error(responseBody.error_description ||
108
+ responseBody.error ||
109
+ 'Unknown error during device token polling.');
110
+ }
111
+ await new Promise((resolve) => setTimeout(resolve, interval * 1000));
112
+ }
113
+ else {
114
+ return responseBody;
115
+ }
116
+ }
117
+ throw new Error('⏳ Timeout: User did not complete authorization in time.');
118
+ }
@@ -0,0 +1,5 @@
1
+ export { clientCredentialsFlow, startDeviceAuthFlow, pollForDeviceToken } from './flow';
2
+ export { renewClientToken, validateAndRenewAuthIfExpired, validateAuthInfo, getRefreshAccessToken, } from './renewal';
3
+ export { getActiveTenant, setActiveTenant, clearActiveTenant } from './tenant-state';
4
+ export { writeTenantAuthInfo, readTenantAuthInfo, deleteTenantAuthInfo, readTenantInfo, getAllTenantsInfo, writeTenantInfo, } from './tenant-store';
5
+ export { encryptData, decryptData, deleteKey } from './encryption';
@@ -0,0 +1 @@
1
+ export {};