@sitecore-content-sdk/core 0.2.0-beta.14 → 0.2.0-beta.16

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,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_SITECORE_AUTH_BASE_URL = exports.DEFAULT_SITECORE_AUTH_AUDIENCE = exports.DEFAULT_SITECORE_AUTH_DOMAIN = exports.HIDDEN_RENDERING_NAME = exports.SITECORE_EDGE_URL_DEFAULT = exports.siteNameError = exports.SitecoreTemplateId = void 0;
3
+ exports.DEFAULT_SITECORE_AUTH_BASE_URL = exports.DEFAULT_SITECORE_AUTH_AUDIENCE = exports.DEFAULT_SITECORE_AUTH_DOMAIN = exports.CLAIMS = exports.HIDDEN_RENDERING_NAME = exports.SITECORE_EDGE_URL_DEFAULT = exports.siteNameError = exports.SitecoreTemplateId = void 0;
4
4
  var SitecoreTemplateId;
5
5
  (function (SitecoreTemplateId) {
6
6
  // /sitecore/templates/Foundation/JavaScript Services/App
@@ -11,6 +11,7 @@ var SitecoreTemplateId;
11
11
  exports.siteNameError = 'The siteName cannot be empty';
12
12
  exports.SITECORE_EDGE_URL_DEFAULT = 'https://edge-platform.sitecorecloud.io';
13
13
  exports.HIDDEN_RENDERING_NAME = 'Hidden Rendering';
14
+ exports.CLAIMS = 'https://auth.sitecorecloud.io/claims';
14
15
  exports.DEFAULT_SITECORE_AUTH_DOMAIN = 'https://auth.sitecorecloud.io';
15
16
  exports.DEFAULT_SITECORE_AUTH_AUDIENCE = 'https://api.sitecorecloud.io';
16
17
  exports.DEFAULT_SITECORE_AUTH_BASE_URL = 'https://edge-platform.sitecorecloud.io/cs/api';
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.unitMocks = exports.deleteKey = exports.decryptData = exports.encryptData = void 0;
40
+ exports.getKey = getKey;
41
+ /* eslint-disable jsdoc/require-jsdoc */
42
+ const keytar_1 = __importDefault(require("keytar"));
43
+ const crypto = __importStar(require("crypto"));
44
+ const tenant_store_1 = require("./tenant-store");
45
+ const tenant_state_1 = require("./tenant-state");
46
+ const algorithm = 'aes-256-gcm';
47
+ const SERVICE_NAME = 'sitecore-tools-cli';
48
+ /**
49
+ * Encrypts plaintext using AES-256-GCM for a given tenant.
50
+ * @param {string} plaintext
51
+ * @param {string} tenantId
52
+ */
53
+ exports.encryptData = _encryptData;
54
+ /**
55
+ * Decrypts encrypted payload using AES-256-GCM for a specific tenant.
56
+ * If key is corrupted or invalid, optionally clears both key and tenant data.
57
+ * @param {EncryptedPayload} payload
58
+ * @param {string} tenantId
59
+ * @param {string} cleanupOnFailure
60
+ */
61
+ exports.decryptData = _decryptData;
62
+ /**
63
+ * Deletes the encryption key for a tenant (useful for cleanup).
64
+ * @param {string} tenantId
65
+ */
66
+ exports.deleteKey = _deleteKey;
67
+ // mock setup for unit tests to make sinon happy and mock-able with esbuild/tsx
68
+ // https://sinonjs.org/how-to/typescript-swc/
69
+ // This, plus the `_` names make the exports writable for sinon
70
+ exports.unitMocks = {
71
+ set encryptData(mockImplementation) {
72
+ exports.encryptData = mockImplementation;
73
+ },
74
+ get encryptData() {
75
+ return _encryptData;
76
+ },
77
+ set decryptData(mockImplementation) {
78
+ exports.decryptData = mockImplementation;
79
+ },
80
+ get decryptData() {
81
+ return _decryptData;
82
+ },
83
+ set deleteKey(mockImplementation) {
84
+ exports.deleteKey = mockImplementation;
85
+ },
86
+ get deleteKey() {
87
+ return _deleteKey;
88
+ },
89
+ };
90
+ /**
91
+ * Generates or retrieves a 32-byte AES key for a specific tenant.
92
+ * @param {string} tenantId
93
+ */
94
+ async function getKey(tenantId) {
95
+ const account = `encryptionKey-${tenantId}`;
96
+ const key = await keytar_1.default.getPassword(SERVICE_NAME, account);
97
+ if (!key) {
98
+ const keyBuffer = crypto.randomBytes(32);
99
+ await keytar_1.default.setPassword(SERVICE_NAME, account, keyBuffer.toString('base64'));
100
+ return keyBuffer;
101
+ }
102
+ return Buffer.from(key, 'base64');
103
+ }
104
+ async function _encryptData(plaintext, tenantId) {
105
+ const key = await getKey(tenantId);
106
+ const iv = crypto.randomBytes(12);
107
+ const cipher = crypto.createCipheriv(algorithm, key, iv);
108
+ let encrypted = cipher.update(plaintext, 'utf8', 'base64');
109
+ encrypted += cipher.final('base64');
110
+ const authTag = cipher.getAuthTag().toString('base64');
111
+ return {
112
+ iv: iv.toString('base64'),
113
+ authTag,
114
+ encryptedData: encrypted,
115
+ };
116
+ }
117
+ async function _decryptData(payload, tenantId, cleanupOnFailure = true) {
118
+ try {
119
+ const key = await getKey(tenantId);
120
+ const decipher = crypto.createDecipheriv(algorithm, key, Buffer.from(payload.iv, 'base64'));
121
+ decipher.setAuthTag(Buffer.from(payload.authTag, 'base64'));
122
+ let decrypted = decipher.update(payload.encryptedData, 'base64', 'utf8');
123
+ decrypted += decipher.final('utf8');
124
+ return decrypted;
125
+ }
126
+ catch (err) {
127
+ console.error(`\nFailed to decrypt data for tenant '${tenantId}':`, err);
128
+ if (cleanupOnFailure) {
129
+ console.warn(`\nCleaning up key and auth data for corrupted tenant '${tenantId}'...`);
130
+ await (0, tenant_store_1.deleteTenantAuthInfo)(tenantId);
131
+ await (0, exports.deleteKey)(`encryptionKey-${tenantId}`);
132
+ (0, tenant_state_1.clearActiveTenant)();
133
+ console.warn(`\nCleanup completed for tenant '${tenantId}'.`);
134
+ return null;
135
+ }
136
+ throw err;
137
+ }
138
+ }
139
+ async function _deleteKey(tenantId) {
140
+ await keytar_1.default.deletePassword(SERVICE_NAME, `encryptionKey-${tenantId}`);
141
+ }
@@ -40,31 +40,25 @@ async function _clientCredentialsFlow({ clientId, clientSecret, organizationId,
40
40
  grant_type: GRANT_TYPE,
41
41
  baseUrl: baseUrl !== null && baseUrl !== void 0 ? baseUrl : '',
42
42
  });
43
- try {
44
- const response = await fetch(`${authority}/oauth/token`, {
45
- method: 'POST',
46
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
47
- body: params.toString(),
48
- });
49
- const data = await response.json();
50
- if (!response.ok) {
51
- throw new Error(data.error_description || data.error || 'Error during client credentials flow');
52
- }
53
- const decodedPayload = (0, tenant_store_1.decodeJwtPayload)(data.access_token) || {};
54
- if (!(decodedPayload === null || decodedPayload === void 0 ? void 0 : decodedPayload.tokenTenantId) || !decodedPayload.tokenOrgId) {
55
- throw new Error('\n Token is missing required claims tenant_id or org_id.');
56
- }
57
- const { tokenTenantId, tokenOrgId, tokenTenantName } = decodedPayload;
58
- if (tenantId && tenantId !== tokenTenantId) {
59
- throw new Error('\n Mismatch: Provided tenant ID does not match claims tenant ID.');
60
- }
61
- if (organizationId && organizationId !== tokenOrgId) {
62
- throw new Error('\n Mismatch: Provided organization ID does not match claims organization ID.');
63
- }
64
- return { data, tokenOrgId, tokenTenantId, tokenTenantName, accessToken: data.access_token };
43
+ const response = await fetch(`${authority}/oauth/token`, {
44
+ method: 'POST',
45
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
46
+ body: params.toString(),
47
+ });
48
+ const data = await response.json();
49
+ if (!response.ok) {
50
+ throw new Error(data.error_description || data.error || 'Error during client credentials flow');
51
+ }
52
+ const decodedPayload = (0, tenant_store_1.decodeJwtPayload)(data.access_token) || {};
53
+ if (!(decodedPayload === null || decodedPayload === void 0 ? void 0 : decodedPayload.tokenTenantId) || !decodedPayload.tokenOrgId) {
54
+ throw new Error('\n Token is missing required claims tenant_id or org_id.');
55
+ }
56
+ const { tokenTenantId, tokenOrgId, tokenTenantName } = decodedPayload;
57
+ if (tenantId && tenantId !== tokenTenantId) {
58
+ throw new Error('\n Mismatch: Provided tenant ID does not match claims tenant ID.');
65
59
  }
66
- catch (error) {
67
- console.error('\n Error during client credentials flow:', error instanceof Error ? error.message : error);
68
- throw error;
60
+ if (organizationId && organizationId !== tokenOrgId) {
61
+ throw new Error('\n Mismatch: Provided organization ID does not match claims organization ID.');
69
62
  }
63
+ return { data, tokenOrgId, tokenTenantId, tokenTenantName, accessToken: data.access_token };
70
64
  }
@@ -1,11 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.writeTenantInfo = exports.getAllTenantsInfo = exports.readTenantInfo = exports.deleteTenantAuthInfo = exports.readTenantAuthInfo = exports.writeTenantAuthInfo = exports.clearActiveTenant = exports.setActiveTenant = exports.getActiveTenant = exports.validateAuthInfo = exports.renewAuthIfExpired = exports.renewClientToken = exports.clientCredentialsFlow = void 0;
3
+ exports.deleteKey = exports.decryptData = exports.encryptData = exports.writeTenantInfo = exports.getAllTenantsInfo = exports.readTenantInfo = exports.deleteTenantAuthInfo = exports.readTenantAuthInfo = exports.writeTenantAuthInfo = exports.clearActiveTenant = exports.setActiveTenant = exports.getActiveTenant = exports.validateAuthInfo = exports.validateAndRenewAuthIfExpired = exports.renewClientToken = exports.clientCredentialsFlow = void 0;
4
4
  var flow_1 = require("./flow");
5
5
  Object.defineProperty(exports, "clientCredentialsFlow", { enumerable: true, get: function () { return flow_1.clientCredentialsFlow; } });
6
6
  var renewal_1 = require("./renewal");
7
7
  Object.defineProperty(exports, "renewClientToken", { enumerable: true, get: function () { return renewal_1.renewClientToken; } });
8
- Object.defineProperty(exports, "renewAuthIfExpired", { enumerable: true, get: function () { return renewal_1.renewAuthIfExpired; } });
8
+ Object.defineProperty(exports, "validateAndRenewAuthIfExpired", { enumerable: true, get: function () { return renewal_1.validateAndRenewAuthIfExpired; } });
9
9
  Object.defineProperty(exports, "validateAuthInfo", { enumerable: true, get: function () { return renewal_1.validateAuthInfo; } });
10
10
  var tenant_state_1 = require("./tenant-state");
11
11
  Object.defineProperty(exports, "getActiveTenant", { enumerable: true, get: function () { return tenant_state_1.getActiveTenant; } });
@@ -18,3 +18,7 @@ Object.defineProperty(exports, "deleteTenantAuthInfo", { enumerable: true, get:
18
18
  Object.defineProperty(exports, "readTenantInfo", { enumerable: true, get: function () { return tenant_store_1.readTenantInfo; } });
19
19
  Object.defineProperty(exports, "getAllTenantsInfo", { enumerable: true, get: function () { return tenant_store_1.getAllTenantsInfo; } });
20
20
  Object.defineProperty(exports, "writeTenantInfo", { enumerable: true, get: function () { return tenant_store_1.writeTenantInfo; } });
21
+ var encryption_1 = require("./encryption");
22
+ Object.defineProperty(exports, "encryptData", { enumerable: true, get: function () { return encryption_1.encryptData; } });
23
+ Object.defineProperty(exports, "decryptData", { enumerable: true, get: function () { return encryption_1.decryptData; } });
24
+ Object.defineProperty(exports, "deleteKey", { enumerable: true, get: function () { return encryption_1.deleteKey; } });
@@ -2,10 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateAuthInfo = validateAuthInfo;
4
4
  exports.renewClientToken = renewClientToken;
5
- exports.renewAuthIfExpired = renewAuthIfExpired;
5
+ exports.validateAndRenewAuthIfExpired = validateAndRenewAuthIfExpired;
6
6
  const tenant_state_1 = require("./tenant-state");
7
7
  const flow_1 = require("./flow");
8
8
  const tenant_store_1 = require("./tenant-store");
9
+ const encryption_1 = require("./encryption");
9
10
  /**
10
11
  * Validates whether a given auth config is still valid (i.e., not expired).
11
12
  * @param {TenantAuth} authInfo - The tenant auth configuration.
@@ -46,7 +47,7 @@ async function renewClientToken(authInfo, tenantInfo) {
46
47
  * Ensures a valid token exists, renews it if expired.
47
48
  * Returns tenant context if successful, otherwise null.
48
49
  */
49
- async function renewAuthIfExpired() {
50
+ async function validateAndRenewAuthIfExpired() {
50
51
  const tenantId = (0, tenant_state_1.getActiveTenant)();
51
52
  if (!tenantId)
52
53
  return null;
@@ -75,6 +76,7 @@ async function renewAuthIfExpired() {
75
76
  console.error(`\n Failed to renew token for tenant '${tenantId}'`);
76
77
  console.warn(`\n Cleaning up stale authentication data for tenant '${tenantId}'...`);
77
78
  await (0, tenant_store_1.deleteTenantAuthInfo)(tenantId);
79
+ await (0, encryption_1.deleteKey)(tenantId);
78
80
  (0, tenant_state_1.clearActiveTenant)();
79
81
  console.info('\n You will need to login again to re-authenticate.');
80
82
  process.exit(1);
@@ -39,7 +39,8 @@ exports.getTenantPath = getTenantPath;
39
39
  const fs = __importStar(require("fs"));
40
40
  const path = __importStar(require("path"));
41
41
  const os = __importStar(require("os"));
42
- const CLAIMS = 'https://auth.sitecorecloud.io/claims';
42
+ const encryption_1 = require("./encryption");
43
+ const constants_1 = require("./../../constants");
43
44
  const rootDir = path.join(os.homedir(), '.sitecore', 'sitecore-tools');
44
45
  /**
45
46
  * Decodes a JWT without verifying its signature.
@@ -139,7 +140,8 @@ async function _writeTenantAuthInfo(tenantId, authInfo) {
139
140
  try {
140
141
  const dir = getTenantPath(tenantId);
141
142
  fs.mkdirSync(dir, { recursive: true });
142
- fs.writeFileSync(path.join(dir, 'auth.json'), JSON.stringify(authInfo, null, 2));
143
+ const encrypted = await (0, encryption_1.encryptData)(JSON.stringify(authInfo), tenantId);
144
+ fs.writeFileSync(path.join(dir, 'auth.json'), JSON.stringify(encrypted));
143
145
  }
144
146
  catch (error) {
145
147
  console.error(`\n Failed to write auth.json for tenant '${tenantId}': ${error.message}`);
@@ -150,8 +152,13 @@ async function _readTenantAuthInfo(tenantId) {
150
152
  if (!fs.existsSync(filePath))
151
153
  return null;
152
154
  try {
153
- const raw = fs.readFileSync(filePath, 'utf-8');
154
- return JSON.parse(raw);
155
+ const encryptedPayloadRaw = fs.readFileSync(filePath, 'utf8');
156
+ const encryptedPayload = JSON.parse(encryptedPayloadRaw);
157
+ const decryptedData = await (0, encryption_1.decryptData)(encryptedPayload, tenantId, true);
158
+ if (decryptedData === null) {
159
+ return null;
160
+ }
161
+ return JSON.parse(decryptedData);
155
162
  }
156
163
  catch (error) {
157
164
  console.error(`\n Failed to read auth.json for tenant '${tenantId}': ${error.message}`);
@@ -231,9 +238,9 @@ function _decodeJwtPayload(token) {
231
238
  const payload = Buffer.from(base64Payload, 'base64').toString('utf-8');
232
239
  const decoded = JSON.parse(payload);
233
240
  return {
234
- tokenTenantId: decoded === null || decoded === void 0 ? void 0 : decoded[`${CLAIMS}/tenant_id`],
235
- tokenOrgId: decoded === null || decoded === void 0 ? void 0 : decoded[`${CLAIMS}/org_id`],
236
- tokenTenantName: decoded === null || decoded === void 0 ? void 0 : decoded[`${CLAIMS}/tenant_name`],
241
+ tokenTenantId: decoded === null || decoded === void 0 ? void 0 : decoded[`${constants_1.CLAIMS}/tenant_id`],
242
+ tokenOrgId: decoded === null || decoded === void 0 ? void 0 : decoded[`${constants_1.CLAIMS}/org_id`],
243
+ tokenTenantName: decoded === null || decoded === void 0 ? void 0 : decoded[`${constants_1.CLAIMS}/tenant_name`],
237
244
  };
238
245
  }
239
246
  catch (error) {
@@ -8,6 +8,7 @@ export var SitecoreTemplateId;
8
8
  export const siteNameError = 'The siteName cannot be empty';
9
9
  export const SITECORE_EDGE_URL_DEFAULT = 'https://edge-platform.sitecorecloud.io';
10
10
  export const HIDDEN_RENDERING_NAME = 'Hidden Rendering';
11
+ export const CLAIMS = 'https://auth.sitecorecloud.io/claims';
11
12
  export const DEFAULT_SITECORE_AUTH_DOMAIN = 'https://auth.sitecorecloud.io';
12
13
  export const DEFAULT_SITECORE_AUTH_AUDIENCE = 'https://api.sitecorecloud.io';
13
14
  export const DEFAULT_SITECORE_AUTH_BASE_URL = 'https://edge-platform.sitecorecloud.io/cs/api';
@@ -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
+ }
@@ -37,31 +37,25 @@ async function _clientCredentialsFlow({ clientId, clientSecret, organizationId,
37
37
  grant_type: GRANT_TYPE,
38
38
  baseUrl: baseUrl !== null && baseUrl !== void 0 ? baseUrl : '',
39
39
  });
40
- try {
41
- const response = await fetch(`${authority}/oauth/token`, {
42
- method: 'POST',
43
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
44
- body: params.toString(),
45
- });
46
- const data = await response.json();
47
- if (!response.ok) {
48
- throw new Error(data.error_description || data.error || 'Error during client credentials flow');
49
- }
50
- const decodedPayload = decodeJwtPayload(data.access_token) || {};
51
- if (!(decodedPayload === null || decodedPayload === void 0 ? void 0 : decodedPayload.tokenTenantId) || !decodedPayload.tokenOrgId) {
52
- throw new Error('\n Token is missing required claims tenant_id or org_id.');
53
- }
54
- const { tokenTenantId, tokenOrgId, tokenTenantName } = decodedPayload;
55
- if (tenantId && tenantId !== tokenTenantId) {
56
- throw new Error('\n Mismatch: Provided tenant ID does not match claims tenant ID.');
57
- }
58
- if (organizationId && organizationId !== tokenOrgId) {
59
- throw new Error('\n Mismatch: Provided organization ID does not match claims organization ID.');
60
- }
61
- return { data, tokenOrgId, tokenTenantId, tokenTenantName, accessToken: data.access_token };
40
+ const response = await fetch(`${authority}/oauth/token`, {
41
+ method: 'POST',
42
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
43
+ body: params.toString(),
44
+ });
45
+ const data = await response.json();
46
+ if (!response.ok) {
47
+ throw new Error(data.error_description || data.error || 'Error during client credentials flow');
48
+ }
49
+ const decodedPayload = decodeJwtPayload(data.access_token) || {};
50
+ if (!(decodedPayload === null || decodedPayload === void 0 ? void 0 : decodedPayload.tokenTenantId) || !decodedPayload.tokenOrgId) {
51
+ throw new Error('\n Token is missing required claims tenant_id or org_id.');
52
+ }
53
+ const { tokenTenantId, tokenOrgId, tokenTenantName } = decodedPayload;
54
+ if (tenantId && tenantId !== tokenTenantId) {
55
+ throw new Error('\n Mismatch: Provided tenant ID does not match claims tenant ID.');
62
56
  }
63
- catch (error) {
64
- console.error('\n Error during client credentials flow:', error instanceof Error ? error.message : error);
65
- throw error;
57
+ if (organizationId && organizationId !== tokenOrgId) {
58
+ throw new Error('\n Mismatch: Provided organization ID does not match claims organization ID.');
66
59
  }
60
+ return { data, tokenOrgId, tokenTenantId, tokenTenantName, accessToken: data.access_token };
67
61
  }
@@ -1,4 +1,5 @@
1
1
  export { clientCredentialsFlow } from './flow';
2
- export { renewClientToken, renewAuthIfExpired, validateAuthInfo } from './renewal';
2
+ export { renewClientToken, validateAndRenewAuthIfExpired, validateAuthInfo } from './renewal';
3
3
  export { getActiveTenant, setActiveTenant, clearActiveTenant } from './tenant-state';
4
4
  export { writeTenantAuthInfo, readTenantAuthInfo, deleteTenantAuthInfo, readTenantInfo, getAllTenantsInfo, writeTenantInfo, } from './tenant-store';
5
+ export { encryptData, decryptData, deleteKey } from './encryption';
@@ -1,6 +1,7 @@
1
1
  import { getActiveTenant, clearActiveTenant } from './tenant-state';
2
2
  import { clientCredentialsFlow } from './flow';
3
3
  import { writeTenantAuthInfo, readTenantAuthInfo, deleteTenantAuthInfo, readTenantInfo, } from './tenant-store';
4
+ import { deleteKey } from './encryption';
4
5
  /**
5
6
  * Validates whether a given auth config is still valid (i.e., not expired).
6
7
  * @param {TenantAuth} authInfo - The tenant auth configuration.
@@ -41,7 +42,7 @@ export async function renewClientToken(authInfo, tenantInfo) {
41
42
  * Ensures a valid token exists, renews it if expired.
42
43
  * Returns tenant context if successful, otherwise null.
43
44
  */
44
- export async function renewAuthIfExpired() {
45
+ export async function validateAndRenewAuthIfExpired() {
45
46
  const tenantId = getActiveTenant();
46
47
  if (!tenantId)
47
48
  return null;
@@ -70,6 +71,7 @@ export async function renewAuthIfExpired() {
70
71
  console.error(`\n Failed to renew token for tenant '${tenantId}'`);
71
72
  console.warn(`\n Cleaning up stale authentication data for tenant '${tenantId}'...`);
72
73
  await deleteTenantAuthInfo(tenantId);
74
+ await deleteKey(tenantId);
73
75
  clearActiveTenant();
74
76
  console.info('\n You will need to login again to re-authenticate.');
75
77
  process.exit(1);
@@ -2,7 +2,8 @@
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
  import * as os from 'os';
5
- const CLAIMS = 'https://auth.sitecorecloud.io/claims';
5
+ import { encryptData, decryptData } from './encryption';
6
+ import { CLAIMS } from './../../constants';
6
7
  const rootDir = path.join(os.homedir(), '.sitecore', 'sitecore-tools');
7
8
  /**
8
9
  * Decodes a JWT without verifying its signature.
@@ -102,7 +103,8 @@ async function _writeTenantAuthInfo(tenantId, authInfo) {
102
103
  try {
103
104
  const dir = getTenantPath(tenantId);
104
105
  fs.mkdirSync(dir, { recursive: true });
105
- fs.writeFileSync(path.join(dir, 'auth.json'), JSON.stringify(authInfo, null, 2));
106
+ const encrypted = await encryptData(JSON.stringify(authInfo), tenantId);
107
+ fs.writeFileSync(path.join(dir, 'auth.json'), JSON.stringify(encrypted));
106
108
  }
107
109
  catch (error) {
108
110
  console.error(`\n Failed to write auth.json for tenant '${tenantId}': ${error.message}`);
@@ -113,8 +115,13 @@ async function _readTenantAuthInfo(tenantId) {
113
115
  if (!fs.existsSync(filePath))
114
116
  return null;
115
117
  try {
116
- const raw = fs.readFileSync(filePath, 'utf-8');
117
- return JSON.parse(raw);
118
+ const encryptedPayloadRaw = fs.readFileSync(filePath, 'utf8');
119
+ const encryptedPayload = JSON.parse(encryptedPayloadRaw);
120
+ const decryptedData = await decryptData(encryptedPayload, tenantId, true);
121
+ if (decryptedData === null) {
122
+ return null;
123
+ }
124
+ return JSON.parse(decryptedData);
118
125
  }
119
126
  catch (error) {
120
127
  console.error(`\n Failed to read auth.json for tenant '${tenantId}': ${error.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sitecore-content-sdk/core",
3
- "version": "0.2.0-beta.14",
3
+ "version": "0.2.0-beta.16",
4
4
  "main": "dist/cjs/index.js",
5
5
  "module": "dist/esm/index.js",
6
6
  "sideEffects": false,
@@ -71,13 +71,14 @@
71
71
  "debug": "^4.4.0",
72
72
  "graphql": "^16.11.0",
73
73
  "graphql-request": "^6.1.0",
74
+ "keytar": "^7.9.0",
74
75
  "memory-cache": "^0.2.0",
75
76
  "sinon-chai": "^4.0.0",
76
77
  "url-parse": "^1.5.10"
77
78
  },
78
79
  "description": "",
79
80
  "types": "types/index.d.ts",
80
- "gitHead": "7a86b92b3c3fd27c57067509613d3a99d3edc74f",
81
+ "gitHead": "c3fdb664842dd1bf000cb6bca7c55cf6d33ff446",
81
82
  "files": [
82
83
  "dist",
83
84
  "types",
@@ -5,6 +5,7 @@ export declare enum SitecoreTemplateId {
5
5
  export declare const siteNameError = "The siteName cannot be empty";
6
6
  export declare const SITECORE_EDGE_URL_DEFAULT = "https://edge-platform.sitecorecloud.io";
7
7
  export declare const HIDDEN_RENDERING_NAME = "Hidden Rendering";
8
+ export declare const CLAIMS = "https://auth.sitecorecloud.io/claims";
8
9
  export declare const DEFAULT_SITECORE_AUTH_DOMAIN = "https://auth.sitecorecloud.io";
9
10
  export declare const DEFAULT_SITECORE_AUTH_AUDIENCE = "https://api.sitecorecloud.io";
10
11
  export declare const DEFAULT_SITECORE_AUTH_BASE_URL = "https://edge-platform.sitecorecloud.io/cs/api";
@@ -0,0 +1,34 @@
1
+ import { EncryptedPayload } from './models';
2
+ /**
3
+ * Encrypts plaintext using AES-256-GCM for a given tenant.
4
+ * @param {string} plaintext
5
+ * @param {string} tenantId
6
+ */
7
+ export declare let encryptData: typeof _encryptData;
8
+ /**
9
+ * Decrypts encrypted payload using AES-256-GCM for a specific tenant.
10
+ * If key is corrupted or invalid, optionally clears both key and tenant data.
11
+ * @param {EncryptedPayload} payload
12
+ * @param {string} tenantId
13
+ * @param {string} cleanupOnFailure
14
+ */
15
+ export declare let decryptData: typeof _decryptData;
16
+ /**
17
+ * Deletes the encryption key for a tenant (useful for cleanup).
18
+ * @param {string} tenantId
19
+ */
20
+ export declare let deleteKey: typeof _deleteKey;
21
+ export declare const unitMocks: {
22
+ encryptData: typeof _encryptData;
23
+ decryptData: typeof _decryptData;
24
+ deleteKey: typeof _deleteKey;
25
+ };
26
+ /**
27
+ * Generates or retrieves a 32-byte AES key for a specific tenant.
28
+ * @param {string} tenantId
29
+ */
30
+ export declare function getKey(tenantId: string): Promise<Buffer>;
31
+ declare function _encryptData(plaintext: string, tenantId: string): Promise<EncryptedPayload>;
32
+ declare function _decryptData(payload: EncryptedPayload, tenantId: string, cleanupOnFailure?: boolean): Promise<string | null>;
33
+ declare function _deleteKey(tenantId: string): Promise<void>;
34
+ export {};
@@ -1,4 +1,5 @@
1
1
  export { clientCredentialsFlow } from './flow';
2
- export { renewClientToken, renewAuthIfExpired, validateAuthInfo } from './renewal';
2
+ export { renewClientToken, validateAndRenewAuthIfExpired, validateAuthInfo } from './renewal';
3
3
  export { getActiveTenant, setActiveTenant, clearActiveTenant } from './tenant-state';
4
4
  export { writeTenantAuthInfo, readTenantAuthInfo, deleteTenantAuthInfo, readTenantInfo, getAllTenantsInfo, writeTenantInfo, } from './tenant-store';
5
+ export { encryptData, decryptData, deleteKey } from './encryption';
@@ -92,3 +92,14 @@ export interface TenantInfo {
92
92
  */
93
93
  baseUrl: string;
94
94
  }
95
+ export type EncryptedPayload = {
96
+ iv: string;
97
+ /**
98
+ * Authentication tag for integrity verification
99
+ */
100
+ authTag: string;
101
+ /**
102
+ * Base64-encoded encrypted data
103
+ */
104
+ encryptedData: string;
105
+ };
@@ -17,6 +17,6 @@ export declare function renewClientToken(authInfo: TenantAuth, tenantInfo: Tenan
17
17
  * Ensures a valid token exists, renews it if expired.
18
18
  * Returns tenant context if successful, otherwise null.
19
19
  */
20
- export declare function renewAuthIfExpired(): Promise<{
20
+ export declare function validateAndRenewAuthIfExpired(): Promise<{
21
21
  tenantId: string;
22
22
  } | null>;