@friggframework/core 2.0.0--canary.461.bb7fcba.0 → 2.0.0--canary.461.5767fa4.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.
Files changed (118) hide show
  1. package/generated/prisma-mongodb/client.d.ts +1 -0
  2. package/generated/prisma-mongodb/client.js +4 -0
  3. package/generated/prisma-mongodb/default.d.ts +1 -0
  4. package/generated/prisma-mongodb/default.js +4 -0
  5. package/generated/prisma-mongodb/edge.d.ts +1 -0
  6. package/generated/prisma-mongodb/edge.js +336 -0
  7. package/generated/prisma-mongodb/index-browser.js +318 -0
  8. package/generated/prisma-mongodb/index.d.ts +22993 -0
  9. package/generated/prisma-mongodb/index.js +361 -0
  10. package/generated/prisma-mongodb/package.json +183 -0
  11. package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
  12. package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
  13. package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
  14. package/generated/prisma-mongodb/runtime/binary.js +289 -0
  15. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  16. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  17. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  18. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  19. package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
  20. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  21. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  22. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  23. package/generated/prisma-mongodb/schema.prisma +364 -0
  24. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  25. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  26. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  27. package/generated/prisma-mongodb/wasm.js +343 -0
  28. package/generated/prisma-postgresql/client.d.ts +1 -0
  29. package/generated/prisma-postgresql/client.js +4 -0
  30. package/generated/prisma-postgresql/default.d.ts +1 -0
  31. package/generated/prisma-postgresql/default.js +4 -0
  32. package/generated/prisma-postgresql/edge.d.ts +1 -0
  33. package/generated/prisma-postgresql/edge.js +358 -0
  34. package/generated/prisma-postgresql/index-browser.js +340 -0
  35. package/generated/prisma-postgresql/index.d.ts +25171 -0
  36. package/generated/prisma-postgresql/index.js +383 -0
  37. package/generated/prisma-postgresql/package.json +183 -0
  38. package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
  39. package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
  40. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  41. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  42. package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
  43. package/generated/prisma-postgresql/runtime/binary.js +289 -0
  44. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  45. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  46. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  47. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  48. package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
  49. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  50. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  51. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  52. package/generated/prisma-postgresql/schema.prisma +347 -0
  53. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  54. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  55. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  56. package/generated/prisma-postgresql/wasm.js +365 -0
  57. package/package.json +5 -5
  58. package/application/commands/integration-commands.test.js +0 -123
  59. package/core/Worker.test.js +0 -159
  60. package/database/encryption/encryption-integration.test.js +0 -553
  61. package/database/encryption/encryption-schema-registry.test.js +0 -392
  62. package/database/encryption/field-encryption-service.test.js +0 -525
  63. package/database/encryption/mongo-decryption-fix-verification.test.js +0 -348
  64. package/database/encryption/postgres-decryption-fix-verification.test.js +0 -371
  65. package/database/encryption/postgres-relation-decryption.test.js +0 -245
  66. package/database/encryption/prisma-encryption-extension.test.js +0 -439
  67. package/database/repositories/migration-status-repository-s3.test.js +0 -158
  68. package/database/use-cases/check-encryption-health-use-case.test.js +0 -192
  69. package/database/use-cases/get-migration-status-use-case.test.js +0 -171
  70. package/database/use-cases/run-database-migration-use-case.test.js +0 -310
  71. package/database/use-cases/trigger-database-migration-use-case.test.js +0 -250
  72. package/database/utils/prisma-runner.test.js +0 -486
  73. package/encrypt/Cryptor.test.js +0 -144
  74. package/errors/base-error.test.js +0 -32
  75. package/errors/fetch-error.test.js +0 -79
  76. package/errors/halt-error.test.js +0 -11
  77. package/errors/validation-errors.test.js +0 -120
  78. package/handlers/auth-flow.integration.test.js +0 -147
  79. package/handlers/integration-event-dispatcher.test.js +0 -209
  80. package/handlers/routers/db-migration.test.js +0 -51
  81. package/handlers/routers/health.test.js +0 -210
  82. package/handlers/routers/integration-webhook-routers.test.js +0 -126
  83. package/handlers/use-cases/check-integrations-health-use-case.test.js +0 -125
  84. package/handlers/webhook-flow.integration.test.js +0 -356
  85. package/handlers/workers/db-migration.test.js +0 -50
  86. package/handlers/workers/integration-defined-workers.test.js +0 -184
  87. package/integrations/tests/integration-router-multi-auth.test.js +0 -369
  88. package/integrations/tests/use-cases/create-integration.test.js +0 -131
  89. package/integrations/tests/use-cases/delete-integration-for-user.test.js +0 -150
  90. package/integrations/tests/use-cases/find-integration-context-by-external-entity-id.test.js +0 -92
  91. package/integrations/tests/use-cases/get-integration-for-user.test.js +0 -150
  92. package/integrations/tests/use-cases/get-integration-instance.test.js +0 -176
  93. package/integrations/tests/use-cases/get-integrations-for-user.test.js +0 -176
  94. package/integrations/tests/use-cases/get-possible-integrations.test.js +0 -188
  95. package/integrations/tests/use-cases/update-integration-messages.test.js +0 -142
  96. package/integrations/tests/use-cases/update-integration-status.test.js +0 -103
  97. package/integrations/tests/use-cases/update-integration.test.js +0 -141
  98. package/integrations/use-cases/create-process.test.js +0 -178
  99. package/integrations/use-cases/get-process.test.js +0 -190
  100. package/integrations/use-cases/load-integration-context-full.test.js +0 -329
  101. package/integrations/use-cases/load-integration-context.test.js +0 -114
  102. package/integrations/use-cases/update-process-metrics.test.js +0 -308
  103. package/integrations/use-cases/update-process-state.test.js +0 -256
  104. package/lambda/TimeoutCatcher.test.js +0 -68
  105. package/logs/logger.test.js +0 -76
  106. package/modules/module-hydration.test.js +0 -205
  107. package/modules/requester/requester.test.js +0 -28
  108. package/queues/queuer-util.test.js +0 -132
  109. package/user/tests/use-cases/create-individual-user.test.js +0 -24
  110. package/user/tests/use-cases/create-organization-user.test.js +0 -28
  111. package/user/tests/use-cases/create-token-for-user-id.test.js +0 -19
  112. package/user/tests/use-cases/get-user-from-adopter-jwt.test.js +0 -113
  113. package/user/tests/use-cases/get-user-from-bearer-token.test.js +0 -64
  114. package/user/tests/use-cases/get-user-from-x-frigg-headers.test.js +0 -346
  115. package/user/tests/use-cases/login-user.test.js +0 -220
  116. package/user/tests/user-password-encryption-isolation.test.js +0 -237
  117. package/user/tests/user-password-hashing.test.js +0 -235
  118. package/websocket/repositories/websocket-connection-repository.test.js +0 -227
@@ -1,220 +0,0 @@
1
- const bcrypt = require('bcryptjs');
2
- const { LoginUser } = require('../../use-cases/login-user');
3
- const { TestUserRepository } = require('../doubles/test-user-repository');
4
-
5
- jest.mock('bcryptjs', () => ({
6
- compare: jest.fn(),
7
- }));
8
-
9
- describe('LoginUser Use Case', () => {
10
- let userRepository;
11
- let loginUser;
12
- let userConfig;
13
-
14
- beforeEach(() => {
15
- userConfig = { usePassword: true, individualUserRequired: true, organizationUserRequired: false };
16
- userRepository = new TestUserRepository({ userConfig });
17
- loginUser = new LoginUser({ userRepository, userConfig });
18
-
19
- bcrypt.compare.mockClear();
20
- });
21
-
22
- describe('With Password Authentication', () => {
23
- it('should successfully log in a user with correct credentials', async () => {
24
- const username = 'test-user';
25
- const password = 'password123';
26
- await userRepository.createIndividualUser({
27
- username,
28
- hashword: 'hashed-password',
29
- });
30
-
31
- bcrypt.compare.mockResolvedValue(true);
32
-
33
- const user = await loginUser.execute({ username, password });
34
-
35
- expect(bcrypt.compare).toHaveBeenCalledWith(
36
- password,
37
- 'hashed-password'
38
- );
39
- expect(user).toBeDefined();
40
- expect(user.getIndividualUser().username).toBe(username);
41
- });
42
-
43
- it('should throw an unauthorized error for an incorrect password', async () => {
44
- const username = 'test-user';
45
- const password = 'wrong-password';
46
- await userRepository.createIndividualUser({
47
- username,
48
- hashword: 'hashed-password',
49
- });
50
-
51
- bcrypt.compare.mockResolvedValue(false);
52
-
53
- await expect(
54
- loginUser.execute({ username, password })
55
- ).rejects.toThrow('Incorrect username or password');
56
- });
57
-
58
- it('should throw an unauthorized error for a non-existent user', async () => {
59
- const username = 'non-existent-user';
60
- const password = 'password123';
61
-
62
- await expect(
63
- loginUser.execute({ username, password })
64
- ).rejects.toThrow('user not found');
65
- });
66
- });
67
-
68
- describe('Without Password (appUserId)', () => {
69
- beforeEach(() => {
70
- userConfig = { usePassword: false, individualUserRequired: true, organizationUserRequired: false };
71
- userRepository = new TestUserRepository({ userConfig });
72
- loginUser = new LoginUser({
73
- userRepository,
74
- userConfig,
75
- });
76
- });
77
-
78
- it('should successfully retrieve a user by appUserId', async () => {
79
- const appUserId = 'app-user-123';
80
- const createdUserData = await userRepository.createIndividualUser({
81
- appUserId,
82
- });
83
-
84
- const result = await loginUser.execute({ appUserId });
85
- expect(result.getId()).toBe(createdUserData.id);
86
- });
87
- });
88
-
89
- describe('With Organization User', () => {
90
- beforeEach(() => {
91
- userConfig = {
92
- primary: 'organization',
93
- individualUserRequired: false,
94
- organizationUserRequired: true,
95
- };
96
- userRepository = new TestUserRepository({ userConfig });
97
- loginUser = new LoginUser({
98
- userRepository,
99
- userConfig,
100
- });
101
- });
102
-
103
- it('should successfully retrieve an organization user by appOrgId', async () => {
104
- const appOrgId = 'app-org-123';
105
- const createdUserData = await userRepository.createOrganizationUser({
106
- name: 'Test Org',
107
- appOrgId,
108
- });
109
-
110
- const result = await loginUser.execute({ appOrgId });
111
- expect(result.getId()).toBe(createdUserData.id);
112
- });
113
-
114
- it('should throw an unauthorized error for a non-existent organization user', async () => {
115
- const appOrgId = 'non-existent-org';
116
-
117
- await expect(loginUser.execute({ appOrgId })).rejects.toThrow(
118
- 'org user non-existent-org not found'
119
- );
120
- });
121
- });
122
-
123
- describe('Required User Checks', () => {
124
- it('should throw an error if a required individual user is not found', async () => {
125
- userConfig = {
126
- individualUserRequired: true,
127
- usePassword: false,
128
- };
129
- userRepository = new TestUserRepository({ userConfig });
130
- loginUser = new LoginUser({
131
- userRepository,
132
- userConfig,
133
- });
134
-
135
- await expect(
136
- loginUser.execute({ appUserId: 'a-non-existent-user-id' })
137
- ).rejects.toThrow('user not found');
138
- });
139
- });
140
-
141
- describe('Bcrypt Hash Verification', () => {
142
- beforeEach(() => {
143
- userConfig = { usePassword: true, individualUserRequired: true, organizationUserRequired: false };
144
- userRepository = new TestUserRepository({ userConfig });
145
- loginUser = new LoginUser({ userRepository, userConfig });
146
- });
147
-
148
- it('should verify bcrypt.compare is called with plain password and hash', async () => {
149
- const username = 'bcrypt-test-user';
150
- const plainPassword = 'MyPlainPassword123';
151
- const bcryptHash = '$2b$10$abcdefghijklmnopqrstuv';
152
-
153
- await userRepository.createIndividualUser({
154
- username,
155
- hashword: bcryptHash,
156
- });
157
-
158
- bcrypt.compare.mockResolvedValue(true);
159
-
160
- await loginUser.execute({ username, password: plainPassword });
161
-
162
- expect(bcrypt.compare).toHaveBeenCalledTimes(1);
163
- expect(bcrypt.compare).toHaveBeenCalledWith(plainPassword, bcryptHash);
164
-
165
- const [firstArg, secondArg] = bcrypt.compare.mock.calls[0];
166
- expect(firstArg).toBe(plainPassword);
167
- expect(secondArg).toBe(bcryptHash);
168
- });
169
-
170
- it('should verify stored password has bcrypt hash format', async () => {
171
- const username = 'format-test-user';
172
- const bcryptHash = '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy';
173
-
174
- await userRepository.createIndividualUser({
175
- username,
176
- hashword: bcryptHash,
177
- });
178
-
179
- const user = await userRepository.findIndividualUserByUsername(username);
180
-
181
- expect(user.hashword).toMatch(/^\$2[ab]\$/);
182
- expect(user.hashword.length).toBeGreaterThan(50);
183
- expect(user.hashword).not.toContain(':');
184
- });
185
-
186
- it('should reject passwords that look encrypted (have colon separators)', async () => {
187
- const username = 'encrypted-format-user';
188
- const encryptedLookingValue = 'kms:us-east-1:key:ciphertext';
189
-
190
- await userRepository.createIndividualUser({
191
- username,
192
- hashword: encryptedLookingValue,
193
- });
194
-
195
- bcrypt.compare.mockResolvedValue(false);
196
-
197
- await expect(
198
- loginUser.execute({ username, password: 'any-password' })
199
- ).rejects.toThrow('Incorrect username or password');
200
- });
201
-
202
- it('should verify bcrypt.compare returns false for mismatched passwords', async () => {
203
- const username = 'mismatch-test-user';
204
- const correctHash = '$2b$10$correcthash';
205
-
206
- await userRepository.createIndividualUser({
207
- username,
208
- hashword: correctHash,
209
- });
210
-
211
- bcrypt.compare.mockResolvedValue(false);
212
-
213
- await expect(
214
- loginUser.execute({ username, password: 'wrong-password' })
215
- ).rejects.toThrow('Incorrect username or password');
216
-
217
- expect(bcrypt.compare).toHaveBeenCalledWith('wrong-password', correctHash);
218
- });
219
- });
220
- });
@@ -1,237 +0,0 @@
1
- /**
2
- * Password Encryption Isolation Test
3
- *
4
- * Verifies that password hashing is completely isolated from the encryption system.
5
- * Tests that passwords are bcrypt hashed regardless of encryption configuration.
6
- *
7
- * Key Tests:
8
- * - With encryption ENABLED: passwords hashed (not encrypted)
9
- * - With encryption DISABLED: passwords still hashed
10
- * - Encryption schema does NOT include User.hashword
11
- * - Side-by-side: tokens encrypted, passwords hashed
12
- */
13
-
14
- // Set default DATABASE_URL for testing if not already set
15
- if (!process.env.DATABASE_URL) {
16
- process.env.DATABASE_URL = 'mongodb://localhost:27017/frigg?replicaSet=rs0';
17
- }
18
-
19
- // Enable encryption for testing (bypass test stage check)
20
- process.env.STAGE = 'integration-test';
21
- process.env.AES_KEY_ID = 'test-key-id';
22
- process.env.AES_KEY = 'test-aes-key-32-characters-long!';
23
-
24
- jest.mock('../../database/config', () => ({
25
- DB_TYPE: 'mongodb',
26
- getDatabaseType: jest.fn(() => 'mongodb'),
27
- PRISMA_LOG_LEVEL: 'error,warn',
28
- PRISMA_QUERY_LOGGING: false,
29
- }));
30
-
31
- const bcrypt = require('bcryptjs');
32
- const { createUserRepository } = require('../repositories/user-repository-factory');
33
- const { prisma, connectPrisma, disconnectPrisma, getEncryptionConfig } = require('../../database/prisma');
34
- const { getEncryptedFields, hasEncryptedFields } = require('../../database/encryption/encryption-schema-registry');
35
- const { mongoose } = require('../../database/mongoose');
36
-
37
- describe('Password Encryption Isolation', () => {
38
- const dbType = process.env.DB_TYPE || 'mongodb';
39
- let userRepository;
40
- let testUserIds = [];
41
- const TEST_PASSWORD = 'IsolationTestPassword123!';
42
-
43
- beforeAll(async () => {
44
- await connectPrisma();
45
- // Connect mongoose for raw database queries
46
- if (mongoose.connection.readyState === 0) {
47
- await mongoose.connect(process.env.DATABASE_URL);
48
- }
49
- userRepository = createUserRepository();
50
- }, 30000); // 30 second timeout for database connection
51
-
52
- afterAll(async () => {
53
- for (const userId of testUserIds) {
54
- await userRepository.deleteUser(userId).catch(() => {});
55
- }
56
- await mongoose.disconnect();
57
- await disconnectPrisma();
58
- }, 30000); // 30 second timeout for cleanup
59
-
60
- test('✅ Encryption schema does NOT include User.hashword', () => {
61
- const userEncryptedFields = getEncryptedFields('User');
62
-
63
- console.log('\n📋 User model encrypted fields:', userEncryptedFields);
64
-
65
- expect(userEncryptedFields).toBeDefined();
66
- expect(Array.isArray(userEncryptedFields)).toBe(true);
67
- expect(userEncryptedFields).not.toContain('hashword');
68
-
69
- if (userEncryptedFields.length > 0) {
70
- console.log('⚠️ WARNING: User model has encrypted fields:', userEncryptedFields);
71
- console.log(' Password field (hashword) should NOT be in this list');
72
- } else {
73
- console.log('✅ User model has no encrypted fields (as expected)');
74
- }
75
- });
76
-
77
- test('✅ Password is bcrypt hashed regardless of encryption config', async () => {
78
- const encryptionConfig = getEncryptionConfig();
79
- console.log('\n🔒 Current encryption config:', encryptionConfig);
80
-
81
- const username = `isolation-test-${Date.now()}`;
82
- const user = await userRepository.createIndividualUser({
83
- username,
84
- hashword: TEST_PASSWORD,
85
- email: `${username}@test.com`,
86
- });
87
- testUserIds.push(user.id);
88
-
89
- expect(user.hashword).toMatch(/^\$2[ab]\$\d{2}\$/);
90
- expect(user.hashword).not.toBe(TEST_PASSWORD);
91
- expect(user.hashword).not.toContain(':');
92
-
93
- const isValid = await bcrypt.compare(TEST_PASSWORD, user.hashword);
94
- expect(isValid).toBe(true);
95
-
96
- console.log('✅ Password correctly hashed with bcrypt');
97
- console.log(' Encryption enabled:', encryptionConfig.enabled);
98
- console.log(' Hashword format:', user.hashword.substring(0, 20) + '...');
99
- });
100
-
101
- test('📊 Field-level encryption status comparison', async () => {
102
- const models = ['User', 'Credential', 'Token', 'IntegrationMapping'];
103
-
104
- console.log('\n📊 ENCRYPTION SCHEMA ANALYSIS:');
105
- console.log('='.repeat(60));
106
-
107
- for (const model of models) {
108
- const fields = getEncryptedFields(model);
109
- const hasEncryption = hasEncryptedFields(model);
110
-
111
- console.log(`\n${model}:`);
112
- console.log(` Has encrypted fields: ${hasEncryption}`);
113
- console.log(` Encrypted fields: ${fields.length > 0 ? fields.join(', ') : 'none'}`);
114
-
115
- if (model === 'User') {
116
- expect(fields).not.toContain('hashword');
117
- console.log(' ✅ Password (hashword) correctly excluded from encryption');
118
- } else if (model === 'Credential') {
119
- expect(fields).toContain('data.access_token');
120
- console.log(' ✅ API tokens correctly included in encryption');
121
- }
122
- }
123
- });
124
-
125
- test('📊 End-to-end: Create user + credential, verify isolation', async () => {
126
- const username = `e2e-isolation-${Date.now()}`;
127
- const secretToken = 'my-secret-api-token-xyz';
128
-
129
- const user = await userRepository.createIndividualUser({
130
- username,
131
- hashword: TEST_PASSWORD,
132
- email: `${username}@test.com`,
133
- });
134
- testUserIds.push(user.id);
135
-
136
- const credential = await prisma.credential.create({
137
- data: {
138
- userId: dbType === 'postgresql' ? parseInt(user.id, 10) : user.id,
139
- externalId: `cred-${Date.now()}`,
140
- data: {
141
- access_token: secretToken,
142
- },
143
- },
144
- });
145
-
146
- console.log('\n📊 END-TO-END ISOLATION TEST:');
147
- console.log('='.repeat(60));
148
-
149
- const fetchedUser = await userRepository.findIndividualUserById(user.id);
150
- console.log('\n👤 User Password:');
151
- console.log(' Format:', fetchedUser.hashword.substring(0, 30) + '...');
152
- console.log(' Is bcrypt:', /^\$2[ab]\$\d{2}\$/.test(fetchedUser.hashword));
153
- console.log(' Is encrypted (has :):', fetchedUser.hashword.includes(':'));
154
-
155
- const fetchedCred = await prisma.credential.findUnique({
156
- where: { id: credential.id },
157
- });
158
-
159
- console.log('\n🔑 Credential Token:');
160
- const tokenValue = fetchedCred.data.access_token;
161
- console.log(' Raw value:', tokenValue.substring(0, 50) + '...');
162
- console.log(' Is encrypted (has :):', tokenValue.includes(':'));
163
- console.log(' Equals plain text:', tokenValue === secretToken);
164
-
165
- expect(fetchedUser.hashword).toMatch(/^\$2[ab]\$\d{2}\$/);
166
- expect(fetchedUser.hashword).not.toContain(':');
167
-
168
- const isPasswordValid = await bcrypt.compare(TEST_PASSWORD, fetchedUser.hashword);
169
- expect(isPasswordValid).toBe(true);
170
-
171
- console.log('\n✅ Password: bcrypt hashed (NOT encrypted)');
172
-
173
- const encryptionEnabled = tokenValue !== secretToken;
174
- if (encryptionEnabled) {
175
- console.log('✅ Credential: properly encrypted');
176
- expect(tokenValue).not.toBe(secretToken);
177
- } else {
178
- console.log('⚠️ Encryption disabled in this environment');
179
- }
180
-
181
- console.log('✅ ISOLATION VERIFIED: Passwords use bcrypt, credentials use encryption');
182
-
183
- await prisma.credential.delete({ where: { id: credential.id } });
184
- });
185
-
186
- test('🔍 Bcrypt vs Encryption format analysis', () => {
187
- const bcryptHash = '$2b$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy';
188
- const encryptedValue = 'kms:us-east-1:alias/app-key:AQICAHg...base64...';
189
-
190
- console.log('\n🔍 FORMAT COMPARISON:');
191
- console.log('='.repeat(60));
192
-
193
- console.log('\nBcrypt Hash Format:');
194
- console.log(' Example:', bcryptHash);
195
- console.log(' Pattern: $2[ab]$rounds$salt+hash');
196
- console.log(' Length: ~60 chars');
197
- console.log(' Colon count:', (bcryptHash.match(/:/g) || []).length);
198
- console.log(' Dollar signs: 3');
199
-
200
- console.log('\nEncryption Format:');
201
- console.log(' Example:', encryptedValue.substring(0, 50) + '...');
202
- console.log(' Pattern: method:region:keyId:base64Ciphertext');
203
- console.log(' Colon separators: 3');
204
- console.log(' Variable length');
205
-
206
- console.log('\n✅ Formats are clearly distinguishable');
207
- console.log('✅ Bcrypt never has colon separators between dollar signs');
208
- console.log('✅ Encryption always has exactly 3 colon separators');
209
- });
210
-
211
- test('⚠️ Verify password NOT double-processed', async () => {
212
- const username = `double-process-test-${Date.now()}`;
213
-
214
- const user = await userRepository.createIndividualUser({
215
- username,
216
- hashword: TEST_PASSWORD,
217
- email: `${username}@test.com`,
218
- });
219
- testUserIds.push(user.id);
220
-
221
- const hash1 = user.hashword;
222
-
223
- const fetchedUser = await userRepository.findIndividualUserById(user.id);
224
- const hash2 = fetchedUser.hashword;
225
-
226
- console.log('\n⚠️ DOUBLE-PROCESSING CHECK:');
227
- console.log('Hash after creation:', hash1.substring(0, 30) + '...');
228
- console.log('Hash after fetch: ', hash2.substring(0, 30) + '...');
229
- console.log('Hashes match:', hash1 === hash2);
230
-
231
- expect(hash1).toBe(hash2);
232
- expect(hash1).toMatch(/^\$2[ab]\$\d{2}\$/);
233
- expect(hash2).toMatch(/^\$2[ab]\$\d{2}\$/);
234
-
235
- console.log('✅ No double-processing detected');
236
- });
237
- });
@@ -1,235 +0,0 @@
1
- /**
2
- * Password Hashing Verification Test
3
- *
4
- * Verifies that passwords are correctly bcrypt hashed (NOT encrypted) throughout
5
- * the user authentication flow. Tests both MongoDB and PostgreSQL.
6
- *
7
- * Expected Behavior:
8
- * - Passwords hashed with bcrypt on creation (format: $2a$ or $2b$)
9
- * - Password hashes stored as-is (NOT encrypted with KMS/AES)
10
- * - bcrypt.compare() works correctly for authentication
11
- * - Password updates also trigger bcrypt hashing
12
- */
13
-
14
- // Set default DATABASE_URL for testing if not already set
15
- if (!process.env.DATABASE_URL) {
16
- process.env.DATABASE_URL = 'mongodb://localhost:27017/frigg?replicaSet=rs0';
17
- }
18
-
19
- // Enable encryption for testing (bypass test stage check)
20
- process.env.STAGE = 'integration-test';
21
- process.env.AES_KEY_ID = 'test-key-id';
22
- process.env.AES_KEY = 'test-aes-key-32-characters-long!';
23
-
24
- jest.mock('../../database/config', () => ({
25
- DB_TYPE: 'mongodb',
26
- getDatabaseType: jest.fn(() => 'mongodb'),
27
- PRISMA_LOG_LEVEL: 'error,warn',
28
- PRISMA_QUERY_LOGGING: false,
29
- }));
30
-
31
- const bcrypt = require('bcryptjs');
32
- const { LoginUser } = require('../use-cases/login-user');
33
- const { createUserRepository } = require('../repositories/user-repository-factory');
34
- const { prisma, connectPrisma, disconnectPrisma } = require('../../database/prisma');
35
- const { mongoose } = require('../../database/mongoose');
36
-
37
- describe('Password Hashing Verification - Both Databases', () => {
38
- const dbType = process.env.DB_TYPE || 'mongodb';
39
- let userRepository;
40
- let testUserId;
41
- const TEST_PASSWORD = 'MySecurePassword123!';
42
- const TEST_USERNAME = `test-user-hash-${Date.now()}`;
43
- const userConfig = {
44
- usePassword: true,
45
- individualUserRequired: true,
46
- organizationUserRequired: false,
47
- primary: 'individual',
48
- };
49
-
50
- beforeAll(async () => {
51
- await connectPrisma();
52
- // Connect mongoose for raw database queries
53
- if (mongoose.connection.readyState === 0) {
54
- await mongoose.connect(process.env.DATABASE_URL);
55
- }
56
- userRepository = createUserRepository();
57
- }, 30000); // 30 second timeout for database connection
58
-
59
- afterAll(async () => {
60
- if (testUserId) {
61
- await userRepository.deleteUser(testUserId).catch(() => {});
62
- }
63
- await mongoose.disconnect();
64
- await disconnectPrisma();
65
- }, 30000); // 30 second timeout for cleanup
66
-
67
- describe(`${dbType.toUpperCase()} - Password Hashing`, () => {
68
- test('✅ Password is bcrypt hashed on user creation', async () => {
69
- const user = await userRepository.createIndividualUser({
70
- username: TEST_USERNAME,
71
- hashword: TEST_PASSWORD,
72
- email: `${TEST_USERNAME}@test.com`,
73
- });
74
- testUserId = user.id;
75
-
76
- expect(user.hashword).toBeDefined();
77
- expect(user.hashword).not.toBe(TEST_PASSWORD);
78
- expect(user.hashword).toMatch(/^\$2[ab]\$\d{2}\$/);
79
- expect(user.hashword.length).toBeGreaterThan(50);
80
- expect(user.hashword).not.toContain(':');
81
-
82
- console.log('✅ Password hashed correctly:', user.hashword.substring(0, 20) + '...');
83
- });
84
-
85
- test('✅ Stored hashword is bcrypt format, NOT encrypted', async () => {
86
- const user = await userRepository.findIndividualUserByUsername(TEST_USERNAME);
87
-
88
- expect(user.hashword).toMatch(/^\$2[ab]\$\d{2}\$/);
89
- expect(user.hashword).not.toContain(':');
90
- expect(user.hashword.split(':')).toHaveLength(1);
91
-
92
- console.log('✅ Stored password has bcrypt format (not encrypted)');
93
- });
94
-
95
- test('✅ bcrypt.compare() verifies correct password', async () => {
96
- const user = await userRepository.findIndividualUserByUsername(TEST_USERNAME);
97
- const isValid = await bcrypt.compare(TEST_PASSWORD, user.hashword);
98
-
99
- expect(isValid).toBe(true);
100
- console.log('✅ bcrypt.compare() successfully verified password');
101
- });
102
-
103
- test('✅ bcrypt.compare() rejects incorrect password', async () => {
104
- const user = await userRepository.findIndividualUserByUsername(TEST_USERNAME);
105
- const isValid = await bcrypt.compare('WrongPassword', user.hashword);
106
-
107
- expect(isValid).toBe(false);
108
- console.log('✅ bcrypt.compare() correctly rejected wrong password');
109
- });
110
-
111
- test('✅ Login succeeds with correct password', async () => {
112
- const loginUser = new LoginUser({ userRepository, userConfig });
113
- const user = await loginUser.execute({
114
- username: TEST_USERNAME,
115
- password: TEST_PASSWORD,
116
- });
117
-
118
- expect(user).toBeDefined();
119
- expect(user.getId()).toBe(testUserId);
120
- console.log('✅ Login successful with correct password');
121
- });
122
-
123
- test('✅ Login fails with incorrect password', async () => {
124
- const loginUser = new LoginUser({ userRepository, userConfig });
125
-
126
- await expect(
127
- loginUser.execute({
128
- username: TEST_USERNAME,
129
- password: 'WrongPassword123',
130
- })
131
- ).rejects.toThrow('Incorrect username or password');
132
-
133
- console.log('✅ Login correctly rejected incorrect password');
134
- });
135
-
136
- test('✅ Password update also hashes the new password', async () => {
137
- const newPassword = 'NewSecurePassword456!';
138
-
139
- const updatedUser = await userRepository.updateIndividualUser(testUserId, {
140
- hashword: newPassword,
141
- });
142
-
143
- expect(updatedUser.hashword).not.toBe(newPassword);
144
- expect(updatedUser.hashword).toMatch(/^\$2[ab]\$\d{2}\$/);
145
- expect(updatedUser.hashword).not.toContain(':');
146
-
147
- const isNewPasswordValid = await bcrypt.compare(newPassword, updatedUser.hashword);
148
- expect(isNewPasswordValid).toBe(true);
149
-
150
- const isOldPasswordValid = await bcrypt.compare(TEST_PASSWORD, updatedUser.hashword);
151
- expect(isOldPasswordValid).toBe(false);
152
-
153
- console.log('✅ Password update correctly hashed new password');
154
- });
155
-
156
- test('📊 Raw database check: bcrypt hash stored directly', async () => {
157
- let rawUser;
158
- if (dbType === 'postgresql') {
159
- const userId = parseInt(testUserId, 10);
160
- rawUser = await prisma.$queryRaw`
161
- SELECT hashword FROM "User" WHERE id = ${userId}
162
- `;
163
- rawUser = rawUser[0];
164
- } else {
165
- rawUser = await prisma.$queryRawUnsafe(
166
- `db.User.findOne({ _id: ObjectId("${testUserId}") })`
167
- ).catch(() => {
168
- return userRepository.findIndividualUserById(testUserId);
169
- });
170
- }
171
-
172
- console.log('\n📊 RAW DATABASE HASHWORD:');
173
- console.log('Format:', rawUser.hashword.substring(0, 30) + '...');
174
- console.log('Length:', rawUser.hashword.length);
175
-
176
- expect(rawUser.hashword).toMatch(/^\$2[ab]\$\d{2}\$/);
177
- expect(rawUser.hashword).not.toContain(':');
178
-
179
- console.log('✅ Raw database stores bcrypt hash (not encrypted)');
180
- });
181
- });
182
-
183
- describe(`${dbType.toUpperCase()} - Encryption Isolation`, () => {
184
- test('📊 COMPARISON: Credential tokens encrypted, passwords hashed', async () => {
185
- const credential = await prisma.credential.create({
186
- data: {
187
- userId: dbType === 'postgresql' ? parseInt(testUserId, 10) : testUserId,
188
- externalId: `test-cred-${Date.now()}`,
189
- data: {
190
- access_token: 'secret-access-token-12345',
191
- refresh_token: 'secret-refresh-token-67890',
192
- },
193
- },
194
- });
195
-
196
- const user = await userRepository.findIndividualUserById(testUserId);
197
-
198
- let rawCred;
199
- if (dbType === 'postgresql') {
200
- rawCred = await prisma.$queryRaw`
201
- SELECT data FROM "Credential" WHERE id = ${credential.id}
202
- `;
203
- rawCred = rawCred[0];
204
- } else {
205
- rawCred = await prisma.credential.findUnique({
206
- where: { id: credential.id },
207
- });
208
- }
209
-
210
- console.log('\n📊 ENCRYPTION COMPARISON:');
211
- console.log('Credential token (should be encrypted):');
212
- console.log(' Format:', rawCred.data.access_token.substring(0, 50) + '...');
213
- console.log(' Has ":" separators:', rawCred.data.access_token.includes(':'));
214
- console.log('\nUser password (should be bcrypt hashed):');
215
- console.log(' Format:', user.hashword.substring(0, 30) + '...');
216
- console.log(' Has ":" separators:', user.hashword.includes(':'));
217
-
218
- const encryptionEnabled = rawCred.data.access_token !== 'secret-access-token-12345';
219
-
220
- if (encryptionEnabled) {
221
- expect(rawCred.data.access_token).toContain(':');
222
- expect(rawCred.data.access_token.split(':')).toHaveLength(4);
223
- console.log('✅ Credential token is encrypted');
224
- } else {
225
- console.log('⚠️ Encryption disabled in this environment');
226
- }
227
-
228
- expect(user.hashword).toMatch(/^\$2[ab]\$\d{2}\$/);
229
- expect(user.hashword).not.toContain(':');
230
- console.log('✅ Password is bcrypt hashed (NOT encrypted)');
231
-
232
- await prisma.credential.delete({ where: { id: credential.id } });
233
- });
234
- });
235
- });