@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
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
'
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
####
|
|
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
|
-
####
|
|
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,
|
|
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
|
-
|
|
13
|
-
|
|
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.
|
|
18
|
-
headers[this.
|
|
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.
|
|
26
|
-
this.
|
|
27
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
42
|
-
"@friggframework/prettier-config": "2.0.0-next.
|
|
43
|
-
"@friggframework/test": "2.0.0-next.
|
|
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": "
|
|
83
|
+
"gitHead": "5551318d5c45913810a6b03a5ea19c72b5c4cb2f"
|
|
84
84
|
}
|