underpost 2.8.878 → 2.8.882

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 (61) hide show
  1. package/.env.development +35 -3
  2. package/.env.production +40 -3
  3. package/.env.test +35 -3
  4. package/.github/workflows/release.cd.yml +3 -3
  5. package/README.md +20 -2
  6. package/bin/deploy.js +40 -0
  7. package/cli.md +3 -1
  8. package/conf.js +29 -3
  9. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  10. package/manifests/deployment/dd-test-development/deployment.yaml +6 -6
  11. package/package.json +1 -2
  12. package/src/api/document/document.controller.js +66 -0
  13. package/src/api/document/document.model.js +51 -0
  14. package/src/api/document/document.router.js +24 -0
  15. package/src/api/document/document.service.js +133 -0
  16. package/src/cli/deploy.js +1 -1
  17. package/src/cli/index.js +2 -0
  18. package/src/cli/repository.js +2 -0
  19. package/src/cli/run.js +27 -1
  20. package/src/client/Default.index.js +46 -1
  21. package/src/client/components/core/Account.js +8 -1
  22. package/src/client/components/core/AgGrid.js +18 -9
  23. package/src/client/components/core/Auth.js +258 -89
  24. package/src/client/components/core/BtnIcon.js +13 -3
  25. package/src/client/components/core/Content.js +2 -1
  26. package/src/client/components/core/CssCore.js +40 -27
  27. package/src/client/components/core/Docs.js +189 -88
  28. package/src/client/components/core/Input.js +34 -19
  29. package/src/client/components/core/LoadingAnimation.js +5 -10
  30. package/src/client/components/core/Modal.js +280 -123
  31. package/src/client/components/core/ObjectLayerEngine.js +470 -104
  32. package/src/client/components/core/ObjectLayerEngineModal.js +1 -0
  33. package/src/client/components/core/Panel.js +9 -2
  34. package/src/client/components/core/PanelForm.js +234 -76
  35. package/src/client/components/core/Router.js +15 -15
  36. package/src/client/components/core/ToolTip.js +83 -19
  37. package/src/client/components/core/Translate.js +1 -1
  38. package/src/client/components/core/VanillaJs.js +7 -3
  39. package/src/client/components/core/windowGetDimensions.js +202 -0
  40. package/src/client/components/default/MenuDefault.js +105 -41
  41. package/src/client/components/default/RoutesDefault.js +2 -0
  42. package/src/client/services/default/default.management.js +1 -0
  43. package/src/client/services/document/document.service.js +97 -0
  44. package/src/client/services/file/file.service.js +2 -0
  45. package/src/client/ssr/Render.js +1 -1
  46. package/src/client/ssr/head/DefaultScripts.js +2 -0
  47. package/src/client/ssr/head/Seo.js +1 -0
  48. package/src/index.js +1 -1
  49. package/src/mailer/EmailRender.js +1 -1
  50. package/src/server/auth.js +68 -17
  51. package/src/server/client-build.js +2 -3
  52. package/src/server/client-formatted.js +40 -12
  53. package/src/server/conf.js +5 -1
  54. package/src/server/crypto.js +195 -76
  55. package/src/server/object-layer.js +196 -0
  56. package/src/server/peer.js +47 -5
  57. package/src/server/process.js +85 -1
  58. package/src/server/runtime.js +23 -23
  59. package/src/server/ssr.js +52 -10
  60. package/src/server/valkey.js +89 -1
  61. package/test/crypto.test.js +117 -0
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Module for managing Valkey
3
+ * @module src/server/valkey.js
4
+ * @namespace Valkey
5
+ */
6
+
1
7
  import Valkey from 'iovalkey';
2
8
  import mongoose from 'mongoose';
3
9
  import { hashPassword } from './auth.js';
@@ -10,11 +16,35 @@ const ValkeyInstances = {};
10
16
  const DummyStores = {}; // in-memory Maps per instance
11
17
  const ValkeyStatus = {}; // 'connected' | 'dummy' | 'error' | undefined
12
18
 
13
- // Backward-compatible overall flag: true if any instance is connected
19
+ /**
20
+ * Checks if any Valkey instance is connected.
21
+ * This is a backward-compatible overall flag.
22
+ * @returns {boolean} True if any instance has a 'connected' status.
23
+ * @memberof Valkey
24
+ */
14
25
  const isValkeyEnable = () => Object.values(ValkeyStatus).some((s) => s === 'connected');
