Flowfile 0.5.3__py3-none-any.whl → 0.5.6__py3-none-any.whl

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 (153) hide show
  1. flowfile/__init__.py +16 -0
  2. flowfile/__main__.py +94 -1
  3. flowfile/web/static/assets/{AdminView-49392a9a.js → AdminView-c2c7942b.js} +1 -1
  4. flowfile/web/static/assets/{CloudConnectionView-f13f202b.js → CloudConnectionView-7a3042c6.js} +4 -4
  5. flowfile/web/static/assets/{CloudConnectionView-36bcd6df.css → CloudConnectionView-cf85f943.css} +17 -17
  6. flowfile/web/static/assets/{CloudStorageReader-0023d4a5.js → CloudStorageReader-709c4037.js} +8 -8
  7. flowfile/web/static/assets/{CloudStorageWriter-8e781e11.js → CloudStorageWriter-604c51a8.js} +8 -8
  8. flowfile/web/static/assets/ColumnActionInput-c44b7aee.css +159 -0
  9. flowfile/web/static/assets/ColumnActionInput-d63d6746.js +330 -0
  10. flowfile/web/static/assets/{ColumnSelector-8ad68ea9.js → ColumnSelector-0c8cd1cd.js} +1 -1
  11. flowfile/web/static/assets/ContextMenu-366bf1b4.js +9 -0
  12. flowfile/web/static/assets/ContextMenu-85cf5b44.js +9 -0
  13. flowfile/web/static/assets/ContextMenu-9d28ae6d.js +9 -0
  14. flowfile/web/static/assets/ContextMenu.vue_vue_type_script_setup_true_lang-774c517c.js +59 -0
  15. flowfile/web/static/assets/{CrossJoin-03df6938.js → CrossJoin-38e5b99a.js} +9 -9
  16. flowfile/web/static/assets/{CustomNode-8479239b.js → CustomNode-76e8f3f5.js} +27 -20
  17. flowfile/web/static/assets/CustomNode-edb9b939.css +42 -0
  18. flowfile/web/static/assets/{DatabaseConnectionSettings-869e3efd.js → DatabaseConnectionSettings-38155669.js} +4 -4
  19. flowfile/web/static/assets/{DatabaseConnectionSettings-e91df89a.css → DatabaseConnectionSettings-c20a1e16.css} +22 -20
  20. flowfile/web/static/assets/{DatabaseReader-c58b9552.js → DatabaseReader-2e549c8f.js} +13 -13
  21. flowfile/web/static/assets/{DatabaseReader-36898a00.css → DatabaseReader-5bf8c75b.css} +39 -44
  22. flowfile/web/static/assets/{DatabaseView-d26a9140.js → DatabaseView-dc877c29.js} +2 -2
  23. flowfile/web/static/assets/{DatabaseWriter-217a99f1.css → DatabaseWriter-bdcf2c8b.css} +27 -25
  24. flowfile/web/static/assets/{DatabaseWriter-4d05ddc7.js → DatabaseWriter-ffb91864.js} +12 -12
  25. flowfile/web/static/assets/{DesignerView-a6d0ee84.css → DesignerView-71d4e9a1.css} +429 -376
  26. flowfile/web/static/assets/{DesignerView-e6f5c0e8.js → DesignerView-a4466dab.js} +338 -183
  27. flowfile/web/static/assets/{DocumentationView-2e78ef1b.js → DocumentationView-979afc84.js} +3 -3
  28. flowfile/web/static/assets/{DocumentationView-fd46c656.css → DocumentationView-9ea6e871.css} +9 -9
  29. flowfile/web/static/assets/{ExploreData-7b54caca.js → ExploreData-e4b92aaf.js} +7 -7
  30. flowfile/web/static/assets/{ExternalSource-47ab05a3.css → ExternalSource-7ac7373f.css} +17 -17
  31. flowfile/web/static/assets/{ExternalSource-3fa399b2.js → ExternalSource-d08e7227.js} +9 -9
  32. flowfile/web/static/assets/{Filter-8cbbdbf3.js → Filter-7add806d.js} +9 -9
  33. flowfile/web/static/assets/{Formula-aac42b1e.js → Formula-36ab24d2.js} +9 -9
  34. flowfile/web/static/assets/{FuzzyMatch-cd9bbfca.js → FuzzyMatch-cc01bb04.js} +10 -10
  35. flowfile/web/static/assets/{GraphSolver-c24dec17.css → GraphSolver-4b4d7db9.css} +4 -4
  36. flowfile/web/static/assets/{GraphSolver-c7e6780e.js → GraphSolver-4fb98f3b.js} +11 -11
  37. flowfile/web/static/assets/GroupBy-5792782d.css +9 -0
  38. flowfile/web/static/assets/{GroupBy-93c5d22b.js → GroupBy-b3c8f429.js} +9 -9
  39. flowfile/web/static/assets/{Join-a19b2de2.js → Join-096b7b26.js} +10 -10
  40. flowfile/web/static/assets/{LoginView-0df4ed0a.js → LoginView-c33a246a.js} +1 -1
  41. flowfile/web/static/assets/{ManualInput-3702e677.css → ManualInput-39111f19.css} +48 -48
  42. flowfile/web/static/assets/{ManualInput-8d3374b2.js → ManualInput-7307e9b1.js} +55 -13
  43. flowfile/web/static/assets/{MultiSelect-ad1b6243.js → MultiSelect-14822c48.js} +2 -2
  44. flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-e278950d.js → MultiSelect.vue_vue_type_script_setup_true_lang-90c4d340.js} +1 -1
  45. flowfile/web/static/assets/{NodeDesigner-40b647c9.js → NodeDesigner-5036c392.js} +171 -69
  46. flowfile/web/static/assets/{NodeDesigner-5f53be3f.css → NodeDesigner-94cd4dd3.css} +190 -190
  47. flowfile/web/static/assets/{NumericInput-7100234c.js → NumericInput-15cf3b72.js} +2 -2
  48. flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-5130219f.js → NumericInput.vue_vue_type_script_setup_true_lang-91e679d7.js} +1 -1
  49. flowfile/web/static/assets/{Output-f5efd2aa.js → Output-1f8ed42c.js} +13 -12
  50. flowfile/web/static/assets/{Output-35e97000.css → Output-692dd25d.css} +10 -10
  51. flowfile/web/static/assets/{Pivot-d981d23c.js → Pivot-0e153f4e.js} +10 -10
  52. flowfile/web/static/assets/{PivotValidation-63de1f73.js → PivotValidation-5a4f7c79.js} +1 -1
  53. flowfile/web/static/assets/{PivotValidation-39386e95.js → PivotValidation-81ec2a33.js} +1 -1
  54. flowfile/web/static/assets/{PolarsCode-f9d69217.js → PolarsCode-a39f15ac.js} +7 -7
  55. flowfile/web/static/assets/PopOver-ddcfe4f6.js +138 -0
  56. flowfile/web/static/assets/{Read-aec2e377.js → Read-39b63932.js} +15 -14
  57. flowfile/web/static/assets/{Read-36e7bd51.css → Read-90f366bc.css} +13 -13
  58. flowfile/web/static/assets/{RecordCount-78ed6845.js → RecordCount-e9048ccd.js} +6 -6
  59. flowfile/web/static/assets/{RecordId-2156e890.js → RecordId-ad02521d.js} +9 -9
  60. flowfile/web/static/assets/{SQLQueryComponent-48c72f5b.js → SQLQueryComponent-2eeecf0b.js} +3 -3
  61. flowfile/web/static/assets/SQLQueryComponent-edb90b98.css +29 -0
  62. flowfile/web/static/assets/{Sample-1352ca74.js → Sample-9a68c23d.js} +6 -6
  63. flowfile/web/static/assets/{SecretSelector-22b5ff89.js → SecretSelector-2429f35a.js} +2 -2
  64. flowfile/web/static/assets/{SecretsView-17df66ee.js → SecretsView-c6afc915.js} +2 -2
  65. flowfile/web/static/assets/{Select-0aee4c54.js → Select-fcd002b6.js} +9 -9
  66. flowfile/web/static/assets/{SettingsSection-cd341bb6.js → SettingsSection-5ce15962.js} +1 -1
  67. flowfile/web/static/assets/{SettingsSection-0784e157.js → SettingsSection-c6b1362c.js} +1 -1
  68. flowfile/web/static/assets/{SettingsSection-f2002a6d.js → SettingsSection-cebb91d5.js} +1 -1
  69. flowfile/web/static/assets/SetupView-2d12e01f.js +160 -0
  70. flowfile/web/static/assets/SetupView-ec26f76a.css +230 -0
  71. flowfile/web/static/assets/{SingleSelect-460cc0ea.js → SingleSelect-b67de4eb.js} +2 -2
  72. flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-30741bb2.js → SingleSelect.vue_vue_type_script_setup_true_lang-eedb70eb.js} +1 -1
  73. flowfile/web/static/assets/{SliderInput-5d926864.js → SliderInput-fd8134ac.js} +1 -1
  74. flowfile/web/static/assets/Sort-4abb7fae.css +9 -0
  75. flowfile/web/static/assets/{Sort-3cdc971b.js → Sort-c005a573.js} +9 -9
  76. flowfile/web/static/assets/{TextInput-a2d0bfbd.js → TextInput-1bb31dab.js} +2 -2
  77. flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-abad1ca2.js → TextInput.vue_vue_type_script_setup_true_lang-a51fe730.js} +1 -1
  78. flowfile/web/static/assets/{TextToRows-918945f7.js → TextToRows-4f363753.js} +9 -9
  79. flowfile/web/static/assets/{ToggleSwitch-f0ef5196.js → ToggleSwitch-ca0f2e5e.js} +2 -2
  80. flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-5605c793.js → ToggleSwitch.vue_vue_type_script_setup_true_lang-49aa41d8.js} +1 -1
  81. flowfile/web/static/assets/{UnavailableFields-54d2f518.css → UnavailableFields-394a1f78.css} +13 -13
  82. flowfile/web/static/assets/{UnavailableFields-bdad6144.js → UnavailableFields-f6147968.js} +4 -4
  83. flowfile/web/static/assets/{Union-e8ab8c86.js → Union-c65f17b7.js} +6 -6
  84. flowfile/web/static/assets/Unique-2b705521.css +3 -0
  85. flowfile/web/static/assets/{Unique-8cd4f976.js → Unique-a1d96fb2.js} +12 -12
  86. flowfile/web/static/assets/{Unpivot-710a2948.css → Unpivot-b6ad6427.css} +6 -6
  87. flowfile/web/static/assets/{Unpivot-8da14095.js → Unpivot-c2657ff3.js} +11 -11
  88. flowfile/web/static/assets/{UnpivotValidation-6f7d89ff.js → UnpivotValidation-28e29a3b.js} +1 -1
  89. flowfile/web/static/assets/{VueGraphicWalker-3fb312e1.js → VueGraphicWalker-2fc3ddd4.js} +1 -1
  90. flowfile/web/static/assets/{api-24483f0d.js → api-df48ec50.js} +1 -1
  91. flowfile/web/static/assets/{api-8b81fa73.js → api-ee542cf7.js} +1 -1
  92. flowfile/web/static/assets/{dropDown-3d8dc5fa.css → dropDown-1d6acbd9.css} +26 -26
  93. flowfile/web/static/assets/{dropDown-ac0fda9d.js → dropDown-7576a76a.js} +3 -3
  94. flowfile/web/static/assets/{fullEditor-5497a84a.js → fullEditor-7583bef5.js} +3 -3
  95. flowfile/web/static/assets/{fullEditor-a0be62b3.css → fullEditor-fe9f7e18.css} +3 -3
  96. flowfile/web/static/assets/{genericNodeSettings-99014e1d.js → genericNodeSettings-0155288b.js} +2 -3
  97. flowfile/web/static/assets/{index-3ba44389.js → index-057d770d.js} +2 -2
  98. flowfile/web/static/assets/{index-07dda503.js → index-aeec439d.js} +1 -1
  99. flowfile/web/static/assets/{index-fb6493ae.js → index-ca6799de.js} +2293 -196
  100. flowfile/web/static/assets/{index-e6289dd0.css → index-d60c9dd4.css} +560 -10
  101. flowfile/web/static/assets/nodeInput-d478b9ac.js +2 -0
  102. flowfile/web/static/assets/{outputCsv-8f8ba42d.js → outputCsv-c492b15e.js} +3 -3
  103. flowfile/web/static/assets/outputCsv-cc84e09f.css +2499 -0
  104. flowfile/web/static/assets/{outputExcel-393f4fef.js → outputExcel-13bfa10f.js} +1 -1
  105. flowfile/web/static/assets/{outputParquet-07c81f65.js → outputParquet-9be1523a.js} +1 -1
  106. flowfile/web/static/assets/{readCsv-07f6d9ad.js → readCsv-5a49a8c9.js} +1 -1
  107. flowfile/web/static/assets/{readExcel-ed69bc8f.js → readExcel-27c30ad8.js} +3 -3
  108. flowfile/web/static/assets/{readParquet-e3ed4528.js → readParquet-446bde68.js} +1 -1
  109. flowfile/web/static/assets/{secrets.api-002e7d7e.js → secrets.api-34431884.js} +1 -1
  110. flowfile/web/static/assets/{selectDynamic-80b92899.js → selectDynamic-5754a2b1.js} +2 -3
  111. flowfile/web/static/assets/{vue-codemirror.esm-0965f39f.js → vue-codemirror.esm-8f46fb36.js} +1 -1
  112. flowfile/web/static/assets/{vue-content-loader.es-c506ad97.js → vue-content-loader.es-808fe33a.js} +1 -1
  113. flowfile/web/static/index.html +2 -2
  114. {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/METADATA +2 -2
  115. {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/RECORD +139 -134
  116. flowfile_core/auth/secrets.py +56 -13
  117. flowfile_core/fileExplorer/funcs.py +26 -4
  118. flowfile_core/flowfile/code_generator/__init__.py +11 -0
  119. flowfile_core/flowfile/code_generator/code_generator.py +347 -2
  120. flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +13 -1
  121. flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +12 -0
  122. flowfile_core/flowfile/flow_graph.py +2 -0
  123. flowfile_core/flowfile/flow_node/flow_node.py +52 -28
  124. flowfile_core/flowfile/node_designer/__init__.py +4 -0
  125. flowfile_core/flowfile/node_designer/ui_components.py +144 -1
  126. flowfile_core/main.py +2 -4
  127. flowfile_core/routes/public.py +43 -1
  128. flowfile_core/schemas/cloud_storage_schemas.py +39 -15
  129. flowfile_core/secret_manager/secret_manager.py +107 -6
  130. flowfile_frame/__init__.py +11 -0
  131. flowfile_frame/database/__init__.py +36 -0
  132. flowfile_frame/database/connection_manager.py +205 -0
  133. flowfile_frame/database/frame_helpers.py +249 -0
  134. flowfile_worker/configs.py +31 -15
  135. flowfile_worker/secrets.py +105 -15
  136. flowfile_worker/spawner.py +10 -6
  137. flowfile/web/static/assets/ContextMenu-26d4dd27.css +0 -26
  138. flowfile/web/static/assets/ContextMenu-31ee57f0.js +0 -41
  139. flowfile/web/static/assets/ContextMenu-69a74055.js +0 -41
  140. flowfile/web/static/assets/ContextMenu-8e2051c6.js +0 -41
  141. flowfile/web/static/assets/ContextMenu-8ec1729e.css +0 -26
  142. flowfile/web/static/assets/ContextMenu-9b310c60.css +0 -26
  143. flowfile/web/static/assets/CustomNode-59e99a86.css +0 -32
  144. flowfile/web/static/assets/GroupBy-be7ac0bf.css +0 -51
  145. flowfile/web/static/assets/PopOver-b22f049e.js +0 -939
  146. flowfile/web/static/assets/SQLQueryComponent-1c2f26b4.css +0 -27
  147. flowfile/web/static/assets/Sort-8a871341.css +0 -51
  148. flowfile/web/static/assets/Unique-9fb2f567.css +0 -51
  149. flowfile/web/static/assets/nodeInput-0eb13f1a.js +0 -2
  150. flowfile/web/static/assets/outputCsv-b9a072af.css +0 -2499
  151. {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/WHEEL +0 -0
  152. {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/entry_points.txt +0 -0
  153. {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/licenses/LICENSE +0 -0
@@ -2,12 +2,15 @@
2
2
  Simplified secure storage module for FlowFile worker to read credentials and secrets.
3
3
  """
4
4
 
5
+ import base64
5
6
  import json
6
7
  import logging
7
8
  import os
8
9
  from pathlib import Path
9
10
 
10
11
  from cryptography.fernet import Fernet
12
+ from cryptography.hazmat.primitives import hashes
13
+ from cryptography.hazmat.primitives.kdf.hkdf import HKDF
11
14
  from pydantic import SecretStr
12
15
 
13
16
  from flowfile_worker.configs import TEST_MODE
@@ -15,6 +18,12 @@ from flowfile_worker.configs import TEST_MODE
15
18
  # Set up logging
16
19
  logger = logging.getLogger(__name__)
17
20
 
21
+ # Version identifier for key derivation scheme (must match flowfile_core)
22
+ KEY_DERIVATION_VERSION = b"flowfile-secrets-v1"
23
+
24
+ # Encrypted secret format: $ffsec$1${user_id}${fernet_token}
25
+ SECRET_FORMAT_PREFIX = "$ffsec$1$"
26
+
18
27
 
19
28
  class SecureStorage:
20
29
  """A secure local storage mechanism for reading secrets using Fernet encryption."""
@@ -69,27 +78,42 @@ def get_password(service_name, username):
69
78
  return _storage.get_password(service_name, username)
70
79
 
71
80
 
72
- def get_docker_secret_key():
81
+ def get_docker_secret_key() -> str | None:
73
82
  """
74
- Get the master key from Docker secret.
83
+ Get the master key from Docker secret or environment variable.
75
84
 
76
85
  Returns:
77
- str: The master key if successfully read from Docker secret.
86
+ str: The master key if successfully read, None if not configured.
78
87
 
79
88
  Raises:
80
- RuntimeError: If running in Docker but unable to access the secret.
89
+ RuntimeError: If the secret file exists but cannot be read, or key is invalid.
81
90
  """
91
+ # First, check for environment variable (allows runtime configuration)
92
+ env_key = os.environ.get("FLOWFILE_MASTER_KEY")
93
+ if env_key:
94
+ # Validate it's a proper Fernet key
95
+ try:
96
+ Fernet(env_key.encode())
97
+ return env_key
98
+ except Exception:
99
+ logger.error("FLOWFILE_MASTER_KEY environment variable is not a valid Fernet key")
100
+ raise RuntimeError("FLOWFILE_MASTER_KEY is not a valid Fernet key")
101
+
102
+ # Then, check for Docker secret file
82
103
  secret_path = "/run/secrets/flowfile_master_key"
83
104
  if os.path.exists(secret_path):
84
105
  try:
85
106
  with open(secret_path) as f:
86
- return f.read().strip()
107
+ key = f.read().strip()
108
+ # Validate the key
109
+ Fernet(key.encode())
110
+ return key
87
111
  except Exception as e:
88
- logger.error(f"Failed to read master key from Docker secret: {e}")
112
+ logger.error(f"Failed to read or validate master key from Docker secret: {e}")
89
113
  raise RuntimeError("Failed to read master key from Docker secret")
90
- else:
91
- logger.critical("Running in Docker but flowfile_master_key secret is not mounted!")
92
- raise RuntimeError("Docker secret 'flowfile_master_key' is not mounted")
114
+
115
+ # No key configured
116
+ return None
93
117
 
94
118
 
95
119
  def get_master_key() -> str:
@@ -97,13 +121,14 @@ def get_master_key() -> str:
97
121
  Get the master encryption key.
98
122
 
99
123
  If in TEST_MODE, returns a test key.
100
- If running in Docker, retrieves the key from Docker secrets.
124
+ If running in Docker, retrieves the key from Docker secrets or environment.
101
125
  Otherwise, retrieves the key from secure storage.
102
126
 
103
127
  Returns:
104
128
  str: The master encryption key
105
129
 
106
130
  Raises:
131
+ RuntimeError: If in Docker mode and no key is configured.
107
132
  ValueError: If the master key is not found in storage.
108
133
  """
109
134
  # First check for test mode
@@ -112,7 +137,13 @@ def get_master_key() -> str:
112
137
 
113
138
  # Next check if running in Docker
114
139
  if os.environ.get("FLOWFILE_MODE") == "docker":
115
- return get_docker_secret_key()
140
+ key = get_docker_secret_key()
141
+ if key is None:
142
+ raise RuntimeError(
143
+ "Master key not configured. Set FLOWFILE_MASTER_KEY environment variable "
144
+ "or mount the flowfile_master_key Docker secret."
145
+ )
146
+ return key
116
147
 
117
148
  # Otherwise read from local storage
118
149
  key = get_password("flowfile", "master_key")
@@ -121,9 +152,41 @@ def get_master_key() -> str:
121
152
  return key
122
153
 
123
154
 
124
- def decrypt_secret(encrypted_value) -> SecretStr:
155
+ def derive_user_key(user_id: int) -> bytes:
125
156
  """
126
- Decrypt an encrypted value using the master key.
157
+ Derive a user-specific encryption key from the master key using HKDF.
158
+
159
+ This provides cryptographic isolation between users - each user's secrets
160
+ are encrypted with a unique key derived from the master key.
161
+
162
+ Args:
163
+ user_id: The unique identifier for the user
164
+
165
+ Returns:
166
+ bytes: A 32-byte URL-safe base64-encoded key suitable for Fernet
167
+ """
168
+ master_key = get_master_key().encode()
169
+
170
+ # Use HKDF to derive a user-specific key
171
+ hkdf = HKDF(
172
+ algorithm=hashes.SHA256(),
173
+ length=32, # Fernet requires 32 bytes
174
+ salt=KEY_DERIVATION_VERSION, # Static salt is fine for key derivation
175
+ info=f"user-{user_id}".encode(), # User-specific context
176
+ )
177
+
178
+ # Derive raw key material and encode for Fernet
179
+ derived_key = hkdf.derive(master_key)
180
+ return base64.urlsafe_b64encode(derived_key)
181
+
182
+
183
+ def decrypt_secret(encrypted_value: str) -> SecretStr:
184
+ """
185
+ Decrypt an encrypted value.
186
+
187
+ Supports both new format (with embedded user_id) and legacy format.
188
+ - New format: $ffsec$1${user_id}${fernet_token} - user_id extracted automatically
189
+ - Legacy format: raw Fernet token - uses master key directly
127
190
 
128
191
  Args:
129
192
  encrypted_value: The encrypted value as a string
@@ -131,21 +194,48 @@ def decrypt_secret(encrypted_value) -> SecretStr:
131
194
  Returns:
132
195
  SecretStr: The decrypted value as a SecretStr
133
196
  """
197
+ # Check for new versioned format with embedded user_id
198
+ if encrypted_value.startswith(SECRET_FORMAT_PREFIX):
199
+ # Parse: $ffsec$1${user_id}${fernet_token}
200
+ remainder = encrypted_value[len(SECRET_FORMAT_PREFIX):]
201
+ parts = remainder.split("$", 1)
202
+ if len(parts) != 2:
203
+ raise ValueError("Invalid encrypted secret format")
204
+
205
+ embedded_user_id = int(parts[0])
206
+ fernet_token = parts[1]
207
+
208
+ key = derive_user_key(embedded_user_id)
209
+ f = Fernet(key)
210
+ return SecretStr(f.decrypt(fernet_token.encode()).decode())
211
+
212
+ # Legacy format - use master key directly
134
213
  key = get_master_key().encode()
135
214
  f = Fernet(key)
136
215
  return SecretStr(f.decrypt(encrypted_value.encode()).decode())
137
216
 
138
217
 
139
- def encrypt_secret(secret_value):
218
+ def encrypt_secret(secret_value: str, user_id: int | None = None) -> str:
140
219
  """
141
- Encrypt a secret value using the master key.
220
+ Encrypt a secret value.
221
+
222
+ If user_id is provided, uses per-user key derivation with embedded user_id format.
223
+ Otherwise, uses legacy master key encryption (for backward compatibility in tests).
142
224
 
143
225
  Args:
144
226
  secret_value: The secret value to encrypt
227
+ user_id: Optional user ID for per-user key derivation
145
228
 
146
229
  Returns:
147
230
  str: The encrypted value as a string
148
231
  """
232
+ if user_id is not None:
233
+ key = derive_user_key(user_id)
234
+ f = Fernet(key)
235
+ fernet_token = f.encrypt(secret_value.encode()).decode()
236
+ return f"{SECRET_FORMAT_PREFIX}{user_id}${fernet_token}"
237
+
238
+ # Legacy format for backward compatibility
149
239
  key = get_master_key().encode()
150
240
  f = Fernet(key)
151
241
  return f.encrypt(secret_value.encode()).decode()
@@ -1,5 +1,6 @@
1
1
  import gc
2
- from multiprocessing import Process, Queue
2
+ from multiprocessing import Process
3
+ from multiprocessing.queues import Queue
3
4
  from time import sleep
4
5
 
5
6
  from flowfile_worker import funcs, models, mp_context, status_dict, status_dict_lock
@@ -55,11 +56,14 @@ def handle_task(task_id: str, p: Process, progress: mp_context.Value, error_mess
55
56
  with status_dict_lock:
56
57
  status = status_dict[task_id]
57
58
  if status.status != "Cancelled":
58
- if progress.value == 100:
59
+ # Read progress value with lock to ensure consistency
60
+ with progress.get_lock():
61
+ final_progress = progress.value
62
+ if final_progress == 100:
59
63
  status.status = "Completed"
60
64
  if not q.empty():
61
65
  status.results = q.get()
62
- elif progress.value != -1:
66
+ elif final_progress != -1:
63
67
  status_dict[task_id].status = "Unknown Error"
64
68
 
65
69
  finally:
@@ -103,7 +107,7 @@ def start_process(
103
107
  kwargs["polars_serializable_object"] = polars_serializable_object
104
108
  kwargs["progress"] = mp_context.Value("i", 0)
105
109
  kwargs["error_message"] = mp_context.Array("c", 1024)
106
- kwargs["queue"] = Queue(maxsize=1)
110
+ kwargs["queue"] = mp_context.Queue(maxsize=1)
107
111
  kwargs["file_path"] = file_ref
108
112
  kwargs["flowfile_flow_id"] = flowfile_flow_id
109
113
  kwargs["flowfile_node_id"] = flowfile_node_id
@@ -145,7 +149,7 @@ def start_generic_process(
145
149
  kwargs["func"] = func_ref
146
150
  kwargs["progress"] = mp_context.Value("i", 0)
147
151
  kwargs["error_message"] = mp_context.Array("c", 1024)
148
- kwargs["queue"] = Queue(maxsize=1)
152
+ kwargs["queue"] = mp_context.Queue(maxsize=1)
149
153
  kwargs["file_path"] = file_ref
150
154
  kwargs["flowfile_flow_id"] = flowfile_flow_id
151
155
  kwargs["flowfile_node_id"] = flowfile_node_id
@@ -187,7 +191,7 @@ def start_fuzzy_process(
187
191
  """
188
192
  progress = mp_context.Value("i", 0)
189
193
  error_message = mp_context.Array("c", 1024)
190
- q = Queue(maxsize=1)
194
+ q = mp_context.Queue(maxsize=1)
191
195
 
192
196
  args: tuple[
193
197
  bytes,
@@ -1,26 +0,0 @@
1
-
2
- .context-menu[data-v-f6c94f99] {
3
- position: fixed;
4
- z-index: 1000;
5
- border: 1px solid #ccc;
6
- background-color: var(--color-background-primary);
7
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
8
- border-radius: 4px;
9
- user-select: none;
10
- }
11
- .context-menu ul[data-v-f6c94f99] {
12
- list-style: none;
13
- padding: 0;
14
- margin: 0;
15
- }
16
- .context-menu li[data-v-f6c94f99] {
17
- padding: 8px 16px;
18
- cursor: pointer;
19
- }
20
- .context-menu li.disabled[data-v-f6c94f99] {
21
- color: #ccc;
22
- cursor: not-allowed;
23
- }
24
- .context-menu li[data-v-f6c94f99]:hover:not(.disabled) {
25
- background-color: #f0f0f0;
26
- }
@@ -1,41 +0,0 @@
1
- import { d as defineComponent, o as openBlock, c as createElementBlock, b as createBaseVNode, F as Fragment, D as renderList, n as normalizeClass, t as toDisplayString, Z as normalizeStyle, i as _export_sfc } from "./index-fb6493ae.js";
2
- const _hoisted_1 = ["onClick"];
3
- const _sfc_main = /* @__PURE__ */ defineComponent({
4
- __name: "ContextMenu",
5
- props: {
6
- position: { type: Object, required: true },
7
- options: {
8
- type: Array,
9
- required: true
10
- }
11
- },
12
- emits: ["select", "close"],
13
- setup(__props, { emit: __emit }) {
14
- const emit = __emit;
15
- const selectOption = (action) => {
16
- emit("select", action);
17
- emit("close");
18
- };
19
- return (_ctx, _cache) => {
20
- return openBlock(), createElementBlock("div", {
21
- class: "context-menu",
22
- style: normalizeStyle({ top: __props.position.y + "px", left: __props.position.x + "px" })
23
- }, [
24
- createBaseVNode("ul", null, [
25
- (openBlock(true), createElementBlock(Fragment, null, renderList(__props.options, (option) => {
26
- return openBlock(), createElementBlock("li", {
27
- key: option.action,
28
- class: normalizeClass({ disabled: option.disabled }),
29
- onClick: ($event) => !option.disabled && selectOption(option.action)
30
- }, toDisplayString(option.label), 11, _hoisted_1);
31
- }), 128))
32
- ])
33
- ], 4);
34
- };
35
- }
36
- });
37
- const ContextMenu_vue_vue_type_style_index_0_scoped_f6c94f99_lang = "";
38
- const ContextMenu = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-f6c94f99"]]);
39
- export {
40
- ContextMenu as default
41
- };
@@ -1,41 +0,0 @@
1
- import { d as defineComponent, o as openBlock, c as createElementBlock, b as createBaseVNode, F as Fragment, D as renderList, n as normalizeClass, t as toDisplayString, Z as normalizeStyle, i as _export_sfc } from "./index-fb6493ae.js";
2
- const _hoisted_1 = ["onClick"];
3
- const _sfc_main = /* @__PURE__ */ defineComponent({
4
- __name: "ContextMenu",
5
- props: {
6
- position: { type: Object, required: true },
7
- options: {
8
- type: Array,
9
- required: true
10
- }
11
- },
12
- emits: ["select", "close"],
13
- setup(__props, { emit: __emit }) {
14
- const emit = __emit;
15
- const selectOption = (action) => {
16
- emit("select", action);
17
- emit("close");
18
- };
19
- return (_ctx, _cache) => {
20
- return openBlock(), createElementBlock("div", {
21
- class: "context-menu",
22
- style: normalizeStyle({ top: __props.position.y + "px", left: __props.position.x + "px" })
23
- }, [
24
- createBaseVNode("ul", null, [
25
- (openBlock(true), createElementBlock(Fragment, null, renderList(__props.options, (option) => {
26
- return openBlock(), createElementBlock("li", {
27
- key: option.action,
28
- class: normalizeClass({ disabled: option.disabled }),
29
- onClick: ($event) => !option.disabled && selectOption(option.action)
30
- }, toDisplayString(option.label), 11, _hoisted_1);
31
- }), 128))
32
- ])
33
- ], 4);
34
- };
35
- }
36
- });
37
- const ContextMenu_vue_vue_type_style_index_0_scoped_6b361c01_lang = "";
38
- const ContextMenu = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-6b361c01"]]);
39
- export {
40
- ContextMenu as default
41
- };
@@ -1,41 +0,0 @@
1
- import { d as defineComponent, o as openBlock, c as createElementBlock, b as createBaseVNode, F as Fragment, D as renderList, n as normalizeClass, t as toDisplayString, Z as normalizeStyle, i as _export_sfc } from "./index-fb6493ae.js";
2
- const _hoisted_1 = ["onClick"];
3
- const _sfc_main = /* @__PURE__ */ defineComponent({
4
- __name: "ContextMenu",
5
- props: {
6
- position: { type: Object, required: true },
7
- options: {
8
- type: Array,
9
- required: true
10
- }
11
- },
12
- emits: ["select", "close"],
13
- setup(__props, { emit: __emit }) {
14
- const emit = __emit;
15
- const selectOption = (action) => {
16
- emit("select", action);
17
- emit("close");
18
- };
19
- return (_ctx, _cache) => {
20
- return openBlock(), createElementBlock("div", {
21
- class: "context-menu",
22
- style: normalizeStyle({ top: __props.position.y + "px", left: __props.position.x + "px" })
23
- }, [
24
- createBaseVNode("ul", null, [
25
- (openBlock(true), createElementBlock(Fragment, null, renderList(__props.options, (option) => {
26
- return openBlock(), createElementBlock("li", {
27
- key: option.action,
28
- class: normalizeClass({ disabled: option.disabled }),
29
- onClick: ($event) => !option.disabled && selectOption(option.action)
30
- }, toDisplayString(option.label), 11, _hoisted_1);
31
- }), 128))
32
- ])
33
- ], 4);
34
- };
35
- }
36
- });
37
- const ContextMenu_vue_vue_type_style_index_0_scoped_5437ad46_lang = "";
38
- const ContextMenu = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-5437ad46"]]);
39
- export {
40
- ContextMenu as default
41
- };
@@ -1,26 +0,0 @@
1
-
2
- .context-menu[data-v-6b361c01] {
3
- position: fixed;
4
- z-index: 1000;
5
- border: 1px solid #ccc;
6
- background-color: var(--color-background-primary);
7
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
8
- border-radius: 4px;
9
- user-select: none;
10
- }
11
- .context-menu ul[data-v-6b361c01] {
12
- list-style: none;
13
- padding: 0;
14
- margin: 0;
15
- }
16
- .context-menu li[data-v-6b361c01] {
17
- padding: 8px 16px;
18
- cursor: pointer;
19
- }
20
- .context-menu li.disabled[data-v-6b361c01] {
21
- color: #ccc;
22
- cursor: not-allowed;
23
- }
24
- .context-menu li[data-v-6b361c01]:hover:not(.disabled) {
25
- background-color: #f0f0f0;
26
- }
@@ -1,26 +0,0 @@
1
-
2
- .context-menu[data-v-5437ad46] {
3
- position: fixed;
4
- z-index: 1000;
5
- border: 1px solid #ccc;
6
- background-color: var(--color-background-primary);
7
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
8
- border-radius: 4px;
9
- user-select: none;
10
- }
11
- .context-menu ul[data-v-5437ad46] {
12
- list-style: none;
13
- padding: 0;
14
- margin: 0;
15
- }
16
- .context-menu li[data-v-5437ad46] {
17
- padding: 8px 16px;
18
- cursor: pointer;
19
- }
20
- .context-menu li.disabled[data-v-5437ad46] {
21
- color: #ccc;
22
- cursor: not-allowed;
23
- }
24
- .context-menu li[data-v-5437ad46]:hover:not(.disabled) {
25
- background-color: #f0f0f0;
26
- }
@@ -1,32 +0,0 @@
1
-
2
- .custom-node-wrapper[data-v-04422d4f] {
3
- padding: 1.5rem;
4
- background-color: var(--color-background-primary);
5
- }
6
- .node-header[data-v-04422d4f] {
7
- padding-bottom: 1rem;
8
- border-bottom: 1px solid var(--color-border-primary);
9
- margin-bottom: 1.5rem;
10
- }
11
- .node-title[data-v-04422d4f] {
12
- font-size: 1.25rem;
13
- font-weight: 700;
14
- color: var(--color-text-primary);
15
- }
16
- .node-category[data-v-04422d4f] {
17
- font-size: 0.875rem;
18
- color: var(--color-text-secondary);
19
- margin-top: 0.25rem;
20
- }
21
- .section-description[data-v-04422d4f] {
22
- font-size: 0.875rem;
23
- color: var(--color-text-secondary);
24
- margin-top: 0.25rem;
25
- margin-bottom: 1.25rem;
26
- padding-left: 0.5rem;
27
- }
28
- .components-container[data-v-04422d4f] {
29
- display: flex;
30
- flex-direction: column;
31
- gap: 1.25rem;
32
- }
@@ -1,51 +0,0 @@
1
-
2
- .context-menu[data-v-9d79e03b] {
3
- position: fixed;
4
- z-index: 1000;
5
- border: 1px solid #ccc;
6
- background-color: var(--color-background-primary);
7
- padding: 8px;
8
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
9
- border-radius: 4px;
10
- user-select: none;
11
- }
12
- .context-menu button[data-v-9d79e03b] {
13
- display: block;
14
- background: none;
15
- border: none;
16
- padding: 4px 8px;
17
- text-align: left;
18
- width: 100%;
19
- cursor: pointer;
20
- z-index: 100;
21
- }
22
- .context-menu button[data-v-9d79e03b]:hover {
23
- background-color: #f0f0f0;
24
- }
25
- .table-wrapper[data-v-9d79e03b] {
26
- max-height: 300px; /* Adjust this value as needed */
27
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* subtle shadow for depth */
28
- border-radius: 8px; /* rounded corners */
29
- overflow: auto; /* ensures the rounded corners are applied to the child elements */
30
- margin: 5px; /* adds a small margin around the table */
31
- }
32
- .context-menu[data-v-9d79e03b] {
33
- position: fixed;
34
- z-index: 1000;
35
- border: 1px solid #ccc;
36
- background-color: var(--color-background-primary);
37
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
38
- border-radius: 4px;
39
- }
40
- .context-menu ul[data-v-9d79e03b] {
41
- list-style: none;
42
- padding: 0;
43
- margin: 0;
44
- }
45
- .context-menu li[data-v-9d79e03b] {
46
- padding: 8px 16px;
47
- cursor: pointer;
48
- }
49
- .context-menu li[data-v-9d79e03b]:hover {
50
- background-color: #f0f0f0;
51
- }