@friggframework/core 2.0.0-next.44 → 2.0.0-next.46
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/README.md +28 -0
- package/application/commands/integration-commands.js +19 -0
- package/core/Worker.js +8 -21
- package/credential/repositories/credential-repository-mongo.js +14 -8
- package/credential/repositories/credential-repository-postgres.js +14 -8
- package/credential/repositories/credential-repository.js +3 -8
- package/database/MONGODB_TRANSACTION_FIX.md +198 -0
- package/database/adapters/lambda-invoker.js +97 -0
- package/database/config.js +11 -2
- package/database/models/WebsocketConnection.js +11 -10
- package/database/prisma.js +63 -3
- package/database/repositories/health-check-repository-mongodb.js +3 -0
- package/database/repositories/migration-status-repository-s3.js +137 -0
- package/database/use-cases/check-database-state-use-case.js +81 -0
- package/database/use-cases/check-encryption-health-use-case.js +3 -2
- package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
- package/database/use-cases/get-migration-status-use-case.js +93 -0
- package/database/use-cases/run-database-migration-use-case.js +137 -0
- package/database/use-cases/trigger-database-migration-use-case.js +157 -0
- package/database/utils/mongodb-collection-utils.js +91 -0
- package/database/utils/mongodb-schema-init.js +106 -0
- package/database/utils/prisma-runner.js +400 -0
- package/database/utils/prisma-schema-parser.js +182 -0
- package/encrypt/Cryptor.js +14 -16
- package/generated/prisma-mongodb/client.d.ts +1 -0
- package/generated/prisma-mongodb/client.js +4 -0
- package/generated/prisma-mongodb/default.d.ts +1 -0
- package/generated/prisma-mongodb/default.js +4 -0
- package/generated/prisma-mongodb/edge.d.ts +1 -0
- package/generated/prisma-mongodb/edge.js +334 -0
- package/generated/prisma-mongodb/index-browser.js +316 -0
- package/generated/prisma-mongodb/index.d.ts +22897 -0
- package/generated/prisma-mongodb/index.js +359 -0
- package/generated/prisma-mongodb/package.json +183 -0
- package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
- package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
- package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
- package/generated/prisma-mongodb/runtime/binary.js +289 -0
- package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
- package/generated/prisma-mongodb/runtime/edge.js +34 -0
- package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
- package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
- package/generated/prisma-mongodb/runtime/react-native.js +83 -0
- package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
- package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
- package/generated/prisma-mongodb/schema.prisma +362 -0
- package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
- package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
- package/generated/prisma-mongodb/wasm.d.ts +1 -0
- package/generated/prisma-mongodb/wasm.js +341 -0
- package/generated/prisma-postgresql/client.d.ts +1 -0
- package/generated/prisma-postgresql/client.js +4 -0
- package/generated/prisma-postgresql/default.d.ts +1 -0
- package/generated/prisma-postgresql/default.js +4 -0
- package/generated/prisma-postgresql/edge.d.ts +1 -0
- package/generated/prisma-postgresql/edge.js +356 -0
- package/generated/prisma-postgresql/index-browser.js +338 -0
- package/generated/prisma-postgresql/index.d.ts +25071 -0
- package/generated/prisma-postgresql/index.js +381 -0
- package/generated/prisma-postgresql/package.json +183 -0
- package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
- package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
- package/generated/prisma-postgresql/query_engine_bg.js +2 -0
- package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
- package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
- package/generated/prisma-postgresql/runtime/binary.js +289 -0
- package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
- package/generated/prisma-postgresql/runtime/edge.js +34 -0
- package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
- package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
- package/generated/prisma-postgresql/runtime/react-native.js +83 -0
- package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
- package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
- package/generated/prisma-postgresql/schema.prisma +345 -0
- package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
- package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
- package/generated/prisma-postgresql/wasm.d.ts +1 -0
- package/generated/prisma-postgresql/wasm.js +363 -0
- package/handlers/WEBHOOKS.md +653 -0
- package/handlers/backend-utils.js +118 -3
- package/handlers/database-migration-handler.js +227 -0
- package/handlers/routers/auth.js +1 -1
- package/handlers/routers/db-migration.handler.js +29 -0
- package/handlers/routers/db-migration.js +256 -0
- package/handlers/routers/health.js +41 -6
- package/handlers/routers/integration-webhook-routers.js +67 -0
- package/handlers/use-cases/check-integrations-health-use-case.js +22 -10
- package/handlers/workers/db-migration.js +352 -0
- package/index.js +28 -0
- package/integrations/WEBHOOK-QUICKSTART.md +151 -0
- package/integrations/integration-base.js +74 -3
- package/integrations/integration-router.js +60 -70
- package/integrations/repositories/integration-repository-interface.js +12 -0
- package/integrations/repositories/integration-repository-mongo.js +32 -0
- package/integrations/repositories/integration-repository-postgres.js +33 -0
- package/integrations/repositories/process-repository-postgres.js +43 -20
- package/integrations/tests/doubles/dummy-integration-class.js +1 -8
- package/integrations/tests/doubles/test-integration-repository.js +2 -2
- package/logs/logger.js +0 -4
- package/modules/entity.js +0 -1
- package/modules/repositories/module-repository-mongo.js +3 -12
- package/modules/repositories/module-repository-postgres.js +0 -11
- package/modules/repositories/module-repository.js +1 -12
- package/modules/use-cases/get-entity-options-by-id.js +1 -1
- package/modules/use-cases/get-module.js +1 -2
- package/modules/use-cases/refresh-entity-options.js +1 -1
- package/modules/use-cases/test-module-auth.js +1 -1
- package/package.json +82 -66
- package/prisma-mongodb/schema.prisma +21 -21
- package/prisma-postgresql/schema.prisma +15 -15
- package/queues/queuer-util.js +28 -15
- package/types/core/index.d.ts +2 -2
- package/types/module-plugin/index.d.ts +0 -2
- package/user/repositories/user-repository-mongo.js +53 -12
- package/user/repositories/user-repository-postgres.js +53 -14
- package/user/use-cases/authenticate-user.js +127 -0
- package/user/use-cases/authenticate-with-shared-secret.js +48 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
- package/user/use-cases/get-user-from-x-frigg-headers.js +106 -0
- package/user/use-cases/login-user.js +1 -1
- package/user/user.js +18 -2
- package/websocket/repositories/websocket-connection-repository-mongo.js +11 -10
- package/websocket/repositories/websocket-connection-repository-postgres.js +11 -10
- package/websocket/repositories/websocket-connection-repository.js +11 -10
- package/application/commands/integration-commands.test.js +0 -123
- package/database/encryption/encryption-integration.test.js +0 -553
- package/database/encryption/encryption-schema-registry.test.js +0 -392
- package/database/encryption/field-encryption-service.test.js +0 -525
- package/database/encryption/mongo-decryption-fix-verification.test.js +0 -348
- package/database/encryption/postgres-decryption-fix-verification.test.js +0 -371
- package/database/encryption/postgres-relation-decryption.test.js +0 -245
- package/database/encryption/prisma-encryption-extension.test.js +0 -439
- package/errors/base-error.test.js +0 -32
- package/errors/fetch-error.test.js +0 -79
- package/errors/halt-error.test.js +0 -11
- package/errors/validation-errors.test.js +0 -120
- package/handlers/auth-flow.integration.test.js +0 -147
- package/handlers/integration-event-dispatcher.test.js +0 -141
- package/handlers/routers/health.test.js +0 -210
- package/integrations/tests/use-cases/create-integration.test.js +0 -131
- package/integrations/tests/use-cases/delete-integration-for-user.test.js +0 -150
- package/integrations/tests/use-cases/find-integration-context-by-external-entity-id.test.js +0 -92
- package/integrations/tests/use-cases/get-integration-for-user.test.js +0 -150
- package/integrations/tests/use-cases/get-integration-instance.test.js +0 -176
- package/integrations/tests/use-cases/get-integrations-for-user.test.js +0 -176
- package/integrations/tests/use-cases/get-possible-integrations.test.js +0 -188
- package/integrations/tests/use-cases/update-integration-messages.test.js +0 -142
- package/integrations/tests/use-cases/update-integration-status.test.js +0 -103
- package/integrations/tests/use-cases/update-integration.test.js +0 -141
- package/integrations/use-cases/create-process.test.js +0 -178
- package/integrations/use-cases/get-process.test.js +0 -190
- package/integrations/use-cases/load-integration-context-full.test.js +0 -329
- package/integrations/use-cases/load-integration-context.test.js +0 -114
- package/integrations/use-cases/update-process-metrics.test.js +0 -308
- package/integrations/use-cases/update-process-state.test.js +0 -256
- package/lambda/TimeoutCatcher.test.js +0 -68
- package/logs/logger.test.js +0 -76
- package/modules/module-hydration.test.js +0 -205
- package/modules/requester/requester.test.js +0 -28
- package/user/tests/use-cases/create-individual-user.test.js +0 -24
- package/user/tests/use-cases/create-organization-user.test.js +0 -28
- package/user/tests/use-cases/create-token-for-user-id.test.js +0 -19
- package/user/tests/use-cases/get-user-from-bearer-token.test.js +0 -64
- package/user/tests/use-cases/login-user.test.js +0 -140
|
@@ -1,553 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration tests for field-level encryption
|
|
3
|
-
* Tests transparent encryption/decryption with Prisma for MongoDB and PostgreSQL
|
|
4
|
-
*
|
|
5
|
-
* These tests verify:
|
|
6
|
-
* - Create operations encrypt fields
|
|
7
|
-
* - Read operations decrypt fields
|
|
8
|
-
* - Update operations handle encryption
|
|
9
|
-
* - Upsert operations work correctly
|
|
10
|
-
* - FindMany operations decrypt arrays
|
|
11
|
-
* - Null/undefined/empty values are handled
|
|
12
|
-
* - Database stores encrypted data
|
|
13
|
-
*
|
|
14
|
-
* Database-Agnostic Design:
|
|
15
|
-
* - Uses repository pattern for raw database access (getRawCredentialById)
|
|
16
|
-
* - MongoDB: Uses Mongoose for raw collection access
|
|
17
|
-
* - PostgreSQL: Uses Prisma $queryRaw for raw SQL queries
|
|
18
|
-
* - Field names match Prisma schema (userId, externalId, not user_id/entity_id)
|
|
19
|
-
* - Uses externalId (string) for test data instead of userId (ObjectId reference)
|
|
20
|
-
*
|
|
21
|
-
* Prerequisites:
|
|
22
|
-
* - Database must be running and accessible
|
|
23
|
-
* - For MongoDB: Replica set recommended (for transactions)
|
|
24
|
-
* - For PostgreSQL: Database must exist
|
|
25
|
-
* - Database type configured in backend/index.js app definition
|
|
26
|
-
*
|
|
27
|
-
* Note: Test explicitly passes 'mongodb' to repository factory for testing purposes
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
// Set default DATABASE_URL for testing if not already set
|
|
31
|
-
if (!process.env.DATABASE_URL) {
|
|
32
|
-
process.env.DATABASE_URL = 'mongodb://localhost:27017/frigg?replicaSet=rs0';
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Enable encryption for testing (bypass test stage check)
|
|
36
|
-
process.env.STAGE = 'integration-test';
|
|
37
|
-
process.env.AES_KEY_ID = 'test-key-id';
|
|
38
|
-
process.env.AES_KEY = 'test-aes-key-32-characters-long!';
|
|
39
|
-
|
|
40
|
-
jest.mock('../config', () => ({
|
|
41
|
-
DB_TYPE: 'mongodb',
|
|
42
|
-
getDatabaseType: jest.fn(() => 'mongodb'),
|
|
43
|
-
PRISMA_LOG_LEVEL: 'error,warn',
|
|
44
|
-
PRISMA_QUERY_LOGGING: false,
|
|
45
|
-
}));
|
|
46
|
-
|
|
47
|
-
const { prisma, connectPrisma, disconnectPrisma } = require('../prisma');
|
|
48
|
-
const { createHealthCheckRepository } = require('../repositories/health-check-repository-factory');
|
|
49
|
-
const { mongoose } = require('../mongoose');
|
|
50
|
-
|
|
51
|
-
describe('Field-Level Encryption Integration Tests', () => {
|
|
52
|
-
// Use externalId for test identification (works with both MongoDB and PostgreSQL)
|
|
53
|
-
const testExternalId = 'test-encryption-integration-id';
|
|
54
|
-
let repository;
|
|
55
|
-
|
|
56
|
-
beforeAll(async () => {
|
|
57
|
-
await connectPrisma();
|
|
58
|
-
// Connect mongoose for raw database queries
|
|
59
|
-
if (mongoose.connection.readyState === 0) {
|
|
60
|
-
await mongoose.connect(process.env.DATABASE_URL);
|
|
61
|
-
}
|
|
62
|
-
// Create database-specific repository for raw access
|
|
63
|
-
// Pass explicit database type for testing
|
|
64
|
-
repository = createHealthCheckRepository('mongodb');
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
afterAll(async () => {
|
|
68
|
-
// Clean up test data - delete all test credentials by externalId
|
|
69
|
-
await prisma.credential.deleteMany({
|
|
70
|
-
where: { externalId: { startsWith: 'test-encryption-' } },
|
|
71
|
-
});
|
|
72
|
-
await mongoose.disconnect();
|
|
73
|
-
await disconnectPrisma();
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
afterEach(async () => {
|
|
77
|
-
// Clean up after each test
|
|
78
|
-
await prisma.credential.deleteMany({
|
|
79
|
-
where: { externalId: { startsWith: 'test-encryption-' } },
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe('Create Operations', () => {
|
|
84
|
-
it('should encrypt sensitive fields on create', async () => {
|
|
85
|
-
const credential = await prisma.credential.create({
|
|
86
|
-
data: {
|
|
87
|
-
externalId: testExternalId,
|
|
88
|
-
data: {
|
|
89
|
-
access_token: 'secret-token-123',
|
|
90
|
-
refresh_token: 'refresh-token-456',
|
|
91
|
-
domain: 'example.com',
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// Verify decrypted values returned to application
|
|
97
|
-
expect(credential.data.access_token).toBe('secret-token-123');
|
|
98
|
-
expect(credential.data.refresh_token).toBe('refresh-token-456');
|
|
99
|
-
expect(credential.data.domain).toBe('example.com');
|
|
100
|
-
|
|
101
|
-
// Verify raw database has encrypted values
|
|
102
|
-
const rawDoc = await repository.getRawCredentialById(credential.id);
|
|
103
|
-
|
|
104
|
-
expect(rawDoc.data.access_token).not.toBe('secret-token-123');
|
|
105
|
-
expect(rawDoc.data.access_token).toContain(':');
|
|
106
|
-
expect(rawDoc.data.refresh_token).not.toBe('refresh-token-456');
|
|
107
|
-
expect(rawDoc.data.refresh_token).toContain(':');
|
|
108
|
-
// domain should NOT be encrypted (not in schema registry)
|
|
109
|
-
expect(rawDoc.data.domain).toBe('example.com');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('should handle null and undefined values', async () => {
|
|
113
|
-
const credential = await prisma.credential.create({
|
|
114
|
-
data: {
|
|
115
|
-
externalId: testExternalId,
|
|
116
|
-
data: {
|
|
117
|
-
access_token: null,
|
|
118
|
-
domain: 'example.com',
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
expect(credential.data.access_token).toBeNull();
|
|
124
|
-
expect(credential.data.domain).toBe('example.com');
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('should handle empty strings', async () => {
|
|
128
|
-
const credential = await prisma.credential.create({
|
|
129
|
-
data: {
|
|
130
|
-
externalId: testExternalId,
|
|
131
|
-
data: {
|
|
132
|
-
access_token: '',
|
|
133
|
-
domain: 'example.com',
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// Empty strings should not be encrypted
|
|
139
|
-
expect(credential.data.access_token).toBe('');
|
|
140
|
-
expect(credential.data.domain).toBe('example.com');
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
describe('Read Operations', () => {
|
|
145
|
-
it('should decrypt fields on findUnique', async () => {
|
|
146
|
-
// Create with encrypted data
|
|
147
|
-
const created = await prisma.credential.create({
|
|
148
|
-
data: {
|
|
149
|
-
externalId: testExternalId,
|
|
150
|
-
data: {
|
|
151
|
-
access_token: 'secret-find-unique',
|
|
152
|
-
domain: 'findunique.com',
|
|
153
|
-
},
|
|
154
|
-
},
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// Read back
|
|
158
|
-
const found = await prisma.credential.findUnique({
|
|
159
|
-
where: { id: created.id },
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
expect(found.data.access_token).toBe('secret-find-unique');
|
|
163
|
-
expect(found.data.domain).toBe('findunique.com');
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('should decrypt fields on findFirst', async () => {
|
|
167
|
-
await prisma.credential.create({
|
|
168
|
-
data: {
|
|
169
|
-
externalId: testExternalId,
|
|
170
|
-
data: {
|
|
171
|
-
access_token: 'secret-find-first',
|
|
172
|
-
domain: 'findfirst.com',
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
const found = await prisma.credential.findFirst({
|
|
178
|
-
where: { externalId: { startsWith: 'test-encryption-' } },
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
expect(found.data.access_token).toBe('secret-find-first');
|
|
182
|
-
expect(found.data.domain).toBe('findfirst.com');
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it('should decrypt array of results on findMany', async () => {
|
|
186
|
-
// Create multiple credentials
|
|
187
|
-
await prisma.credential.createMany({
|
|
188
|
-
data: [
|
|
189
|
-
{
|
|
190
|
-
externalId: 'test-encryption-entity-1',
|
|
191
|
-
data: {
|
|
192
|
-
access_token: 'secret-1',
|
|
193
|
-
domain: 'domain1.com',
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
externalId: 'test-encryption-entity-2',
|
|
198
|
-
data: {
|
|
199
|
-
access_token: 'secret-2',
|
|
200
|
-
domain: 'domain2.com',
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
externalId: 'test-encryption-entity-3',
|
|
205
|
-
data: {
|
|
206
|
-
access_token: 'secret-3',
|
|
207
|
-
domain: 'domain3.com',
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
],
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
const credentials = await prisma.credential.findMany({
|
|
214
|
-
where: { externalId: { startsWith: 'test-encryption-' } },
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
expect(credentials).toHaveLength(3);
|
|
218
|
-
expect(credentials[0].data.access_token).toBe('secret-1');
|
|
219
|
-
expect(credentials[1].data.access_token).toBe('secret-2');
|
|
220
|
-
expect(credentials[2].data.access_token).toBe('secret-3');
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it('should return null for non-existent records', async () => {
|
|
224
|
-
// Use a valid ObjectId format that doesn't exist in database
|
|
225
|
-
const { ObjectId } = require('mongodb');
|
|
226
|
-
const nonExistentId = new ObjectId().toString();
|
|
227
|
-
|
|
228
|
-
const found = await prisma.credential.findUnique({
|
|
229
|
-
where: { id: nonExistentId },
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
expect(found).toBeNull();
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
it('should return empty array for no matches', async () => {
|
|
236
|
-
const credentials = await prisma.credential.findMany({
|
|
237
|
-
where: { externalId: 'non-existent-external-id' },
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
expect(credentials).toEqual([]);
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
describe('Update Operations', () => {
|
|
245
|
-
it('should encrypt new values on update', async () => {
|
|
246
|
-
// Create
|
|
247
|
-
const created = await prisma.credential.create({
|
|
248
|
-
data: {
|
|
249
|
-
externalId: testExternalId,
|
|
250
|
-
data: {
|
|
251
|
-
access_token: 'old-token',
|
|
252
|
-
domain: 'old.com',
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
// Update
|
|
258
|
-
const updated = await prisma.credential.update({
|
|
259
|
-
where: { id: created.id },
|
|
260
|
-
data: {
|
|
261
|
-
data: {
|
|
262
|
-
access_token: 'new-token',
|
|
263
|
-
domain: 'new.com',
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// Verify decrypted values
|
|
269
|
-
expect(updated.data.access_token).toBe('new-token');
|
|
270
|
-
expect(updated.data.domain).toBe('new.com');
|
|
271
|
-
|
|
272
|
-
// Verify raw database has new encrypted value
|
|
273
|
-
const rawDoc = await repository.getRawCredentialById(created.id);
|
|
274
|
-
|
|
275
|
-
expect(rawDoc.data.access_token).not.toBe('new-token');
|
|
276
|
-
expect(rawDoc.data.access_token).toContain(':');
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
it('should handle partial updates', async () => {
|
|
280
|
-
const created = await prisma.credential.create({
|
|
281
|
-
data: {
|
|
282
|
-
externalId: testExternalId,
|
|
283
|
-
data: {
|
|
284
|
-
access_token: 'original-token',
|
|
285
|
-
refresh_token: 'original-refresh',
|
|
286
|
-
domain: 'original.com',
|
|
287
|
-
},
|
|
288
|
-
},
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
// Update only access_token
|
|
292
|
-
const updated = await prisma.credential.update({
|
|
293
|
-
where: { id: created.id },
|
|
294
|
-
data: {
|
|
295
|
-
data: {
|
|
296
|
-
...created.data,
|
|
297
|
-
access_token: 'updated-token',
|
|
298
|
-
},
|
|
299
|
-
},
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
expect(updated.data.access_token).toBe('updated-token');
|
|
303
|
-
expect(updated.data.refresh_token).toBe('original-refresh');
|
|
304
|
-
expect(updated.data.domain).toBe('original.com');
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
describe('Upsert Operations', () => {
|
|
309
|
-
it('should encrypt on insert path', async () => {
|
|
310
|
-
// Use a valid ObjectId format that doesn't exist in database
|
|
311
|
-
const { ObjectId } = require('mongodb');
|
|
312
|
-
const nonExistentId = new ObjectId().toString();
|
|
313
|
-
|
|
314
|
-
const upserted = await prisma.credential.upsert({
|
|
315
|
-
where: {
|
|
316
|
-
id: nonExistentId,
|
|
317
|
-
},
|
|
318
|
-
create: {
|
|
319
|
-
id: nonExistentId,
|
|
320
|
-
externalId: 'test-encryption-upsert-entity',
|
|
321
|
-
data: {
|
|
322
|
-
access_token: 'upsert-create-token',
|
|
323
|
-
domain: 'upsert-create.com',
|
|
324
|
-
},
|
|
325
|
-
},
|
|
326
|
-
update: {
|
|
327
|
-
data: {
|
|
328
|
-
access_token: 'upsert-update-token',
|
|
329
|
-
domain: 'upsert-update.com',
|
|
330
|
-
},
|
|
331
|
-
},
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
expect(upserted.data.access_token).toBe('upsert-create-token');
|
|
335
|
-
|
|
336
|
-
// Verify encryption in database
|
|
337
|
-
const rawDoc = await repository.getRawCredentialById(upserted.id);
|
|
338
|
-
|
|
339
|
-
expect(rawDoc.data.access_token).not.toBe('upsert-create-token');
|
|
340
|
-
expect(rawDoc.data.access_token).toContain(':');
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
it('should encrypt on update path', async () => {
|
|
344
|
-
// Create first
|
|
345
|
-
const created = await prisma.credential.create({
|
|
346
|
-
data: {
|
|
347
|
-
externalId: 'test-encryption-upsert-update-entity',
|
|
348
|
-
data: {
|
|
349
|
-
access_token: 'original-token',
|
|
350
|
-
domain: 'original.com',
|
|
351
|
-
},
|
|
352
|
-
},
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
// Upsert (should hit update path)
|
|
356
|
-
const upserted = await prisma.credential.upsert({
|
|
357
|
-
where: {
|
|
358
|
-
id: created.id,
|
|
359
|
-
},
|
|
360
|
-
create: {
|
|
361
|
-
externalId: 'test-encryption-upsert-update-entity',
|
|
362
|
-
data: {
|
|
363
|
-
access_token: 'create-path-token',
|
|
364
|
-
domain: 'create.com',
|
|
365
|
-
},
|
|
366
|
-
},
|
|
367
|
-
update: {
|
|
368
|
-
data: {
|
|
369
|
-
access_token: 'update-path-token',
|
|
370
|
-
domain: 'update.com',
|
|
371
|
-
},
|
|
372
|
-
},
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
expect(upserted.data.access_token).toBe('update-path-token');
|
|
376
|
-
|
|
377
|
-
// Verify encryption in database
|
|
378
|
-
const rawDoc = await repository.getRawCredentialById(upserted.id);
|
|
379
|
-
|
|
380
|
-
expect(rawDoc.data.access_token).not.toBe('update-path-token');
|
|
381
|
-
expect(rawDoc.data.access_token).toContain(':');
|
|
382
|
-
});
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
describe('Delete Operations', () => {
|
|
386
|
-
it('should decrypt deleted record', async () => {
|
|
387
|
-
const created = await prisma.credential.create({
|
|
388
|
-
data: {
|
|
389
|
-
externalId: testExternalId,
|
|
390
|
-
data: {
|
|
391
|
-
access_token: 'to-be-deleted',
|
|
392
|
-
domain: 'delete.com',
|
|
393
|
-
},
|
|
394
|
-
},
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
const deleted = await prisma.credential.delete({
|
|
398
|
-
where: { id: created.id },
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
expect(deleted.data.access_token).toBe('to-be-deleted');
|
|
402
|
-
expect(deleted.data.domain).toBe('delete.com');
|
|
403
|
-
});
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
describe('CreateMany Operations', () => {
|
|
407
|
-
it('should encrypt fields in bulk create', async () => {
|
|
408
|
-
const result = await prisma.credential.createMany({
|
|
409
|
-
data: [
|
|
410
|
-
{
|
|
411
|
-
externalId: 'test-encryption-bulk-1',
|
|
412
|
-
data: {
|
|
413
|
-
access_token: 'bulk-secret-1',
|
|
414
|
-
domain: 'bulk1.com',
|
|
415
|
-
},
|
|
416
|
-
},
|
|
417
|
-
{
|
|
418
|
-
externalId: 'test-encryption-bulk-2',
|
|
419
|
-
data: {
|
|
420
|
-
access_token: 'bulk-secret-2',
|
|
421
|
-
domain: 'bulk2.com',
|
|
422
|
-
},
|
|
423
|
-
},
|
|
424
|
-
],
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
expect(result.count).toBe(2);
|
|
428
|
-
|
|
429
|
-
// Verify encryption in database by reading back with Prisma and checking one record's raw form
|
|
430
|
-
const credentials = await prisma.credential.findMany({
|
|
431
|
-
where: { externalId: { startsWith: 'test-encryption-' } },
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
// Check raw database for first credential
|
|
435
|
-
const rawDoc = await repository.getRawCredentialById(credentials[0].id);
|
|
436
|
-
expect(rawDoc.data.access_token).toContain(':');
|
|
437
|
-
expect(rawDoc.data.access_token).not.toMatch(/bulk-secret-/);
|
|
438
|
-
|
|
439
|
-
// Verify decryption when reading
|
|
440
|
-
const tokens = credentials.map((c) => c.data.access_token);
|
|
441
|
-
expect(tokens).toContain('bulk-secret-1');
|
|
442
|
-
expect(tokens).toContain('bulk-secret-2');
|
|
443
|
-
});
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
describe('Non-Encrypted Fields', () => {
|
|
447
|
-
it('should not encrypt fields not in schema registry', async () => {
|
|
448
|
-
const credential = await prisma.credential.create({
|
|
449
|
-
data: {
|
|
450
|
-
externalId: testExternalId,
|
|
451
|
-
data: {
|
|
452
|
-
access_token: 'secret-token',
|
|
453
|
-
domain: 'example.com',
|
|
454
|
-
custom_field: 'should-not-encrypt',
|
|
455
|
-
},
|
|
456
|
-
},
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
// Verify domain is not encrypted (not in schema)
|
|
460
|
-
const rawDoc = await repository.getRawCredentialById(credential.id);
|
|
461
|
-
|
|
462
|
-
expect(rawDoc.data.domain).toBe('example.com');
|
|
463
|
-
expect(rawDoc.data.custom_field).toBe('should-not-encrypt');
|
|
464
|
-
|
|
465
|
-
// access_token should be encrypted (in schema)
|
|
466
|
-
expect(rawDoc.data.access_token).not.toBe('secret-token');
|
|
467
|
-
});
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
describe('Error Handling', () => {
|
|
471
|
-
it('should handle malformed encrypted data gracefully', async () => {
|
|
472
|
-
let created;
|
|
473
|
-
try {
|
|
474
|
-
// Create a credential first to get a valid ID
|
|
475
|
-
created = await prisma.credential.create({
|
|
476
|
-
data: {
|
|
477
|
-
externalId: 'test-encryption-malformed-entity',
|
|
478
|
-
data: {
|
|
479
|
-
access_token: 'valid-token',
|
|
480
|
-
domain: 'malformed.com',
|
|
481
|
-
},
|
|
482
|
-
},
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
// Manually corrupt the encrypted data in the database
|
|
486
|
-
// Use realistic corrupted format: 4 colon-separated parts (passes _isEncrypted check)
|
|
487
|
-
// but contains invalid base64 that will fail during decryption
|
|
488
|
-
const { ObjectId } = require('mongodb');
|
|
489
|
-
const dbType = 'mongodb';
|
|
490
|
-
if (dbType === 'mongodb') {
|
|
491
|
-
const { mongoose } = require('../mongoose');
|
|
492
|
-
// Ensure mongoose is connected
|
|
493
|
-
if (mongoose.connection.readyState !== 1) {
|
|
494
|
-
await mongoose.connect(process.env.DATABASE_URL);
|
|
495
|
-
}
|
|
496
|
-
await mongoose.connection.db.collection('Credential').updateOne(
|
|
497
|
-
{ _id: new ObjectId(created.id) },
|
|
498
|
-
{ $set: { 'data.access_token': 'CORRUPT:INVALID:DATA:FAKE=' } }
|
|
499
|
-
);
|
|
500
|
-
} else {
|
|
501
|
-
// PostgreSQL - use raw query to corrupt data
|
|
502
|
-
await prisma.$executeRaw`
|
|
503
|
-
UPDATE "Credential"
|
|
504
|
-
SET data = jsonb_set(data, '{access_token}', '"CORRUPT:INVALID:DATA:FAKE="')
|
|
505
|
-
WHERE id = ${created.id}
|
|
506
|
-
`;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// Attempt to read should fail with decryption error
|
|
510
|
-
// Fix: Remove async wrapper - expect needs the promise directly for .rejects to work
|
|
511
|
-
await expect(
|
|
512
|
-
prisma.credential.findUnique({
|
|
513
|
-
where: { id: created.id },
|
|
514
|
-
})
|
|
515
|
-
).rejects.toThrow();
|
|
516
|
-
} finally {
|
|
517
|
-
// Cleanup - ensure it runs even if test throws
|
|
518
|
-
// Use raw database delete to bypass Prisma encryption extension
|
|
519
|
-
// (the encrypted data is corrupted so Prisma delete would fail)
|
|
520
|
-
if (created) {
|
|
521
|
-
const { ObjectId } = require('mongodb');
|
|
522
|
-
const { mongoose } = require('../mongoose');
|
|
523
|
-
await mongoose.connection.db.collection('Credential').deleteOne(
|
|
524
|
-
{ _id: new ObjectId(created.id) }
|
|
525
|
-
);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
});
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
describe('Count and Aggregate Operations', () => {
|
|
532
|
-
it('should not interfere with count operations', async () => {
|
|
533
|
-
await prisma.credential.createMany({
|
|
534
|
-
data: [
|
|
535
|
-
{
|
|
536
|
-
externalId: 'test-encryption-count-1',
|
|
537
|
-
data: { access_token: 'token1', domain: 'count1.com' },
|
|
538
|
-
},
|
|
539
|
-
{
|
|
540
|
-
externalId: 'test-encryption-count-2',
|
|
541
|
-
data: { access_token: 'token2', domain: 'count2.com' },
|
|
542
|
-
},
|
|
543
|
-
],
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
const count = await prisma.credential.count({
|
|
547
|
-
where: { externalId: { startsWith: 'test-encryption-' } },
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
expect(count).toBe(2);
|
|
551
|
-
});
|
|
552
|
-
});
|
|
553
|
-
});
|