15
26
 
27
+ /**
28
+ * Generates a unique key for a Valkey instance based on its host and path.
29
+ * @param {object} [opts={ host: '', path: '' }] - The instance options.
30
+ * @param {string} [opts.host=''] - The host of the instance.
31
+ * @param {string} [opts.path=''] - The path of the instance.
32
+ * @returns {string} The instance key.
33
+ * @private
34
+ * @memberof Valkey
35
+ */
16
36
  const _instanceKey = (opts = { host: '', path: '' }) => `${opts.host || ''}${opts.path || ''}`;
17
37
 
38
+ /**
39
+ * Creates and manages a connection to a Valkey server for a given instance.
40
+ * It sets up a client, attaches event listeners for connection status, and implements a fallback to an in-memory dummy store if the connection fails.
41
+ * @param {object} [instance={ host: '', path: '' }] - The instance identifier.
42
+ * @param {string} [instance.host=''] - The host of the instance.
43
+ * @param {string} [instance.path=''] - The path of the instance.
44
+ * @param {object} [valkeyServerConnectionOptions={ host: '', path: '' }] - Connection options for the iovalkey client.
45
+ * @returns {Promise<Valkey|undefined>} A promise that resolves to the Valkey client instance, or undefined if creation fails.
46
+ * @memberof Valkey
47
+ */
18
48
  const createValkeyConnection = async (
19
49
  instance = { host: '', path: '' },
20
50
  valkeyServerConnectionOptions = { host: '', path: '' },
@@ -72,6 +102,14 @@ const createValkeyConnection = async (
72
102
  return ValkeyInstances[key];
73
103
  };
74
104
 
105
+ /**
106
+ * Factory function to create a Data Transfer Object (DTO) from a payload.
107
+ * It filters the payload to include only the keys specified in the `select` object.
108
+ * @param {object} payload - The source object.
109
+ * @param {object} select - An object where keys are field names and values are 1 to include them.
110
+ * @returns {object} A new object containing only the selected fields from the payload.
111
+ * @memberof Valkey
112
+ */
75
113
  const selectDtoFactory = (payload, select) => {
76
114
  const result = {};
77
115
  for (const key of Object.keys(select)) {
@@ -80,6 +118,12 @@ const selectDtoFactory = (payload, select) => {
80
118
  return result;
81
119
  };
82
120
 
121
+ /**
122
+ * Factory function to create a new Valkey client instance.
123
+ * @param {object} options - Connection options for the iovalkey client.
124
+ * @returns {Promise<Valkey>} A promise that resolves to a new Valkey client.
125
+ * @memberof Valkey
126
+ */
83
127
  const valkeyClientFactory = async (options) => {
84
128
  const valkey = new Valkey({
85
129
  port: options?.port ? options.port : undefined,
@@ -103,6 +147,15 @@ const valkeyClientFactory = async (options) => {
103
147
  return valkey;
104
148
  };
105
149
 
150
+ /**
151
+ * Retrieves an object from Valkey by key for a specific instance.
152
+ * If the Valkey client is not connected or an error occurs, it falls back to the dummy in-memory store.
153
+ * It automatically parses JSON strings.
154
+ * @param {object} [options={ host: '', path: '' }] - The instance identifier.
155
+ * @param {string} [key=''] - The key of the object to retrieve.
156
+ * @returns {Promise<object|string|null>} A promise that resolves to the retrieved object, string, or null if not found.
157
+ * @memberof Valkey
158
+ */
106
159
  const getValkeyObject = async (options = { host: '', path: '' }, key = '') => {
107
160
  const k = _instanceKey(options);
108
161
  const status = ValkeyStatus[k];
@@ -124,6 +177,16 @@ const getValkeyObject = async (options = { host: '', path: '' }, key = '') => {
124
177
  return DummyStores[k]?.get(key) ?? null;
125
178
  };
126
179
 
180
+ /**
181
+ * Sets an object or string in Valkey for a specific instance.
182
+ * If the Valkey client is not connected, it writes to the in-memory dummy store instead.
183
+ * Objects are automatically stringified.
184
+ * @param {object} [options={ host: '', path: '' }] - The instance identifier.
185
+ * @param {string} [key=''] - The key under which to store the payload.
186
+ * @param {object|string} [payload={}] - The data to store.
187
+ * @returns {Promise<string>} A promise that resolves to 'OK' on success.
188
+ * @memberof Valkey
189
+ */
127
190
  const setValkeyObject = async (options = { host: '', path: '' }, key = '', payload = {}) => {
128
191
  const k = _instanceKey(options);
129
192
  const isString = typeof payload === 'string';
@@ -141,6 +204,16 @@ const setValkeyObject = async (options = { host: '', path: '' }, key = '', paylo
141
204
  return 'OK';
142
205
  };
143
206
 
207
+ /**
208
+ * Updates an existing object in Valkey by merging it with a new payload.
209
+ * It retrieves the current object, merges it with the new payload, and sets the updated object back.
210
+ * It also updates the `updatedAt` timestamp.
211
+ * @param {object} [options={ host: '', path: '' }] - The instance identifier.
212
+ * @param {string} [key=''] - The key of the object to update.
213
+ * @param {object} [payload={}] - The new data to merge into the object.
214
+ * @returns {Promise<string>} A promise that resolves to the result of the set operation.
215
+ * @memberof Valkey
216
+ */
144
217
  const updateValkeyObject = async (options = { host: '', path: '' }, key = '', payload = {}) => {
145
218
  let base = await getValkeyObject(options, key);
146
219
  if (typeof base !== 'object' || base === null) base = {};
@@ -148,6 +221,17 @@ const updateValkeyObject = async (options = { host: '', path: '' }, key = '', pa
148
221
  return await setValkeyObject(options, key, { ...base, ...payload });
149
222
  };
150
223
 
224
+ /**
225
+ * Factory function to create a new object based on a model schema.
226
+ * It generates a new object with default properties like `_id`, `createdAt`, and `updatedAt`,
227
+ * and model-specific properties.
228
+ * @param {object} [options={ host: 'localhost', path: '', object: {} }] - Options for object creation.
229
+ * @param {string} [options.host='localhost'] - The host context for the object.
230
+ * @param {object} [options.object={}] - An initial object to extend.
231
+ * @param {string} [model=''] - The name of the model schema to use (e.g., 'user').
232
+ * @returns {Promise<object>} A promise that resolves to the newly created object.
233
+ * @memberof Valkey
234
+ */
151
235
  const valkeyObjectFactory = async (options = { host: 'localhost', path: '', object: {} }, model = '') => {
152
236
  const idoDate = new Date().toISOString();
153
237
  options.object = options.object || {};
@@ -181,6 +265,10 @@ const valkeyObjectFactory = async (options = { host: 'localhost', path: '', obje
181
265
  }
182
266
  };
183
267
 
268
+ /**
269
+ * A collection of Valkey-related API functions.
270
+ * @type {object}
271
+ */
184
272
  const ValkeyAPI = {
185
273
  valkeyClientFactory,
186
274
  selectDtoFactory,
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @module crypto.test
3
+ * @description Unit tests for SymmetricCrypto and AsymmetricCrypto classes
4
+ * in the crypto module.
5
+ * * Uses 'chai' for assertions.
6
+ */
7
+
8
+ // Import Chai's assertion library
9
+ import { expect } from 'chai';
10
+
11
+ // Import the cryptographic classes from the Canvas's refactored module
12
+ import { SymmetricCrypto, AsymmetricCrypto } from '../src/server/crypto.js';
13
+
14
+ // Define a common plaintext message for testing
15
+ const plaintext = 'This is a secret message for testing cryptographic operations.';
16
+
17
+ // --- Main Test Suite ---
18
+
19
+ describe('Crypto Module Tests', () => {
20
+ // --- SymmetricCrypto Tests (AES-256-CBC) ---
21
+ describe('SymmetricCrypto (AES-256-CBC)', () => {
22
+ /**
23
+ * Test case: Verify that key and IV are automatically generated.
24
+ */
25
+ it('should generate new 32-byte key and 16-byte IV if none are provided', () => {
26
+ const symm = new SymmetricCrypto();
27
+ // Key should be 32 bytes (64 hex characters) and IV 16 bytes (32 hex characters)
28
+ expect(symm.encryptionKeyHex).to.be.a('string').and.have.lengthOf(64);
29
+ expect(symm.ivHex).to.be.a('string').and.have.lengthOf(32);
30
+ });
31
+
32
+ /**
33
+ * Test case: Encrypt data and ensure successful decryption back to the original plaintext.
34
+ */
35
+ it('should encrypt and successfully decrypt data', () => {
36
+ const symm = new SymmetricCrypto();
37
+ const ciphertext = symm.encryptData(plaintext);
38
+
39
+ // Ciphertext should contain IV and the encrypted data, separated by a colon
40
+ expect(ciphertext).to.include(':');
41
+
42
+ const decryptedText = symm.decryptData(ciphertext);
43
+ expect(decryptedText).to.equal(plaintext);
44
+ });
45
+
46
+ /**
47
+ * Test case: Verify that decryption fails gracefully if the ciphertext is tampered with (e.g., corrupting the encrypted payload).
48
+ *
49
+ * FIX: We now reliably tamper with the encrypted hex string by removing the last character.
50
+ * This ensures the underlying crypto operation fails due to invalid hex length or incomplete data,
51
+ * guaranteeing the try/catch block in the implementation is hit, and the expected error is thrown.
52
+ */
53
+ it('should fail decryption gracefully for tampered ciphertext (invalid payload)', () => {
54
+ const symm = new SymmetricCrypto();
55
+ const ciphertext = symm.encryptData(plaintext);
56
+
57
+ const [ivHex, encryptedHex] = ciphertext.split(':');
58
+
59
+ // Tamper with the encrypted content by cutting off the last character.
60
+ const tamperedEncryptedHex = encryptedHex.substring(0, encryptedHex.length - 1);
61
+
62
+ const tamperedCiphertext = `${ivHex}:${tamperedEncryptedHex}`;
63
+
64
+ // Expect the internal error handling to throw the generic error message
65
+ expect(() => symm.decryptData(tamperedCiphertext)).to.throw(
66
+ Error,
67
+ 'Decryption failed. Check key, IV, or ciphertext integrity.',
68
+ );
69
+ });
70
+ });
71
+
72
+ // --- AsymmetricCrypto Tests (RSA 2048) ---
73
+ describe('AsymmetricCrypto (RSA 2048)', () => {
74
+ /**
75
+ * Test case: Verify that RSA key pair is automatically generated.
76
+ */
77
+ it('should generate a new RSA key pair if none are provided', () => {
78
+ const asymm = new AsymmetricCrypto();
79
+ // Public and Private keys should be PEM strings
80
+ expect(asymm.publicKey).to.be.a('string').and.include('BEGIN PUBLIC KEY');
81
+ expect(asymm.privateKey).to.be.a('string').and.include('BEGIN PRIVATE KEY');
82
+ });
83
+
84
+ /**
85
+ * Test case: Encrypt with public key and decrypt with the corresponding private key.
86
+ */
87
+ it('should encrypt data with public key and decrypt with private key', () => {
88
+ const asymm = new AsymmetricCrypto();
89
+ const ciphertext = asymm.encryptData(plaintext);
90
+
91
+ // Ciphertext is a hex string
92
+ expect(ciphertext).to.be.a('string');
93
+
94
+ const decryptedText = asymm.decryptData(ciphertext);
95
+ expect(decryptedText).to.equal(plaintext);
96
+ });
97
+
98
+ /**
99
+ * Test case: Verify that decryption fails gracefully when using a mismatched private key.
100
+ */
101
+ it('should fail decryption gracefully when using the wrong private key', () => {
102
+ // 1. Generate the key pair and encrypt the data
103
+ const asymm1 = new AsymmetricCrypto();
104
+ const ciphertext = asymm1.encryptData(plaintext);
105
+
106
+ // 2. Generate a completely different key pair (wrong key)
107
+ const asymm2 = new AsymmetricCrypto();
108
+
109
+ // 3. Try to decrypt ciphertext from asymm1 using the private key from asymm2
110
+ // The implementation will log the 'oaep decoding error' and re-throw the generic message.
111
+ expect(() => asymm2.decryptData(ciphertext)).to.throw(
112
+ Error,
113
+ 'Decryption failed. Check private key or ciphertext integrity.',
114
+ );
115
+ });
116
+ });
117
+ });