From 2d390f5665b0dc4e8691e034d9c4ef2b76f934d5 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Thu, 24 Apr 2025 14:43:36 -0400 Subject: [PATCH 1/5] Encrypt payload to the node-server and add header "X-ENCRYPTED" --- .../domain/encryption/EncryptionService.java | 2 + .../encryption/EncryptionServiceImpl.java | 17 +++++- .../plugin/client/DatasourcePluginClient.java | 52 ++++++++++++----- .../src/main/resources/application-debug.yaml | 7 ++- .../src/main/resources/application.yaml | 5 ++ .../node-service/src/controllers/plugins.ts | 31 +++++++--- server/node-service/src/server.ts | 10 ++++ server/node-service/src/utils/encryption.ts | 58 +++++++++++++++++++ 8 files changed, 158 insertions(+), 24 deletions(-) create mode 100644 server/node-service/src/utils/encryption.ts diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionService.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionService.java index 276f4059d4..fdd173ed77 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionService.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionService.java @@ -4,6 +4,8 @@ public interface EncryptionService { String encryptString(String plaintext); + String encryptStringForNodeServer(String plaintext); + String decryptString(String encryptedText); String encryptPassword(String plaintext); diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionServiceImpl.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionServiceImpl.java index 6524682b60..d1dafd8af7 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionServiceImpl.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionServiceImpl.java @@ -5,6 +5,7 @@ import org.lowcoder.sdk.config.CommonConfig; import org.lowcoder.sdk.config.CommonConfig.Encrypt; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.encrypt.Encryptors; import org.springframework.security.crypto.encrypt.TextEncryptor; @@ -14,13 +15,20 @@ public class EncryptionServiceImpl implements EncryptionService { private final TextEncryptor textEncryptor; + private final TextEncryptor textEncryptorForNodeServer; private final BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); @Autowired - public EncryptionServiceImpl(CommonConfig commonConfig) { + public EncryptionServiceImpl( + CommonConfig commonConfig, + @Value("${lowcoder.node-server.password}") String password, + @Value("${lowcoder.node-server.salt}") String salt + ) { Encrypt encrypt = commonConfig.getEncrypt(); String saltInHex = Hex.encodeHexString(encrypt.getSalt().getBytes()); this.textEncryptor = Encryptors.text(encrypt.getPassword(), saltInHex); + String saltInHexForNodeServer = Hex.encodeHexString(salt.getBytes()); + this.textEncryptorForNodeServer = Encryptors.text(password, saltInHexForNodeServer); } @Override @@ -30,6 +38,13 @@ public String encryptString(String plaintext) { } return textEncryptor.encrypt(plaintext); } + @Override + public String encryptStringForNodeServer(String plaintext) { + if (StringUtils.isEmpty(plaintext)) { + return plaintext; + } + return textEncryptorForNodeServer.encrypt(plaintext); + } @Override public String decryptString(String encryptedText) { diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java index f2aa878bb3..812978193f 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java @@ -5,6 +5,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; +import org.lowcoder.domain.encryption.EncryptionService; import org.lowcoder.domain.plugin.client.dto.DatasourcePluginDefinition; import org.lowcoder.domain.plugin.client.dto.GetPluginDynamicConfigRequestDTO; import org.lowcoder.infra.js.NodeServerClient; @@ -30,6 +31,8 @@ import static org.lowcoder.sdk.constants.GlobalContext.REQUEST; +import com.fasterxml.jackson.databind.ObjectMapper; + @Slf4j @RequiredArgsConstructor @Component @@ -46,12 +49,15 @@ public class DatasourcePluginClient implements NodeServerClient { private final CommonConfigHelper commonConfigHelper; private final NodeServerHelper nodeServerHelper; + private final EncryptionService encryptionService; private static final String PLUGINS_PATH = "plugins"; private static final String RUN_PLUGIN_QUERY = "runPluginQuery"; private static final String VALIDATE_PLUGIN_DATA_SOURCE_CONFIG = "validatePluginDataSourceConfig"; private static final String GET_PLUGIN_DYNAMIC_CONFIG = "getPluginDynamicConfig"; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + public Mono> getPluginDynamicConfigSafely(List getPluginDynamicConfigRequestDTOS) { return getPluginDynamicConfig(getPluginDynamicConfigRequestDTOS) .onErrorResume(throwable -> { @@ -119,21 +125,37 @@ public Flux getDatasourcePluginDefinitions() { @SuppressWarnings("unchecked") public Mono executeQuery(String pluginName, Object queryDsl, List> context, Object datasourceConfig) { return getAcceptLanguage() - .flatMap(language -> WEB_CLIENT - .post() - .uri(nodeServerHelper.createUri(RUN_PLUGIN_QUERY)) - .header(HttpHeaders.ACCEPT_LANGUAGE, language) - .bodyValue(Map.of("pluginName", pluginName, "dsl", queryDsl, "context", context, "dataSourceConfig", datasourceConfig)) - .exchangeToMono(response -> { - if (response.statusCode().is2xxSuccessful()) { - return response.bodyToMono(Map.class) - .map(map -> map.get("result")) - .map(QueryExecutionResult::success); - } - return response.bodyToMono(Map.class) - .map(map -> MapUtils.getString(map, "message")) - .map(QueryExecutionResult::errorWithMessage); - })); + .flatMap(language -> { + try { + Map body = Map.of( + "pluginName", pluginName, + "dsl", queryDsl, + "context", context, + "dataSourceConfig", datasourceConfig + ); + String json = OBJECT_MAPPER.writeValueAsString(body); + String encrypted = encryptionService.encryptStringForNodeServer(json); + return WEB_CLIENT + .post() + .uri(nodeServerHelper.createUri(RUN_PLUGIN_QUERY)) + .header(HttpHeaders.ACCEPT_LANGUAGE, language) + .header("X-Encrypted", "true") // Optional: custom header to indicate encryption + .bodyValue(encrypted) + .exchangeToMono(response -> { + if (response.statusCode().is2xxSuccessful()) { + return response.bodyToMono(Map.class) + .map(map -> map.get("result")) + .map(QueryExecutionResult::success); + } + return response.bodyToMono(Map.class) + .map(map -> MapUtils.getString(map, "message")) + .map(QueryExecutionResult::errorWithMessage); + }); + } catch (Exception e) { + log.error("Encryption error", e); + return Mono.error(new ServerException("Encryption error")); + } + }); } @SuppressWarnings("unchecked") diff --git a/server/api-service/lowcoder-server/src/main/resources/application-debug.yaml b/server/api-service/lowcoder-server/src/main/resources/application-debug.yaml index d52888ca7e..4c85ab8588 100644 --- a/server/api-service/lowcoder-server/src/main/resources/application-debug.yaml +++ b/server/api-service/lowcoder-server/src/main/resources/application-debug.yaml @@ -61,4 +61,9 @@ logging: org.lowcoder: debug default: - query-timeout: ${LOWCODER_DEFAULT_QUERY_TIMEOUT:10s} \ No newline at end of file + query-timeout: ${LOWCODER_DEFAULT_QUERY_TIMEOUT:10s} + +lowcoder: + node-server: + password: ${LOWCODER_NODE_SERVICE_SECRET:lowcoderpwd} + salt: ${LOWCODER_NODE_SERVICE_SECRET_SALT:lowcodersalt} \ No newline at end of file diff --git a/server/api-service/lowcoder-server/src/main/resources/application.yaml b/server/api-service/lowcoder-server/src/main/resources/application.yaml index e5058563cb..783391083d 100644 --- a/server/api-service/lowcoder-server/src/main/resources/application.yaml +++ b/server/api-service/lowcoder-server/src/main/resources/application.yaml @@ -130,3 +130,8 @@ management: enabled: true diskspace: enabled: false + +lowcoder: + node-server: + password: ${LOWCODER_NODE_SERVICE_SECRET:lowcoderpwd} + salt: ${LOWCODER_NODE_SERVICE_SECRET_SALT:lowcodersalt} \ No newline at end of file diff --git a/server/node-service/src/controllers/plugins.ts b/server/node-service/src/controllers/plugins.ts index e20a109b38..d763586ce2 100644 --- a/server/node-service/src/controllers/plugins.ts +++ b/server/node-service/src/controllers/plugins.ts @@ -3,6 +3,23 @@ import { Request, Response } from "express"; import _ from "lodash"; import { Config } from "lowcoder-sdk/dataSource"; import * as pluginServices from "../services/plugin"; +// Add import for decryption utility +import { decryptString } from "../utils/encryption"; // <-- implement this utility as needed + +async function getDecryptedBody(req: Request): Promise { + if (req.headers["x-encrypted"]) { + // Assume body is a raw encrypted string, decrypt and parse as JSON + const encrypted = typeof req.body === "string" ? req.body : req.body?.toString?.(); + if (!encrypted) throw badRequest("Missing encrypted body"); + const decrypted = await decryptString(encrypted); + try { + return JSON.parse(decrypted); + } catch (e) { + throw badRequest("Failed to parse decrypted body as JSON"); + } + } + return req.body; +} export async function listPlugins(req: Request, res: Response) { let ids = req.query["id"] || []; @@ -15,12 +32,10 @@ export async function listPlugins(req: Request, res: Response) { } export async function runPluginQuery(req: Request, res: Response) { - const { pluginName, dsl, context, dataSourceConfig } = req.body; + const body = await getDecryptedBody(req); + const { pluginName, dsl, context, dataSourceConfig } = body; const ctx = pluginServices.getPluginContext(req); - - // console.log("pluginName: ", pluginName, "dsl: ", dsl, "context: ", context, "dataSourceConfig: ", dataSourceConfig, "ctx: ", ctx); - const result = await pluginServices.runPluginQuery( pluginName, dsl, @@ -32,7 +47,8 @@ export async function runPluginQuery(req: Request, res: Response) { } export async function validatePluginDataSourceConfig(req: Request, res: Response) { - const { pluginName, dataSourceConfig } = req.body; + const body = await getDecryptedBody(req); + const { pluginName, dataSourceConfig } = body; const ctx = pluginServices.getPluginContext(req); const result = await pluginServices.validatePluginDataSourceConfig( pluginName, @@ -50,10 +66,11 @@ type GetDynamicDefReqBody = { export async function getDynamicDef(req: Request, res: Response) { const ctx = pluginServices.getPluginContext(req); - if (!Array.isArray(req.body)) { + const body = await getDecryptedBody(req); + if (!Array.isArray(body)) { throw badRequest("request body is not a valid array"); } - const fields = req.body as GetDynamicDefReqBody; + const fields = body as GetDynamicDefReqBody; const result: Config[] = []; for (const item of fields) { const def = await pluginServices.getDynamicConfigDef( diff --git a/server/node-service/src/server.ts b/server/node-service/src/server.ts index 124c8d1e5c..793161ef5a 100644 --- a/server/node-service/src/server.ts +++ b/server/node-service/src/server.ts @@ -9,6 +9,7 @@ import { collectDefaultMetrics } from "prom-client"; import apiRouter from "./routes/apiRouter"; import systemRouter from "./routes/systemRouter"; import cors, { CorsOptions } from "cors"; +import bodyParser from "body-parser"; collectDefaultMetrics(); const prefix = "/node-service"; @@ -32,6 +33,15 @@ router.use(morgan("dev")); /** Parse the request */ router.use(express.urlencoded({ extended: false })); +/** Custom middleware: use raw body for encrypted requests */ +router.use((req, res, next) => { + if (req.headers["x-encrypted"]) { + bodyParser.text({ type: "*/*" })(req, res, next); + } else { + bodyParser.json()(req, res, next); + } +}); + /** Takes care of JSON data */ router.use( express.json({ diff --git a/server/node-service/src/utils/encryption.ts b/server/node-service/src/utils/encryption.ts new file mode 100644 index 0000000000..38b10474b5 --- /dev/null +++ b/server/node-service/src/utils/encryption.ts @@ -0,0 +1,58 @@ +import { createDecipheriv, createHash } from "crypto"; +import { badRequest } from "../common/error"; + +// Spring's Encryptors.text uses AES-256-CBC with a key derived from password and salt (hex). +// The encrypted string format is: hex(salt) + encryptedBase64 +// See: https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/crypto/encrypt/Encryptors.html + +const ALGORITHM = "aes-256-cbc"; +const KEY_LENGTH = 32; // 256 bits +const IV_LENGTH = 16; // 128 bits + +// You must set these to match your Java config: +const PASSWORD = process.env.LOWCODER_NODE_SERVICE_SECRET || "lowcoderpwd"; +const SALT_HEX = process.env.LOWCODER_NODE_SERVICE_SECRET_SALT || "lowcodersalt"; + +/** + * Convert a string to its binary representation, then to a hex string. + */ +function stringToHexFromBinary(str: string): string { + // Convert string to binary (Buffer), then to hex string + return Buffer.from(str, "utf8").toString("hex"); +} + +/** + * Derive key from password and salt using SHA-256 (Spring's default). + */ +function deriveKey(password: string, saltHex: string): Buffer { + // Convert salt string to binary, then to hex string + const saltHexFromBinary = stringToHexFromBinary(saltHex); + const salt = Buffer.from(saltHexFromBinary, "hex"); + const hash = createHash("sha256"); + hash.update(password); + hash.update(salt); + return hash.digest(); +} + +/** + * Decrypt a string encrypted by Spring's Encryptors.text. + */ +export async function decryptString(encrypted: string): Promise { + try { + // Spring's format: hex(salt) + encryptedBase64 + // But if you know salt, encrypted is just Base64(IV + ciphertext) + const key = deriveKey(PASSWORD, SALT_HEX); + + // Spring's Encryptors.text prepends a random IV (16 bytes) to the ciphertext, all base64 encoded. + const encryptedBuf = Buffer.from(encrypted, "base64"); + const iv = encryptedBuf.slice(0, IV_LENGTH); + const ciphertext = encryptedBuf.slice(IV_LENGTH); + + const decipher = createDecipheriv(ALGORITHM, key, iv); + let decrypted = decipher.update(ciphertext, undefined, "utf8"); + decrypted += decipher.final("utf8"); + return decrypted; + } catch (e) { + throw badRequest("Failed to decrypt string"); + } +} \ No newline at end of file From cdd86d4a7aefb19df0aa5bb1706f5d8e81cfac33 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Fri, 25 Apr 2025 04:41:01 -0400 Subject: [PATCH 2/5] 1024 iteration for deriveKey method --- server/node-service/src/utils/encryption.ts | 34 ++++++--------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/server/node-service/src/utils/encryption.ts b/server/node-service/src/utils/encryption.ts index 38b10474b5..2240a6571f 100644 --- a/server/node-service/src/utils/encryption.ts +++ b/server/node-service/src/utils/encryption.ts @@ -1,37 +1,23 @@ -import { createDecipheriv, createHash } from "crypto"; +import { createDecipheriv, pbkdf2Sync } from "crypto"; import { badRequest } from "../common/error"; -// Spring's Encryptors.text uses AES-256-CBC with a key derived from password and salt (hex). -// The encrypted string format is: hex(salt) + encryptedBase64 -// See: https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/crypto/encrypt/Encryptors.html - +// Spring's Encryptors.text uses AES-256-CBC with PBKDF2 (HmacSHA1, 1024 iterations). const ALGORITHM = "aes-256-cbc"; const KEY_LENGTH = 32; // 256 bits const IV_LENGTH = 16; // 128 bits +const ITERATIONS = 1024; +const DIGEST = "sha1"; // You must set these to match your Java config: const PASSWORD = process.env.LOWCODER_NODE_SERVICE_SECRET || "lowcoderpwd"; const SALT_HEX = process.env.LOWCODER_NODE_SERVICE_SECRET_SALT || "lowcodersalt"; /** - * Convert a string to its binary representation, then to a hex string. - */ -function stringToHexFromBinary(str: string): string { - // Convert string to binary (Buffer), then to hex string - return Buffer.from(str, "utf8").toString("hex"); -} - -/** - * Derive key from password and salt using SHA-256 (Spring's default). + * Derive key from password and salt using PBKDF2WithHmacSHA1 (Spring's default). */ function deriveKey(password: string, saltHex: string): Buffer { - // Convert salt string to binary, then to hex string - const saltHexFromBinary = stringToHexFromBinary(saltHex); - const salt = Buffer.from(saltHexFromBinary, "hex"); - const hash = createHash("sha256"); - hash.update(password); - hash.update(salt); - return hash.digest(); + const salt = Buffer.from(saltHex, "utf8"); + return pbkdf2Sync(password, salt, ITERATIONS, KEY_LENGTH, DIGEST); } /** @@ -39,12 +25,10 @@ function deriveKey(password: string, saltHex: string): Buffer { */ export async function decryptString(encrypted: string): Promise { try { - // Spring's format: hex(salt) + encryptedBase64 - // But if you know salt, encrypted is just Base64(IV + ciphertext) + // Spring's format: hex(salt) + encryptedHex(IV + ciphertext) const key = deriveKey(PASSWORD, SALT_HEX); - // Spring's Encryptors.text prepends a random IV (16 bytes) to the ciphertext, all base64 encoded. - const encryptedBuf = Buffer.from(encrypted, "base64"); + const encryptedBuf = Buffer.from(encrypted, "hex"); const iv = encryptedBuf.slice(0, IV_LENGTH); const ciphertext = encryptedBuf.slice(IV_LENGTH); From 210f2926ae934941754fcdb2d4115ecb923243fa Mon Sep 17 00:00:00 2001 From: Thomasr Date: Fri, 25 Apr 2025 15:02:12 -0400 Subject: [PATCH 3/5] backward compatible encryption option --- .../encryption/EncryptionServiceImpl.java | 8 +++---- .../plugin/client/DatasourcePluginClient.java | 22 ++++++++++++++----- .../org/lowcoder/sdk/config/CommonConfig.java | 3 +++ .../src/main/resources/application-debug.yaml | 10 ++++----- .../src/main/resources/application.yaml | 10 ++++----- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionServiceImpl.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionServiceImpl.java index d1dafd8af7..72eeba4121 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionServiceImpl.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionServiceImpl.java @@ -20,15 +20,13 @@ public class EncryptionServiceImpl implements EncryptionService { @Autowired public EncryptionServiceImpl( - CommonConfig commonConfig, - @Value("${lowcoder.node-server.password}") String password, - @Value("${lowcoder.node-server.salt}") String salt + CommonConfig commonConfig ) { Encrypt encrypt = commonConfig.getEncrypt(); String saltInHex = Hex.encodeHexString(encrypt.getSalt().getBytes()); this.textEncryptor = Encryptors.text(encrypt.getPassword(), saltInHex); - String saltInHexForNodeServer = Hex.encodeHexString(salt.getBytes()); - this.textEncryptorForNodeServer = Encryptors.text(password, saltInHexForNodeServer); + String saltInHexForNodeServer = Hex.encodeHexString(commonConfig.getJsExecutor().getSalt().getBytes()); + this.textEncryptorForNodeServer = Encryptors.text(commonConfig.getJsExecutor().getPassword(), saltInHexForNodeServer); } @Override diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java index 812978193f..5f0da78c43 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java @@ -10,6 +10,7 @@ import org.lowcoder.domain.plugin.client.dto.GetPluginDynamicConfigRequestDTO; import org.lowcoder.infra.js.NodeServerClient; import org.lowcoder.infra.js.NodeServerHelper; +import org.lowcoder.sdk.config.CommonConfig; import org.lowcoder.sdk.config.CommonConfigHelper; import org.lowcoder.sdk.exception.ServerException; import org.lowcoder.sdk.models.DatasourceTestResult; @@ -48,6 +49,7 @@ public class DatasourcePluginClient implements NodeServerClient { .build(); private final CommonConfigHelper commonConfigHelper; + private final CommonConfig commonConfig; private final NodeServerHelper nodeServerHelper; private final EncryptionService encryptionService; @@ -134,13 +136,23 @@ public Mono executeQuery(String pluginName, Object queryDs "dataSourceConfig", datasourceConfig ); String json = OBJECT_MAPPER.writeValueAsString(body); - String encrypted = encryptionService.encryptStringForNodeServer(json); - return WEB_CLIENT + + boolean encryptionEnabled = commonConfig.getJsExecutor().isEncrypted(); + String payload; + WebClient.RequestBodySpec requestSpec = WEB_CLIENT .post() .uri(nodeServerHelper.createUri(RUN_PLUGIN_QUERY)) - .header(HttpHeaders.ACCEPT_LANGUAGE, language) - .header("X-Encrypted", "true") // Optional: custom header to indicate encryption - .bodyValue(encrypted) + .header(HttpHeaders.ACCEPT_LANGUAGE, language); + + if (encryptionEnabled) { + payload = encryptionService.encryptStringForNodeServer(json); + requestSpec = requestSpec.header("X-Encrypted", "true"); + } else { + payload = json; + } + + return requestSpec + .bodyValue(payload) .exchangeToMono(response -> { if (response.statusCode().is2xxSuccessful()) { return response.bodyToMono(Map.class) diff --git a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/config/CommonConfig.java b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/config/CommonConfig.java index b50d069354..697f42fcd6 100644 --- a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/config/CommonConfig.java +++ b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/config/CommonConfig.java @@ -147,6 +147,9 @@ public long getMaxAgeInSeconds() { @Data public static class JsExecutor { private String host; + private String password; + private String salt; + private boolean isEncrypted; } @Data diff --git a/server/api-service/lowcoder-server/src/main/resources/application-debug.yaml b/server/api-service/lowcoder-server/src/main/resources/application-debug.yaml index 4c85ab8588..050e8077b8 100644 --- a/server/api-service/lowcoder-server/src/main/resources/application-debug.yaml +++ b/server/api-service/lowcoder-server/src/main/resources/application-debug.yaml @@ -37,6 +37,9 @@ common: cookie-name: LOWCODER_DEBUG_TOKEN js-executor: host: "http://127.0.0.1:6060" + password: ${LOWCODER_NODE_SERVICE_SECRET:lowcoderpwd} + salt: ${LOWCODER_NODE_SERVICE_SECRET_SALT:lowcodersalt} + is-encrypted: ${LOWCODER_NODE_SERVICE_ENCRYPTED:false} workspace: mode: ${LOWCODER_WORKSPACE_MODE:SAAS} plugin-dirs: @@ -61,9 +64,4 @@ logging: org.lowcoder: debug default: - query-timeout: ${LOWCODER_DEFAULT_QUERY_TIMEOUT:10s} - -lowcoder: - node-server: - password: ${LOWCODER_NODE_SERVICE_SECRET:lowcoderpwd} - salt: ${LOWCODER_NODE_SERVICE_SECRET_SALT:lowcodersalt} \ No newline at end of file + query-timeout: ${LOWCODER_DEFAULT_QUERY_TIMEOUT:10s} \ No newline at end of file diff --git a/server/api-service/lowcoder-server/src/main/resources/application.yaml b/server/api-service/lowcoder-server/src/main/resources/application.yaml index 783391083d..00bce2fc18 100644 --- a/server/api-service/lowcoder-server/src/main/resources/application.yaml +++ b/server/api-service/lowcoder-server/src/main/resources/application.yaml @@ -74,6 +74,9 @@ common: corsAllowedDomainString: ${LOWCODER_CORS_DOMAINS:*} js-executor: host: ${LOWCODER_NODE_SERVICE_URL:http://127.0.0.1:6060} + password: ${LOWCODER_NODE_SERVICE_SECRET:lowcoderpwd} + salt: ${LOWCODER_NODE_SERVICE_SECRET_SALT:lowcodersalt} + is-encrypted: ${LOWCODER_NODE_SERVICE_ENCRYPTED:false} max-query-request-size: ${LOWCODER_MAX_REQUEST_SIZE:20m} max-query-response-size: ${LOWCODER_MAX_REQUEST_SIZE:20m} max-upload-size: ${LOWCODER_MAX_REQUEST_SIZE:20m} @@ -129,9 +132,4 @@ management: redis: enabled: true diskspace: - enabled: false - -lowcoder: - node-server: - password: ${LOWCODER_NODE_SERVICE_SECRET:lowcoderpwd} - salt: ${LOWCODER_NODE_SERVICE_SECRET_SALT:lowcodersalt} \ No newline at end of file + enabled: false \ No newline at end of file From 998e37314f0e0684f7a4779cf896d2ea57ff60de Mon Sep 17 00:00:00 2001 From: Thomasr Date: Mon, 28 Apr 2025 02:25:53 -0400 Subject: [PATCH 4/5] testcase for payload encryption --- .../encryption/EncryptionServiceImplTest.java | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 server/api-service/lowcoder-domain/src/test/java/org/lowcoder/domain/encryption/EncryptionServiceImplTest.java diff --git a/server/api-service/lowcoder-domain/src/test/java/org/lowcoder/domain/encryption/EncryptionServiceImplTest.java b/server/api-service/lowcoder-domain/src/test/java/org/lowcoder/domain/encryption/EncryptionServiceImplTest.java new file mode 100644 index 0000000000..41bd465c82 --- /dev/null +++ b/server/api-service/lowcoder-domain/src/test/java/org/lowcoder/domain/encryption/EncryptionServiceImplTest.java @@ -0,0 +1,80 @@ +package org.lowcoder.domain.encryption; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lowcoder.sdk.config.CommonConfig; +import org.lowcoder.sdk.config.CommonConfig.Encrypt; +import org.lowcoder.sdk.config.CommonConfig.JsExecutor; +import org.springframework.security.crypto.encrypt.Encryptors; +import org.springframework.security.crypto.encrypt.TextEncryptor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class EncryptionServiceImplTest { + + private EncryptionServiceImpl encryptionService; + private TextEncryptor nodeServerEncryptor; + private String nodePassword = "nodePassword"; + private String nodeSalt = "nodeSalt"; + + @BeforeEach + void setUp() { + // Mock CommonConfig and its nested classes + Encrypt encrypt = mock(Encrypt.class); + when(encrypt.getPassword()).thenReturn("testPassword"); + when(encrypt.getSalt()).thenReturn("testSalt"); + + JsExecutor jsExecutor = mock(JsExecutor.class); + when(jsExecutor.getPassword()).thenReturn(nodePassword); + when(jsExecutor.getSalt()).thenReturn(nodeSalt); + + CommonConfig commonConfig = mock(CommonConfig.class); + when(commonConfig.getEncrypt()).thenReturn(encrypt); + when(commonConfig.getJsExecutor()).thenReturn(jsExecutor); + + encryptionService = new EncryptionServiceImpl(commonConfig); + + // For direct comparison in test + String saltInHexForNodeServer = org.apache.commons.codec.binary.Hex.encodeHexString(nodeSalt.getBytes()); + nodeServerEncryptor = Encryptors.text(nodePassword, saltInHexForNodeServer); + } + + @Test + void testEncryptStringForNodeServer_NullInput() { + assertNull(encryptionService.encryptStringForNodeServer(null)); + } + + @Test + void testEncryptStringForNodeServer_EmptyInput() { + assertEquals("", encryptionService.encryptStringForNodeServer("")); + } + + @Test + void testEncryptStringForNodeServer_EncryptsAndDecryptsCorrectly() { + String plain = "node secret"; + String encrypted = encryptionService.encryptStringForNodeServer(plain); + assertNotNull(encrypted); + assertNotEquals(plain, encrypted); + + // Decrypt using the same encryptor to verify correctness + String decrypted = nodeServerEncryptor.decrypt(encrypted); + assertEquals(plain, decrypted); + } + + @Test + void testEncryptStringForNodeServer_DifferentInputsProduceDifferentOutputs() { + String encrypted1 = encryptionService.encryptStringForNodeServer("abc"); + String encrypted2 = encryptionService.encryptStringForNodeServer("def"); + assertNotEquals(encrypted1, encrypted2); + } + + @Test + void testEncryptStringForNodeServer_SameInputProducesDifferentOutputs() { + String input = "repeat"; + String encrypted1 = encryptionService.encryptStringForNodeServer(input); + String encrypted2 = encryptionService.encryptStringForNodeServer(input); + // Spring's Encryptors.text uses random IV, so outputs should differ + assertNotEquals(encrypted1, encrypted2); + } +} \ No newline at end of file From f43ed65c8e00dea377577453e4b0049a8ff23b30 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Thu, 22 May 2025 14:12:41 -0400 Subject: [PATCH 5/5] Delete env variable LOWCODER_NODE_SERVICE_ENCRYPTED --- .../domain/plugin/client/DatasourcePluginClient.java | 2 +- .../src/main/resources/application-debug.yaml | 5 ++--- .../lowcoder-server/src/main/resources/application.yaml | 5 ++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java index 5f0da78c43..cae767e6f5 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java @@ -137,7 +137,7 @@ public Mono executeQuery(String pluginName, Object queryDs ); String json = OBJECT_MAPPER.writeValueAsString(body); - boolean encryptionEnabled = commonConfig.getJsExecutor().isEncrypted(); + boolean encryptionEnabled = !(commonConfig.getJsExecutor().getPassword().isEmpty() || commonConfig.getJsExecutor().getSalt().isEmpty()); String payload; WebClient.RequestBodySpec requestSpec = WEB_CLIENT .post() diff --git a/server/api-service/lowcoder-server/src/main/resources/application-debug.yaml b/server/api-service/lowcoder-server/src/main/resources/application-debug.yaml index 050e8077b8..80b8ddfff4 100644 --- a/server/api-service/lowcoder-server/src/main/resources/application-debug.yaml +++ b/server/api-service/lowcoder-server/src/main/resources/application-debug.yaml @@ -37,9 +37,8 @@ common: cookie-name: LOWCODER_DEBUG_TOKEN js-executor: host: "http://127.0.0.1:6060" - password: ${LOWCODER_NODE_SERVICE_SECRET:lowcoderpwd} - salt: ${LOWCODER_NODE_SERVICE_SECRET_SALT:lowcodersalt} - is-encrypted: ${LOWCODER_NODE_SERVICE_ENCRYPTED:false} + password: ${LOWCODER_NODE_SERVICE_SECRET:} + salt: ${LOWCODER_NODE_SERVICE_SECRET_SALT:} workspace: mode: ${LOWCODER_WORKSPACE_MODE:SAAS} plugin-dirs: diff --git a/server/api-service/lowcoder-server/src/main/resources/application.yaml b/server/api-service/lowcoder-server/src/main/resources/application.yaml index 00bce2fc18..fc89e0e746 100644 --- a/server/api-service/lowcoder-server/src/main/resources/application.yaml +++ b/server/api-service/lowcoder-server/src/main/resources/application.yaml @@ -74,9 +74,8 @@ common: corsAllowedDomainString: ${LOWCODER_CORS_DOMAINS:*} js-executor: host: ${LOWCODER_NODE_SERVICE_URL:http://127.0.0.1:6060} - password: ${LOWCODER_NODE_SERVICE_SECRET:lowcoderpwd} - salt: ${LOWCODER_NODE_SERVICE_SECRET_SALT:lowcodersalt} - is-encrypted: ${LOWCODER_NODE_SERVICE_ENCRYPTED:false} + password: ${LOWCODER_NODE_SERVICE_SECRET:} + salt: ${LOWCODER_NODE_SERVICE_SECRET_SALT:} max-query-request-size: ${LOWCODER_MAX_REQUEST_SIZE:20m} max-query-response-size: ${LOWCODER_MAX_REQUEST_SIZE:20m} max-upload-size: ${LOWCODER_MAX_REQUEST_SIZE:20m} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy