@friggframework/core 2.0.0-next.55 → 2.0.0-next.56

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.
@@ -122,34 +122,140 @@ Or simply don't configure any encryption keys. In Production field level encrypt
122
122
 
123
123
  ## Encrypted Fields
124
124
 
125
- Fields are defined in `encryption-schema-registry.js`:
125
+ Core and custom encrypted fields are defined in `encryption-schema-registry.js`. See that file for the current list of encrypted fields.
126
126
 
127
+ **Core fields include**:
128
+ - OAuth tokens: `access_token`, `refresh_token`, `id_token`
129
+ - API keys: `api_key`, `apiKey`, `API_KEY_VALUE`
130
+ - Basic auth: `password`
131
+ - OAuth client credentials: `client_secret`
132
+
133
+ **Note**: API modules should use `api_key` (snake_case) in their `apiPropertiesToPersist.credential` arrays for consistency with OAuth2Requester and BasicAuthRequester conventions.
134
+
135
+ ### API Module Credential Naming Conventions
136
+
137
+ When creating API module definitions, use **snake_case** for credential property names to ensure automatic encryption:
138
+
139
+ **✅ Recommended (automatically encrypted):**
127
140
  ```javascript
128
- const ENCRYPTION_SCHEMA = {
129
- Credential: {
130
- fields: [
131
- 'data.access_token', // OAuth access token
132
- 'data.refresh_token', // OAuth refresh token
133
- 'data.id_token', // OpenID Connect ID token
134
- ],
135
- },
136
- IntegrationMapping: {
137
- fields: ['mapping'], // Complete mapping object
138
- },
139
- User: {
140
- fields: ['hashword'], // Password hash
141
+ // API Module Definition
142
+ const Definition = {
143
+ requiredAuthMethods: {
144
+ apiPropertiesToPersist: {
145
+ // For API key authentication
146
+ credential: ['api_key'], // Automatically encrypted
147
+ // or for OAuth authentication
148
+ credential: ['access_token', 'refresh_token'], // ✅ OAuth - encrypted
149
+ // or for Basic authentication
150
+ credential: ['username', 'password'], // Basic auth - encrypted
151
+ }
152
+ }
153
+ };
154
+
155
+ // API class (extends ApiKeyRequester)
156
+ class MyApi extends ApiKeyRequester {
157
+ constructor(params) {
158
+ super(params);
159
+ this.api_key = params.api_key; // ✅ snake_case convention
160
+ }
161
+ }
162
+ ```
163
+
164
+ **❌ Avoid (requires manual encryption schema):**
165
+ ```javascript
166
+ apiPropertiesToPersist: {
167
+ credential: ['customToken', 'proprietaryKey'] // ❌ Not in core schema
168
+ }
169
+ ```
170
+
171
+ For custom credential fields not in the core schema, use the custom encryption schema feature (see below).
172
+
173
+ ### Extending Encryption Schema
174
+
175
+ #### Option 1: Module-Level Encryption (API Module Developers)
176
+
177
+ **NEW**: API modules can now declare their encryption requirements directly in the module definition:
178
+
179
+ ```javascript
180
+ // api-module-library/my-service/definition.js
181
+ const Definition = {
182
+ moduleName: 'myService',
183
+ API: MyServiceApi,
184
+
185
+ // Declare which credential fields need encryption
186
+ encryption: {
187
+ credentialFields: ['api_key', 'webhook_secret']
141
188
  },
142
- Token: {
143
- fields: ['token'], // Authentication token
189
+
190
+ requiredAuthMethods: {
191
+ apiPropertiesToPersist: {
192
+ credential: ['api_key', 'webhook_secret'], // These will be auto-encrypted
193
+ entity: []
194
+ },
195
+ // ... other methods
196
+ }
197
+ };
198
+ ```
199
+
200
+ **How it works**:
201
+ 1. Module declares `encryption.credentialFields` array
202
+ 2. Framework automatically adds `data.` prefix: `['api_key']` → `['data.api_key']`
203
+ 3. Fields are merged with core encryption schema on app startup
204
+ 4. All modules across all integrations are scanned and combined
205
+
206
+ **Benefits**:
207
+ - ✅ Module authors control their own security requirements
208
+ - ✅ No need to modify core framework or app configuration
209
+ - ✅ Automatic encryption for API key-based integrations
210
+ - ✅ Works seamlessly with `apiPropertiesToPersist`
211
+
212
+ **Example - API Key Module**:
213
+ ```javascript
214
+ // API Module Definition
215
+ const Definition = {
216
+ moduleName: 'axiscare',
217
+ API: AxisCareApi,
218
+ encryption: {
219
+ credentialFields: ['api_key'] // Auto-encrypted as 'data.api_key'
144
220
  },
221
+ requiredAuthMethods: {
222
+ apiPropertiesToPersist: {
223
+ credential: ['api_key'] // Will be encrypted automatically
224
+ }
225
+ }
145
226
  };
227
+
228
+ // API Class (extends ApiKeyRequester)
229
+ class AxisCareApi extends ApiKeyRequester {
230
+ constructor(params) {
231
+ super(params);
232
+ this.api_key = params.api_key; // snake_case convention
233
+ }
234
+ }
146
235
  ```
