@friggframework/core 2.0.0--canary.395.e3c5038.0 → 2.0.0--canary.395.f03dc2b.0
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.
- package/application/commands/integration-commands.test.js +7 -0
- package/application/commands/user-commands.js +1 -1
- package/credential/repositories/credential-repository-factory.js +6 -7
- package/credential/repositories/credential-repository-mongo.js +2 -2
- package/credential/repositories/credential-repository-postgres.js +2 -2
- package/database/config.js +93 -6
- package/database/encryption/README.md +97 -5
- package/database/encryption/encryption-integration.test.js +149 -109
- package/database/encryption/encryption-schema-registry.js +11 -52
- package/database/encryption/field-encryption-service.js +39 -29
- package/database/encryption/logger.js +79 -0
- package/database/prisma.js +15 -17
- package/database/repositories/health-check-repository-factory.js +10 -16
- package/database/repositories/health-check-repository-mongodb.js +72 -0
- package/database/repositories/health-check-repository-postgres.js +75 -0
- package/handlers/auth-flow.integration.test.js +7 -0
- package/handlers/integration-event-dispatcher.test.js +7 -0
- package/handlers/routers/health.test.js +7 -0
- package/handlers/routers/user.js +1 -1
- package/integrations/integration-router.js +1 -1
- package/integrations/repositories/integration-mapping-repository-factory.js +7 -7
- package/integrations/repositories/integration-mapping-repository-mongo.js +2 -2
- package/integrations/repositories/integration-mapping-repository-postgres.js +2 -2
- package/integrations/repositories/integration-repository-factory.js +8 -10
- package/integrations/repositories/integration-repository-mongo.js +2 -2
- package/integrations/repositories/integration-repository-postgres.js +2 -2
- package/integrations/tests/use-cases/create-integration.test.js +7 -0
- package/integrations/tests/use-cases/delete-integration-for-user.test.js +7 -0
- package/integrations/tests/use-cases/get-integration-for-user.test.js +7 -0
- package/integrations/tests/use-cases/get-integration-instance.test.js +7 -0
- package/integrations/tests/use-cases/get-integrations-for-user.test.js +7 -0
- package/integrations/tests/use-cases/update-integration.test.js +7 -0
- package/integrations/use-cases/load-integration-context-full.test.js +9 -2
- package/modules/module-hydration.test.js +7 -0
- package/modules/repositories/module-repository-factory.js +6 -19
- package/modules/repositories/module-repository-mongo.js +2 -2
- package/modules/repositories/module-repository-postgres.js +2 -2
- package/package.json +5 -5
- package/syncs/repositories/sync-repository-factory.js +6 -14
- package/syncs/repositories/sync-repository-mongo.js +2 -2
- package/syncs/repositories/sync-repository-postgres.js +2 -2
- package/token/repositories/token-repository-factory.js +6 -19
- package/token/repositories/token-repository-mongo.js +2 -2
- package/token/repositories/token-repository-postgres.js +2 -2
- package/user/repositories/user-repository-factory.js +6 -9
- package/user/repositories/user-repository-mongo.js +4 -11
- package/user/repositories/user-repository-postgres.js +4 -11
- package/websocket/repositories/websocket-connection-repository-factory.js +6 -20
- package/websocket/repositories/websocket-connection-repository-mongo.js +2 -2
- package/websocket/repositories/websocket-connection-repository-postgres.js +2 -2
- package/modules/test/auther.test.js +0 -97
- package/user/repositories/user-repository.js +0 -263
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
jest.mock('../../database/config', () => ({
|
|
2
|
+
DB_TYPE: 'mongodb',
|
|
3
|
+
getDatabaseType: jest.fn(() => 'mongodb'),
|
|
4
|
+
PRISMA_LOG_LEVEL: 'error,warn',
|
|
5
|
+
PRISMA_QUERY_LOGGING: false,
|
|
6
|
+
}));
|
|
7
|
+
|
|
1
8
|
const mockFindExecute = jest.fn();
|
|
2
9
|
|
|
3
10
|
jest.mock('../../integrations/use-cases/find-integration-context-by-external-entity-id', () => {
|
|
@@ -25,7 +25,7 @@ function mapErrorToResponse(error) {
|
|
|
25
25
|
* @returns {Object} User command object with CRUD operations
|
|
26
26
|
*/
|
|
27
27
|
function createUserCommands() {
|
|
28
|
-
const userRepository = createUserRepository(
|
|
28
|
+
const userRepository = createUserRepository();
|
|
29
29
|
|
|
30
30
|
return {
|
|
31
31
|
/**
|
|
@@ -2,6 +2,7 @@ const { CredentialRepositoryMongo } = require('./credential-repository-mongo');
|
|
|
2
2
|
const {
|
|
3
3
|
CredentialRepositoryPostgres,
|
|
4
4
|
} = require('./credential-repository-postgres');
|
|
5
|
+
const config = require('../../database/config');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Credential Repository Factory
|
|
@@ -17,25 +18,23 @@ const {
|
|
|
17
18
|
* Usage:
|
|
18
19
|
* ```javascript
|
|
19
20
|
* const repository = createCredentialRepository();
|
|
20
|
-
* const credential = await repository.findCredentialById(id); // ID is string
|
|
21
21
|
* ```
|
|
22
22
|
*
|
|
23
|
-
* @param {Object} [prismaClient] - Optional Prisma client for testing
|
|
24
23
|
* @returns {CredentialRepositoryInterface} Configured repository adapter
|
|
25
24
|
*/
|
|
26
|
-
function createCredentialRepository(
|
|
27
|
-
const dbType =
|
|
25
|
+
function createCredentialRepository() {
|
|
26
|
+
const dbType = config.DB_TYPE;
|
|
28
27
|
|
|
29
28
|
switch (dbType) {
|
|
30
29
|
case 'mongodb':
|
|
31
|
-
return new CredentialRepositoryMongo(
|
|
30
|
+
return new CredentialRepositoryMongo();
|
|
32
31
|
|
|
33
32
|
case 'postgresql':
|
|
34
|
-
return new CredentialRepositoryPostgres(
|
|
33
|
+
return new CredentialRepositoryPostgres();
|
|
35
34
|
|
|
36
35
|
default:
|
|
37
36
|
throw new Error(
|
|
38
|
-
`Unsupported
|
|
37
|
+
`Unsupported database type: ${dbType}. Supported values: 'mongodb', 'postgresql'`
|
|
39
38
|
);
|
|
40
39
|
}
|
|
41
40
|
}
|
|
@@ -13,9 +13,9 @@ const {
|
|
|
13
13
|
* - Dynamic schema support via JSON field
|
|
14
14
|
*/
|
|
15
15
|
class CredentialRepositoryMongo extends CredentialRepositoryInterface {
|
|
16
|
-
constructor(
|
|
16
|
+
constructor() {
|
|
17
17
|
super();
|
|
18
|
-
this.prisma =
|
|
18
|
+
this.prisma = prisma;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -13,9 +13,9 @@ const {
|
|
|
13
13
|
* - All returned IDs are converted to strings for application layer consistency
|
|
14
14
|
*/
|
|
15
15
|
class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
|
|
16
|
-
constructor(
|
|
16
|
+
constructor() {
|
|
17
17
|
super();
|
|
18
|
-
this.prisma =
|
|
18
|
+
this.prisma = prisma;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
package/database/config.js
CHANGED
|
@@ -4,11 +4,82 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Determines database type from app definition
|
|
8
|
+
* Reads backend/index.js Definition.database configuration
|
|
9
|
+
*
|
|
10
|
+
* @returns {'mongodb'|'postgresql'} Database type
|
|
11
|
+
* @throws {Error} If database type cannot be determined or app definition missing
|
|
10
12
|
*/
|
|
11
|
-
|
|
13
|
+
function getDatabaseType() {
|
|
14
|
+
try {
|
|
15
|
+
const path = require('node:path');
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const { findNearestBackendPackageJson } = require('../utils');
|
|
18
|
+
|
|
19
|
+
let backendIndexPath;
|
|
20
|
+
let database;
|
|
21
|
+
const backendPackagePath = findNearestBackendPackageJson();
|
|
22
|
+
|
|
23
|
+
if (!backendPackagePath) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
'[Frigg] Cannot find backend package.json. ' +
|
|
26
|
+
'Ensure backend/package.json exists in your project.'
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const backendDir = path.dirname(backendPackagePath);
|
|
31
|
+
backendIndexPath = path.join(backendDir, 'index.js');
|
|
32
|
+
|
|
33
|
+
if (!fs.existsSync(backendIndexPath)) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`[Frigg] Backend index.js not found at ${backendIndexPath}. ` +
|
|
36
|
+
'Ensure backend/index.js exists with a Definition export.'
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const backendModule = require(backendIndexPath);
|
|
41
|
+
database = backendModule?.Definition?.database;
|
|
42
|
+
|
|
43
|
+
if (!database) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
'[Frigg] App definition missing database configuration. ' +
|
|
46
|
+
`Add database: { postgres: { enable: true } } (or mongoDB/documentDB) to ${backendIndexPath}`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Determine database type from enabled database
|
|
51
|
+
// Priority order: postgres > mongoDB > documentDB
|
|
52
|
+
if (database.postgres?.enable === true) {
|
|
53
|
+
return 'postgresql';
|
|
54
|
+
}
|
|
55
|
+
if (database.mongoDB?.enable === true) {
|
|
56
|
+
return 'mongodb';
|
|
57
|
+
}
|
|
58
|
+
if (database.documentDB?.enable === true) {
|
|
59
|
+
return 'mongodb'; // DocumentDB is MongoDB-compatible
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
throw new Error(
|
|
63
|
+
'[Frigg] No database enabled in app definition. ' +
|
|
64
|
+
'Set one of: database.postgres.enable, database.mongoDB.enable, or database.documentDB.enable to true'
|
|
65
|
+
);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// Re-throw with context if it's our error
|
|
68
|
+
if (error.message.includes('[Frigg]')) {
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
// Wrap unexpected errors
|
|
72
|
+
throw new Error(
|
|
73
|
+
`[Frigg] Failed to determine database type: ${error.message}`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Cached database type (lazy evaluation)
|
|
80
|
+
* @type {'mongodb'|'postgresql'|null}
|
|
81
|
+
*/
|
|
82
|
+
let cachedDbType = null;
|
|
12
83
|
|
|
13
84
|
/**
|
|
14
85
|
* Enable Prisma debug logging
|
|
@@ -24,7 +95,23 @@ const PRISMA_LOG_LEVEL = process.env.PRISMA_LOG_LEVEL || 'error,warn';
|
|
|
24
95
|
const PRISMA_QUERY_LOGGING = process.env.PRISMA_QUERY_LOGGING === 'true';
|
|
25
96
|
|
|
26
97
|
module.exports = {
|
|
27
|
-
|
|
98
|
+
getDatabaseType, // Export for testing and direct use
|
|
28
99
|
PRISMA_LOG_LEVEL,
|
|
29
100
|
PRISMA_QUERY_LOGGING,
|
|
30
|
-
};
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Lazy-evaluated database type determined from app definition
|
|
105
|
+
* Only evaluates when accessed, preventing module load failures in test environments
|
|
106
|
+
* @type {'mongodb'|'postgresql'}
|
|
107
|
+
*/
|
|
108
|
+
Object.defineProperty(module.exports, 'DB_TYPE', {
|
|
109
|
+
get() {
|
|
110
|
+
if (cachedDbType === null) {
|
|
111
|
+
cachedDbType = getDatabaseType();
|
|
112
|
+
}
|
|
113
|
+
return cachedDbType;
|
|
114
|
+
},
|
|
115
|
+
enumerable: true,
|
|
116
|
+
configurable: true
|
|
117
|
+
});
|
|
@@ -41,7 +41,34 @@ External Systems (AWS KMS, Database)
|
|
|
41
41
|
|
|
42
42
|
## Configuration
|
|
43
43
|
|
|
44
|
-
###
|
|
44
|
+
### Database Selection
|
|
45
|
+
|
|
46
|
+
Database type is configured in `backend/index.js` app definition:
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
const appDefinition = {
|
|
50
|
+
database: {
|
|
51
|
+
mongoDB: {
|
|
52
|
+
enable: true, // Use MongoDB
|
|
53
|
+
},
|
|
54
|
+
documentDB: {
|
|
55
|
+
enable: false, // Use DocumentDB (MongoDB-compatible)
|
|
56
|
+
tlsCAFile: './security/global-bundle.pem',
|
|
57
|
+
},
|
|
58
|
+
postgres: {
|
|
59
|
+
enable: false, // Use PostgreSQL
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
// ... other config
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Important**: Only enable ONE database at a time. The framework will use the first enabled database in this priority order:
|
|
67
|
+
1. PostgreSQL (`postgres.enable = true`)
|
|
68
|
+
2. MongoDB (`mongoDB.enable = true`)
|
|
69
|
+
3. DocumentDB (`documentDB.enable = true`)
|
|
70
|
+
|
|
71
|
+
### Encryption Configuration
|
|
45
72
|
|
|
46
73
|
In `backend/index.js`:
|
|
47
74
|
|
|
@@ -371,14 +398,79 @@ npm test -- database/encryption/
|
|
|
371
398
|
|
|
372
399
|
### Integration Tests
|
|
373
400
|
|
|
401
|
+
Database type is determined from your app definition in `backend/index.js`:
|
|
402
|
+
|
|
403
|
+
```javascript
|
|
404
|
+
// backend/index.js
|
|
405
|
+
database: {
|
|
406
|
+
mongoDB: { enable: true }, // For MongoDB tests
|
|
407
|
+
postgres: { enable: false }
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
374
411
|
```bash
|
|
375
|
-
#
|
|
376
|
-
|
|
412
|
+
# Run encryption tests
|
|
413
|
+
npm test -- database/encryption/
|
|
414
|
+
|
|
415
|
+
# Tests use explicit database type parameter for testing:
|
|
416
|
+
# createHealthCheckRepository('mongodb')
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## Error Handling & Logging
|
|
420
|
+
|
|
421
|
+
### Error Handling Strategy
|
|
422
|
+
|
|
423
|
+
The encryption system uses **fail-fast error handling**:
|
|
424
|
+
|
|
425
|
+
- **Encryption failures**: Throw errors immediately (don't save corrupted/unencrypted sensitive data)
|
|
426
|
+
- **Decryption failures**: Throw errors immediately (prevents exposing invalid data)
|
|
427
|
+
- **Configuration errors**: Warn and disable encryption (graceful degradation for development)
|
|
428
|
+
- **Validation errors**: Throw errors on startup (catch issues before production)
|
|
377
429
|
|
|
378
|
-
|
|
379
|
-
|
|
430
|
+
**Why fail-fast?**
|
|
431
|
+
- Security-critical operations must not silently fail
|
|
432
|
+
- Better to expose issues during development than risk data breaches
|
|
433
|
+
- Prevents inconsistent database state (partially encrypted data)
|
|
434
|
+
|
|
435
|
+
### Logging Configuration
|
|
436
|
+
|
|
437
|
+
Configure log verbosity with `FRIGG_LOG_LEVEL`:
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
# Production (minimal logging)
|
|
441
|
+
FRIGG_LOG_LEVEL=WARN
|
|
442
|
+
|
|
443
|
+
# Development (detailed logging)
|
|
444
|
+
FRIGG_LOG_LEVEL=DEBUG
|
|
445
|
+
|
|
446
|
+
# Default
|
|
447
|
+
FRIGG_LOG_LEVEL=INFO
|
|
380
448
|
```
|
|
381
449
|
|
|
450
|
+
**Log Levels:**
|
|
451
|
+
- `DEBUG`: Detailed encryption operations (includes schema loading, key checks)
|
|
452
|
+
- `INFO`: High-level status (encryption enabled/disabled, custom schema registration)
|
|
453
|
+
- `WARN`: Configuration issues (missing keys, bypassed encryption)
|
|
454
|
+
- `ERROR`: Operation failures (encryption/decryption errors)
|
|
455
|
+
|
|
456
|
+
**Production Safety:**
|
|
457
|
+
- Sensitive data automatically sanitized in logs
|
|
458
|
+
- Long base64 strings truncated (prevents key leakage)
|
|
459
|
+
- Stack traces omitted in production (`STAGE=production`)
|
|
460
|
+
- Key IDs never logged
|
|
461
|
+
|
|
462
|
+
### Performance Optimizations
|
|
463
|
+
|
|
464
|
+
**Parallel field encryption:**
|
|
465
|
+
- Multiple fields encrypted concurrently using `Promise.all()`
|
|
466
|
+
- Significantly faster for models with many encrypted fields
|
|
467
|
+
- Example: 3 fields encrypted in ~30ms vs ~90ms (3x speedup)
|
|
468
|
+
|
|
469
|
+
**Deep cloning:**
|
|
470
|
+
- Uses native `structuredClone()` on Node.js 17+ (2-5x faster)
|
|
471
|
+
- Falls back to custom implementation for compatibility
|
|
472
|
+
- No external dependencies required
|
|
473
|
+
|
|
382
474
|
## Troubleshooting
|
|
383
475
|
|
|
384
476
|
### Encryption Not Working
|