147
236
 
148
- ### Extending Encryption Schema
237
+ **Example - Custom Authentication**:
238
+ ```javascript
239
+ const Definition = {
240
+ moduleName: 'customService',
241
+ encryption: {
242
+ credentialFields: [
243
+ 'signing_key',
244
+ 'webhook_secret',
245
+ 'data.custom_nested_field' // Can specify data. prefix explicitly
246
+ ]
247
+ }
248
+ };
249
+ ```
250
+
251
+ **Limitations**:
252
+ - Only supports Credential model fields (stored in `credential.data`)
253
+ - Cannot encrypt entity fields or custom models (use app-level schema for those)
254
+ - Applied globally once - module schemas loaded at app startup
149
255
 
150
- #### Recommended: Custom Schema via appDefinition (Integration Developers)
256
+ #### Option 2: App-Level Custom Schema (Integration Developers)
151
257
 
152
- Integration developers can extend encryption without modifying core framework files:
258
+ Integration developers can extend encryption without modifying core framework files.
153
259
 
154
260
  **In `backend/index.js`:**
155
261
 
@@ -234,7 +340,7 @@ await prisma.asanaTaskMapping.create({
234
340
  FRIGG_DEBUG=1 npm run frigg:start
235
341
  ```
236
342
 
237
- #### Advanced: Modifying Core Schema (Framework Developers)
343
+ #### Option 3: Modifying Core Schema (Framework Developers)
238
344
 
239
345
  Framework developers maintaining core models can modify `encryption-schema-registry.js`:
240
346
 
@@ -19,6 +19,11 @@ const CORE_ENCRYPTION_SCHEMA = {
19
19
  'data.access_token',
20
20
  'data.refresh_token',
21
21
  'data.id_token',
22
+ 'data.api_key',
23
+ 'data.apiKey',
24
+ 'data.API_KEY_VALUE',
25
+ 'data.password',
26
+ 'data.client_secret',
22
27
  ],
23
28
  },
24
29
 
@@ -109,6 +114,74 @@ function registerCustomSchema(schema) {
109
114
  );
110
115
  }
111
116
 
117
+ /**
118
+ * Extracts credential field paths from module definitions
119
+ * @param {Array} moduleDefinitions - Array of module definition objects
120
+ * @returns {Array<string>} Array of field paths with data. prefix
121
+ */
122
+ function extractCredentialFieldsFromModules(moduleDefinitions) {
123
+ const fields = [];
124
+
125
+ for (const moduleDef of moduleDefinitions) {
126
+ if (!moduleDef?.encryption?.credentialFields) {
127
+ continue;
128
+ }
129
+
130
+ const credentialFields = moduleDef.encryption.credentialFields;
131
+ if (!Array.isArray(credentialFields) || credentialFields.length === 0) {
132
+ continue;
133
+ }
134
+
135
+ for (const field of credentialFields) {
136
+ const prefixedField = field.startsWith('data.') ? field : `data.${field}`;
137
+ fields.push(prefixedField);
138
+ }
139
+ }
140
+
141
+ return [...new Set(fields)];
142
+ }
143
+
144
+ /**
145
+ * Loads and registers encryption schemas from API module definitions.
146
+ * Each module can declare credentialFields to encrypt in its encryption config.
147
+ *
148
+ * @param {Array} integrations - Array of integration classes with modules
149
+ */
150
+ function loadModuleEncryptionSchemas(integrations) {
151
+ if (!integrations) {
152
+ throw new Error('integrations parameter is required');
153
+ }
154
+
155
+ if (!Array.isArray(integrations)) {
156
+ throw new Error('integrations must be an array');
157
+ }
158
+
159
+ if (integrations.length === 0) {
160
+ return;
161
+ }
162
+
163
+ const { getModulesDefinitionFromIntegrationClasses } = require('../integrations/utils/map-integration-dto');
164
+
165
+ const moduleDefinitions = getModulesDefinitionFromIntegrationClasses(integrations);
166
+ const credentialFields = extractCredentialFieldsFromModules(moduleDefinitions);
167
+
168
+ if (credentialFields.length === 0) {
169
+ return;
170
+ }
171
+
172
+ const moduleSchema = {
173
+ Credential: {
174
+ fields: credentialFields
175
+ }
176
+ };
177
+
178
+ logger.info(
179
+ `Registering module-level encryption for ${credentialFields.length} credential fields`
180
+ );
181
+
182
+ registerCustomSchema(moduleSchema);
183
+ }
184
+
112
185
  /**
113
186
  * Loads and registers custom encryption schema from appDefinition.
114
187
  * Gracefully handles cases where appDefinition is not available.
@@ -139,11 +212,17 @@ function loadCustomEncryptionSchema() {
139
212
  return; // No app definition found
140
213
  }
141
214
 
215
+ // Load app-level custom schema
142
216
  const customSchema = appDefinition.encryption?.schema;
143
-
144
217
  if (customSchema && Object.keys(customSchema).length > 0) {
145
218
  registerCustomSchema(customSchema);
146
219
  }
220
+
221
+ // Load module-level encryption schemas from integrations
222
+ const integrations = appDefinition.integrations;
223
+ if (integrations && Array.isArray(integrations)) {
224
+ loadModuleEncryptionSchemas(integrations);
225
+ }
147
226
  } catch (error) {
148
227
  // Silently ignore errors - custom schema is optional
149
228
  // This handles cases like:
@@ -182,6 +261,8 @@ module.exports = {
182
261
  getEncryptedModels,
183
262
  registerCustomSchema,
184
263
  loadCustomEncryptionSchema,
264
+ loadModuleEncryptionSchemas,
265
+ extractCredentialFieldsFromModules,
185
266
  validateCustomSchema,
186
- resetCustomSchema, // For testing only
267
+ resetCustomSchema,
187
268
  };
@@ -1,4 +1,5 @@
1
1
  const { Requester } = require('./requester');
2
+ const { get } = require('../../assertions');
2
3
  const { ModuleConstants } = require('../ModuleConstants');
3
4
 
4
5
 
@@ -9,27 +10,42 @@ class ApiKeyRequester extends Requester {
9
10
  constructor(params) {
10
11
  super(params);
11
12
  this.requesterType = 'apiKey';
12
- this.API_KEY_NAME = 'key';
13
- this.API_KEY_VALUE = null;
13
+
14
+ // Use snake_case convention consistent with OAuth2Requester and BasicAuthRequester
15
+ this.api_key_name = get(params, 'api_key_name', 'key');
16
+ this.api_key = get(params, 'api_key', null);
17
+
18
+ // Backward compatibility: support old naming convention
19
+ if (!this.api_key && params.API_KEY_VALUE) {
20
+ this.api_key = params.API_KEY_VALUE;
21
+ }
22
+ if (!this.api_key_name && params.API_KEY_NAME) {
23
+ this.api_key_name = params.API_KEY_NAME;
24
+ }
14
25
  }
15
26
 
16
27
  async addAuthHeaders(headers) {
17
- if (this.API_KEY_VALUE) {
18
- headers[this.API_KEY_NAME] = this.API_KEY_VALUE;
28
+ if (this.api_key) {
29
+ headers[this.api_key_name] = this.api_key;
19
30
  }
20
31
  return headers;
21
32
  }
22
33
 
23
34
  isAuthenticated() {
24
35
  return (
25
- this.API_KEY_VALUE !== null &&
26
- this.API_KEY_VALUE !== undefined &&
27
- this.API_KEY_VALUE.trim().length() > 0
36
+ this.api_key !== null &&
37
+ this.api_key !== undefined &&
38
+ typeof this.api_key === 'string' &&
39
+ this.api_key.trim().length > 0
28
40
  );
29
41
  }
30
42
 
31
43
  setApiKey(api_key) {
32
- this.API_KEY_VALUE = api_key;
44
+ this.api_key = api_key;
45
+ }
46
+
47
+ setApiKeyName(api_key_name) {
48
+ this.api_key_name = api_key_name;
33
49
  }
34
50
  }
35
51
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/core",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0-next.55",
4
+ "version": "2.0.0-next.56",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
7
7
  "@aws-sdk/client-kms": "^3.588.0",
@@ -38,9 +38,9 @@
38
38
  }
39
39
  },
40
40
  "devDependencies": {
41
- "@friggframework/eslint-config": "2.0.0-next.55",
42
- "@friggframework/prettier-config": "2.0.0-next.55",
43
- "@friggframework/test": "2.0.0-next.55",
41
+ "@friggframework/eslint-config": "2.0.0-next.56",
42
+ "@friggframework/prettier-config": "2.0.0-next.56",
43
+ "@friggframework/test": "2.0.0-next.56",
44
44
  "@prisma/client": "^6.17.0",
45
45
  "@types/lodash": "4.17.15",
46
46
  "@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -80,5 +80,5 @@
80
80
  "publishConfig": {
81
81
  "access": "public"
82
82
  },
83
- "gitHead": "7f86d4b5faaab4de74e6e3676948b62d1e1a5bb1"
83
+ "gitHead": "5551318d5c45913810a6b03a5ea19c72b5c4cb2f"
84
84
  }