@friggframework/core 2.0.0--canary.503.d72d514.0 → 2.0.0--canary.503.ce72072.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.
@@ -10,7 +10,9 @@ const {
10
10
  const {
11
11
  CredentialRepositoryInterface,
12
12
  } = require('./credential-repository-interface');
13
- const { DocumentDBEncryptionService } = require('../../database/documentdb-encryption-service');
13
+ const {
14
+ DocumentDBEncryptionService,
15
+ } = require('../../database/documentdb-encryption-service');
14
16
 
15
17
  /**
16
18
  * Credential repository for DocumentDB.
@@ -39,8 +41,10 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
39
41
  const doc = await findOne(this.prisma, 'Credential', { _id: objectId });
40
42
  if (!doc) return null;
41
43
 
42
- // Decrypt sensitive fields using service
43
- const decryptedCredential = await this.encryptionService.decryptFields('Credential', doc);
44
+ const decryptedCredential = await this.encryptionService.decryptFields(
45
+ 'Credential',
46
+ doc
47
+ );
44
48
  return this._mapCredentialById(decryptedCredential);
45
49
  }
46
50
 
@@ -62,16 +66,19 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
62
66
  async deleteCredentialById(credentialId) {
63
67
  const objectId = toObjectId(credentialId);
64
68
  if (!objectId) return { acknowledged: true, deletedCount: 0 };
65
- const result = await deleteOne(this.prisma, 'Credential', { _id: objectId });
69
+ const result = await deleteOne(this.prisma, 'Credential', {
70
+ _id: objectId,
71
+ });
66
72
  const deleted = result?.n ?? 0;
67
73
  return { acknowledged: true, deletedCount: deleted };
68
74
  }
69
75
 
70
76
  async upsertCredential(credentialDetails) {
71
77
  const { identifiers, details } = credentialDetails;
72
- if (!identifiers) throw new Error('identifiers required to upsert credential');
73
- if (!identifiers.user && !identifiers.userId) {
74
- throw new Error('user or userId required in identifiers');
78
+ if (!identifiers)
79
+ throw new Error('identifiers required to upsert credential');
80
+ if (!identifiers.userId) {
81
+ throw new Error('userId required in identifiers');
75
82
  }
76
83
  if (!identifiers.externalId) {
77
84
  throw new Error(
@@ -81,32 +88,29 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
81
88
 
82
89
  const filter = this._buildIdentifierFilter(identifiers);
83
90
  const existing = await findOne(this.prisma, 'Credential', filter);
84
-
85
- const {
86
- user,
87
- userId,
88
- authIsValid,
89
- externalId,
90
- ...oauthData
91
- } = details || {};
92
-
93
91
  const now = new Date();
94
92
 
93
+ const { authIsValid, ...oauthData } = details || {};
94
+
95
95
  if (existing) {
96
- // Decrypt existing credential data first
97
- const decryptedExisting = await this.encryptionService.decryptFields('Credential', existing);
98
- const mergedData = { ...(decryptedExisting.data || {}), ...oauthData };
96
+ const decryptedExisting =
97
+ await this.encryptionService.decryptFields(
98
+ 'Credential',
99
+ existing
100
+ );
101
+ const mergedData = {
102
+ ...(decryptedExisting.data || {}),
103
+ ...oauthData,
104
+ };
99
105
 
100
- // Build update document
101
106
  const updateDocument = {
102
- userId: toObjectId(userId || user) || existing.userId || null,
103
- externalId: externalId !== undefined ? externalId : existing.externalId,
104
- authIsValid: authIsValid !== undefined ? authIsValid : existing.authIsValid,
107
+ userId: existing.userId,
108
+ externalId: existing.externalId,
109
+ authIsValid: authIsValid,
105
110
  data: mergedData,
106
111
  updatedAt: now,
107
112
  };
108
113
 
109
- // Encrypt before storing
110
114
  const encryptedUpdate = await this.encryptionService.encryptFields(
111
115
  'Credential',
112
116
  { data: updateDocument.data }
@@ -127,53 +131,44 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
127
131
  }
128
132
  );
129
133
 
130
- // Read back and decrypt
131
- const updated = await findOne(this.prisma, 'Credential', { _id: existing._id });
132
- if (!updated) {
133
- console.error('[CredentialRepositoryDocumentDB] Credential not found after update', {
134
- credentialId: fromObjectId(existing._id),
135
- identifiers,
136
- });
137
- throw new Error(
138
- 'Failed to update credential: Document not found after update. ' +
139
- 'This indicates a database consistency issue.'
134
+ const updated = await findOne(this.prisma, 'Credential', {
135
+ _id: existing._id,
136
+ });
137
+ const decryptedCredential =
138
+ await this.encryptionService.decryptFields(
139
+ 'Credential',
140
+ updated
140
141
  );
141
- }
142
- const decryptedCredential = await this.encryptionService.decryptFields('Credential', updated);
143
142
  return this._mapCredential(decryptedCredential);
144
143
  }
145
144
 
146
- // Build plain text document
147
145
  const plainDocument = {
148
- userId: toObjectId(userId || user || identifiers.user),
149
- externalId: externalId !== undefined ? externalId : identifiers.externalId,
150
- authIsValid: authIsValid ?? null,
151
- data: oauthData,
146
+ userId: identifiers.userId,
147
+ externalId: identifiers.externalId,
148
+ authIsValid: details.authIsValid,
149
+ data: { ...oauthData },
152
150
  createdAt: now,
153
151
  updatedAt: now,
154
152
  };
155
153
 
156
- // Encrypt before storing
157
154
  const encryptedDocument = await this.encryptionService.encryptFields(
158
155
  'Credential',
159
156
  plainDocument
160
157
  );
161
158
 
162
- const insertedId = await insertOne(this.prisma, 'Credential', encryptedDocument);
159
+ const insertedId = await insertOne(
160
+ this.prisma,
161
+ 'Credential',
162
+ encryptedDocument
163
+ );
163
164
 
164
- // Read back and decrypt
165
- const created = await findOne(this.prisma, 'Credential', { _id: insertedId });
166
- if (!created) {
167
- console.error('[CredentialRepositoryDocumentDB] Credential not found after insert', {
168
- insertedId: fromObjectId(insertedId),
169
- identifiers,
170
- });
171
- throw new Error(
172
- 'Failed to create credential: Document not found after insert. ' +
173
- 'This indicates a database consistency issue.'
174
- );
175
- }
176
- const decryptedCredential = await this.encryptionService.decryptFields('Credential', created);
165
+ const created = await findOne(this.prisma, 'Credential', {
166
+ _id: insertedId,
167
+ });
168
+ const decryptedCredential = await this.encryptionService.decryptFields(
169
+ 'Credential',
170
+ created
171
+ );
177
172
  return this._mapCredential(decryptedCredential);
178
173
  }
179
174
 
@@ -182,39 +177,37 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
182
177
  const credential = await findOne(this.prisma, 'Credential', query);
183
178
  if (!credential) return null;
184
179
 
185
- // Decrypt sensitive fields using service
186
- const decryptedCredential = await this.encryptionService.decryptFields('Credential', credential);
180
+ const decryptedCredential = await this.encryptionService.decryptFields(
181
+ 'Credential',
182
+ credential
183
+ );
187
184
  return this._mapCredential(decryptedCredential);
188
185
  }
189
186
 
190
187
  async updateCredential(credentialId, updates) {
191
188
  const objectId = toObjectId(credentialId);
192
189
  if (!objectId) return null;
193
- const existing = await findOne(this.prisma, 'Credential', { _id: objectId });
190
+ const existing = await findOne(this.prisma, 'Credential', {
191
+ _id: objectId,
192
+ });
194
193
  if (!existing) return null;
195
194
 
196
- const {
197
- user,
198
- userId,
199
- authIsValid,
200
- externalId,
201
- ...oauthData
202
- } = updates || {};
195
+ const { authIsValid, ...oauthData } = updates || {};
203
196
 
204
- // Decrypt existing credential data first
205
- const decryptedExisting = await this.encryptionService.decryptFields('Credential', existing);
197
+ const decryptedExisting = await this.encryptionService.decryptFields(
198
+ 'Credential',
199
+ existing
200
+ );
206
201
  const mergedData = { ...(decryptedExisting.data || {}), ...oauthData };
207
202
 
208
- // Build update document
209
203
  const updateDocument = {
210
- userId: toObjectId(userId || user) || existing.userId || null,
211
- externalId: externalId !== undefined ? externalId : existing.externalId,
212
- authIsValid: authIsValid !== undefined ? authIsValid : existing.authIsValid,
204
+ userId: existing.userId,
205
+ externalId: existing.externalId,
206
+ authIsValid: authIsValid,
213
207
  data: mergedData,
214
208
  updatedAt: new Date(),
215
209
  };
216
210
 
217
- // Encrypt before storing
218
211
  const encryptedUpdate = await this.encryptionService.encryptFields(
219
212
  'Credential',
220
213
  { data: updateDocument.data }
@@ -235,18 +228,13 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
235
228
  }
236
229
  );
237
230
 
238
- // Read back and decrypt
239
- const updated = await findOne(this.prisma, 'Credential', { _id: objectId });
240
- if (!updated) {
241
- console.error('[CredentialRepositoryDocumentDB] Credential not found after update', {
242
- credentialId: fromObjectId(objectId),
243
- });
244
- throw new Error(
245
- 'Failed to update credential: Document not found after update. ' +
246
- 'This indicates a database consistency issue.'
247
- );
248
- }
249
- const decryptedCredential = await this.encryptionService.decryptFields('Credential', updated);
231
+ const updated = await findOne(this.prisma, 'Credential', {
232
+ _id: objectId,
233
+ });
234
+ const decryptedCredential = await this.encryptionService.decryptFields(
235
+ 'Credential',
236
+ updated
237
+ );
250
238
  return this._mapCredential(decryptedCredential);
251
239
  }
252
240
 
@@ -256,9 +244,8 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
256
244
  const idObj = toObjectId(identifiers._id || identifiers.id);
257
245
  if (idObj) filter._id = idObj;
258
246
  }
259
- if (identifiers.user || identifiers.userId) {
260
- const userObj = toObjectId(identifiers.user || identifiers.userId);
261
- if (userObj) filter.userId = userObj;
247
+ if (identifiers.userId) {
248
+ filter.userId = identifiers.userId;
262
249
  }
263
250
  if (identifiers.externalId !== undefined) {
264
251
  filter.externalId = identifiers.externalId;
@@ -273,9 +260,8 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
273
260
  const idObj = toObjectId(filter.credentialId || filter.id);
274
261
  if (idObj) query._id = idObj;
275
262
  }
276
- if (filter.user || filter.userId) {
277
- const userObj = toObjectId(filter.user || filter.userId);
278
- if (userObj) query.userId = userObj;
263
+ if (filter.userId !== undefined) {
264
+ query.userId = filter.userId;
279
265
  }
280
266
  if (filter.externalId !== undefined) {
281
267
  query.externalId = filter.externalId;
@@ -284,15 +270,14 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
284
270
  }
285
271
 
286
272
  /**
287
- * Map credential document to application format (without legacy fields)
288
- * Used by findCredential, upsertCredential, updateCredential
273
+ * Map credential document to application format
289
274
  * Matches MongoDB repository format
290
275
  * @private
291
276
  */
292
277
  _mapCredential(doc) {
293
278
  const data = doc?.data || {};
294
279
  const id = fromObjectId(doc?._id);
295
- const userId = fromObjectId(doc?.userId);
280
+ const userId = doc?.userId;
296
281
  return {
297
282
  id,
298
283
  userId,
@@ -302,20 +287,12 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
302
287
  };
303
288
  }
304
289
 
305
- /**
306
- * Map credential document with legacy fields for findCredentialById
307
- * Includes _id and user fields for backward compatibility
308
- * Matches MongoDB repository format
309
- * @private
310
- */
311
290
  _mapCredentialById(doc) {
312
291
  const data = doc?.data || {};
313
292
  const id = fromObjectId(doc?._id);
314
- const userId = fromObjectId(doc?.userId);
293
+ const userId = doc?.userId;
315
294
  return {
316
- _id: id,
317
295
  id,
318
- user: userId,
319
296
  userId,
320
297
  externalId: doc?.externalId ?? null,
321
298
  authIsValid: doc?.authIsValid ?? null,
@@ -325,4 +302,3 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
325
302
  }
326
303
 
327
304
  module.exports = { CredentialRepositoryDocumentDB };
328
-
@@ -38,9 +38,7 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
38
38
  const data = credential.data || {};
39
39
 
40
40
  return {
41
- _id: credential.id,
42
41
  id: credential.id,
43
- user: credential.userId,
44
42
  userId: credential.userId,
45
43
  externalId: credential.externalId,
46
44
  authIsValid: credential.authIsValid,
@@ -80,7 +78,6 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
80
78
  return { acknowledged: true, deletedCount: 1 };
81
79
  } catch (error) {
82
80
  if (error.code === 'P2025') {
83
- // Record not found
84
81
  return { acknowledged: true, deletedCount: 0 };
85
82
  }
86
83
  throw error;
@@ -99,49 +96,32 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
99
96
  if (!identifiers)
100
97
  throw new Error('identifiers required to upsert credential');
101
98
 
102
- if (!identifiers.user && !identifiers.userId) {
103
- throw new Error('user or userId required in identifiers');
99
+ if (!identifiers.userId) {
100
+ throw new Error('userId required in identifiers');
104
101
  }
105
102
  if (!identifiers.externalId) {
106
103
  throw new Error(
107
104
  'externalId required in identifiers to prevent credential collision. ' +
108
- 'When multiple credentials exist for the same user, both userId and externalId ' +
109
- 'are needed to uniquely identify which credential to update.'
105
+ 'When multiple credentials exist for the same user, both userId and externalId ' +
106
+ 'are needed to uniquely identify which credential to update.'
110
107
  );
111
108
  }
112
109
 
113
- // Build where clause from identifiers
114
110
  const where = this._convertIdentifiersToWhere(identifiers);
115
111
 
116
- // Separate schema fields from dynamic OAuth data
117
- const {
118
- user,
119
- userId,
120
- externalId,
121
- authIsValid,
122
-
123
- ...oauthData
124
- } = details;
125
-
126
- // Find existing credential
112
+ const { authIsValid, ...oauthData } = details;
113
+
127
114
  const existing = await this.prisma.credential.findFirst({ where });
128
115
 
129
116
  if (existing) {
130
- // Update existing - merge OAuth data into existing data JSON
131
117
  const mergedData = { ...(existing.data || {}), ...oauthData };
132
118
 
133
119
  const updated = await this.prisma.credential.update({
134
120
  where: { id: existing.id },
135
121
  data: {
136
- userId: userId || user || existing.userId,
137
- externalId:
138
- externalId !== undefined
139
- ? externalId
140
- : existing.externalId,
141
- authIsValid:
142
- authIsValid !== undefined
143
- ? authIsValid
144
- : existing.authIsValid,
122
+ userId: existing.userId,
123
+ externalId: existing.externalId,
124
+ authIsValid: authIsValid,
145
125
  data: mergedData,
146
126
  },
147
127
  });
@@ -155,13 +135,11 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
155
135
  };
156
136
  }
157
137
 
158
- // Create new credential
159
138
  const created = await this.prisma.credential.create({
160
139
  data: {
161
- userId: userId || user,
162
- externalId,
140
+ userId: identifiers.userId,
141
+ externalId: identifiers.externalId,
163
142
  authIsValid: authIsValid,
164
-
165
143
  data: oauthData,
166
144
  },
167
145
  });
@@ -205,7 +183,6 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
205
183
  authIsValid: credential.authIsValid,
206
184
  access_token: data.access_token,
207
185
  refresh_token: data.refresh_token,
208
- domain: data.domain,
209
186
  ...data,
210
187
  };
211
188
  }
@@ -219,7 +196,6 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
219
196
  * @returns {Promise<Object|null>} Updated credential object or null if not found
220
197
  */
221
198
  async updateCredential(credentialId, updates) {
222
- // Get existing credential to merge OAuth data
223
199
  const existing = await this.prisma.credential.findUnique({
224
200
  where: { id: credentialId },
225
201
  });
@@ -228,27 +204,16 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
228
204
  return null;
229
205
  }
230
206
 
231
- // Separate schema fields from OAuth data
232
- const {
233
- user,
234
- userId,
235
- externalId,
236
- authIsValid,
237
-
238
- ...oauthData
239
- } = updates;
240
-
241
- // Merge OAuth data with existing
207
+ const { authIsValid, ...oauthData } = updates;
208
+
242
209
  const mergedData = { ...(existing.data || {}), ...oauthData };
243
210
 
244
211
  const updated = await this.prisma.credential.update({
245
212
  where: { id: credentialId },
246
213
  data: {
247
- userId: userId || user || existing.userId,
248
- externalId:
249
- externalId !== undefined ? externalId : existing.externalId,
250
- authIsValid:
251
- authIsValid !== undefined ? authIsValid : existing.authIsValid,
214
+ userId: existing.userId,
215
+ externalId: existing.externalId,
216
+ authIsValid: authIsValid,
252
217
  data: mergedData,
253
218
  },
254
219
  });
@@ -262,7 +227,6 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
262
227
  authIsValid: updated.authIsValid,
263
228
  access_token: data.access_token,
264
229
  refresh_token: data.refresh_token,
265
- domain: data.domain,
266
230
  ...data,
267
231
  };
268
232
  }
@@ -278,7 +242,6 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
278
242
 
279
243
  if (identifiers._id) where.id = identifiers._id;
280
244
  if (identifiers.id) where.id = identifiers.id;
281
- if (identifiers.user) where.userId = identifiers.user;
282
245
  if (identifiers.userId) where.userId = identifiers.userId;
283
246
  if (identifiers.externalId) where.externalId = identifiers.externalId;
284
247
 
@@ -296,7 +259,6 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
296
259
 
297
260
  if (filter.credentialId) where.id = filter.credentialId;
298
261
  if (filter.id) where.id = filter.id;
299
- if (filter.user) where.userId = filter.user;
300
262
  if (filter.userId) where.userId = filter.userId;
301
263
  if (filter.externalId) where.externalId = filter.externalId;
302
264
 
@@ -36,7 +36,6 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
36
36
 
37
37
  /**
38
38
  * Find credential by ID
39
- * Replaces: Credential.findById(id)
40
39
  *
41
40
  * @param {string} id - Credential ID (string from application layer)
42
41
  * @returns {Promise<Object|null>} Credential object with string IDs or null
@@ -51,14 +50,11 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
51
50
  return null;
52
51
  }
53
52
 
54
- // Extract data from JSON field
55
53
  const data = credential.data || {};
56
54
 
57
55
  return {
58
- _id: credential.id.toString(),
59
56
  id: credential.id.toString(),
60
- user: credential.userId?.toString(),
61
- userId: credential.userId?.toString(),
57
+ userId: credential.userId.toString(),
62
58
  externalId: credential.externalId,
63
59
  authIsValid: credential.authIsValid,
64
60
  ...data, // Spread OAuth tokens from JSON field
@@ -67,7 +63,6 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
67
63
 
68
64
  /**
69
65
  * Update authentication status
70
- * Replaces: Credential.updateOne({ _id: credentialId }, { $set: { authIsValid } })
71
66
  *
72
67
  * @param {string} credentialId - Credential ID (string from application layer)
73
68
  * @param {boolean} authIsValid - Authentication validity status
@@ -85,7 +80,6 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
85
80
 
86
81
  /**
87
82
  * Permanently remove a credential document
88
- * Replaces: Credential.deleteOne({ _id: credentialId })
89
83
  *
90
84
  * @param {string} credentialId - Credential ID (string from application layer)
91
85
  * @returns {Promise<Object>} Deletion result
@@ -108,7 +102,6 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
108
102
 
109
103
  /**
110
104
  * Create or update credential matching identifiers
111
- * Replaces: Credential.findOneAndUpdate(query, update, { upsert: true })
112
105
  *
113
106
  * @param {{identifiers: Object, details: Object}} credentialDetails
114
107
  * @returns {Promise<Object>} The persisted credential with string IDs
@@ -118,22 +111,21 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
118
111
  if (!identifiers)
119
112
  throw new Error('identifiers required to upsert credential');
120
113
 
121
- if (!identifiers.user && !identifiers.userId) {
122
- throw new Error('user or userId required in identifiers');
114
+ if (!identifiers.userId) {
115
+ throw new Error('userId required in identifiers');
123
116
  }
124
117
  if (!identifiers.externalId) {
125
118
  throw new Error(
126
119
  'externalId required in identifiers to prevent credential collision. ' +
127
- 'When multiple credentials exist for the same user, both userId and externalId ' +
128
- 'are needed to uniquely identify which credential to update.'
120
+ 'When multiple credentials exist for the same user, both userId and externalId ' +
121
+ 'are needed to uniquely identify which credential to update.'
129
122
  );
130
123
  }
131
124
 
132
125
  const where = this._convertIdentifiersToWhere(identifiers);
133
126
 
134
- const { user, externalId } = identifiers;
127
+ const { externalId } = identifiers;
135
128
 
136
- // Separate schema fields from dynamic OAuth data
137
129
  const { authIsValid, ...oauthData } = details;
138
130
 
139
131
  const existing = await this.prisma.credential.findFirst({ where });
@@ -144,15 +136,9 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
144
136
  const updated = await this.prisma.credential.update({
145
137
  where: { id: existing.id },
146
138
  data: {
147
- userId: this._convertId(user || existing.userId),
148
- externalId:
149
- externalId !== undefined
150
- ? externalId
151
- : existing.externalId,
152
- authIsValid:
153
- authIsValid !== undefined
154
- ? authIsValid
155
- : existing.authIsValid,
139
+ userId: this._convertId(existing.userId),
140
+ externalId: existing.externalId,
141
+ authIsValid: authIsValid,
156
142
  data: mergedData,
157
143
  },
158
144
  });
@@ -168,10 +154,9 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
168
154
 
169
155
  const created = await this.prisma.credential.create({
170
156
  data: {
171
- userId: this._convertId(user),
157
+ userId: this._convertId(identifiers.userId),
172
158
  externalId,
173
159
  authIsValid: authIsValid,
174
-
175
160
  data: oauthData,
176
161
  },
177
162
  });
@@ -187,7 +172,6 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
187
172
 
188
173
  /**
189
174
  * Find a credential by filter criteria
190
- * Replaces: Credential.findOne(query)
191
175
  *
192
176
  * @param {Object} filter
193
177
  * @param {string} [filter.userId] - User ID (string from application layer)
@@ -215,21 +199,18 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
215
199
  authIsValid: credential.authIsValid,
216
200
  access_token: data.access_token,
217
201
  refresh_token: data.refresh_token,
218
- domain: data.domain,
219
202
  ...data,
220
203
  };
221
204
  }
222
205
 
223
206
  /**
224
207
  * Update a credential by ID
225
- * Replaces: Credential.findByIdAndUpdate(credentialId, { $set: updates })
226
208
  *
227
209
  * @param {string} credentialId - Credential ID (string from application layer)
228
210
  * @param {Object} updates - Fields to update
229
211
  * @returns {Promise<Object|null>} Updated credential object with string IDs or null if not found
230
212
  */
231
213
  async updateCredential(credentialId, updates) {
232
- // Get existing credential to merge OAuth data
233
214
  const intId = this._convertId(credentialId);
234
215
  const existing = await this.prisma.credential.findUnique({
235
216
  where: { id: intId },
@@ -239,21 +220,16 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
239
220
  return null;
240
221
  }
241
222
 
242
- // Separate schema fields from OAuth data
243
- const { user, authIsValid, ...oauthData } =
244
- updates;
223
+ const { authIsValid, ...oauthData } = updates;
245
224
 
246
- // Merge OAuth data with existing
247
225
  const mergedData = { ...(existing.data || {}), ...oauthData };
248
226
 
249
227
  const updated = await this.prisma.credential.update({
250
228
  where: { id: intId },
251
229
  data: {
252
- userId: this._convertId(userId || user || existing.userId),
253
- externalId:
254
- externalId !== undefined ? externalId : existing.externalId,
255
- authIsValid:
256
- authIsValid !== undefined ? authIsValid : existing.authIsValid,
230
+ userId: this._convertId(existing.userId),
231
+ externalId: existing.externalId,
232
+ authIsValid: authIsValid,
257
233
  data: mergedData,
258
234
  },
259
235
  });
@@ -267,7 +243,6 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
267
243
  authIsValid: updated.authIsValid,
268
244
  access_token: data.access_token,
269
245
  refresh_token: data.refresh_token,
270
- domain: data.domain,
271
246
  ...data,
272
247
  };
273
248
  }
@@ -282,7 +257,6 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
282
257
  const where = {};
283
258
 
284
259
  if (identifiers.id) where.id = this._convertId(identifiers.id);
285
- if (identifiers.user) where.userId = this._convertId(identifiers.user);
286
260
  if (identifiers.userId)
287
261
  where.userId = this._convertId(identifiers.userId);
288
262
  if (identifiers.externalId) where.externalId = identifiers.externalId;
@@ -302,7 +276,6 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
302
276
  if (filter.credentialId)
303
277
  where.id = this._convertId(filter.credentialId);
304
278
  if (filter.id) where.id = this._convertId(filter.id);
305
- if (filter.user) where.userId = this._convertId(filter.user);
306
279
  if (filter.userId) where.userId = this._convertId(filter.userId);
307
280
  if (filter.externalId) where.externalId = filter.externalId;
308
281
 
@@ -4,14 +4,18 @@ class GetCredentialForUser {
4
4
  }
5
5
 
6
6
  async execute(credentialId, userId) {
7
- const credential = await this.credentialRepository.findCredentialById(credentialId);
7
+ const credential = await this.credentialRepository.findCredentialById(
8
+ credentialId
9
+ );
8
10
 
9
11
  if (!credential) {
10
12
  throw new Error(`Credential with id ${credentialId} not found`);
11
13
  }
12
14
 
13
- if (credential.user.toString() !== userId.toString()) {
14
- throw new Error(`Credential ${credentialId} does not belong to user ${userId}`);
15
+ if (credential.userId.toString() !== userId.toString()) {
16
+ throw new Error(
17
+ `Credential ${credentialId} does not belong to user ${userId}`
18
+ );
15
19
  }
16
20
 
17
21
  return credential;
@@ -61,8 +61,9 @@ class FetchError extends BaseError {
61
61
  ];
62
62
 
63
63
  super(messageParts.filter(Boolean).join('\n'));
64
-
64
+
65
65
  this.response = response;
66
+ this.statusCode = response?.status;
66
67
  }
67
68
 
68
69
  static async create(options = {}) {
@@ -65,9 +65,7 @@ const {
65
65
  const {
66
66
  AuthenticateWithSharedSecret,
67
67
  } = require('../user/use-cases/authenticate-with-shared-secret');
68
- const {
69
- AuthenticateUser,
70
- } = require('../user/use-cases/authenticate-user');
68
+ const { AuthenticateUser } = require('../user/use-cases/authenticate-user');
71
69
  const {
72
70
  ProcessAuthorizationCallback,
73
71
  } = require('../modules/use-cases/process-authorization-callback');
@@ -234,8 +232,10 @@ function checkRequiredParams(params, requiredKeys) {
234
232
 
235
233
  if (missingKeys.length > 0) {
236
234
  throw Boom.badRequest(
237
- `Missing Parameter${missingKeys.length === 1 ? '' : 's'
238
- }: ${missingKeys.join(', ')} ${missingKeys.length === 1 ? 'is' : 'are'
235
+ `Missing Parameter${
236
+ missingKeys.length === 1 ? '' : 's'
237
+ }: ${missingKeys.join(', ')} ${
238
+ missingKeys.length === 1 ? 'is' : 'are'
239
239
  } required.`
240
240
  );
241
241
  }
@@ -584,7 +584,7 @@ function setEntityRoutes(router, authenticateUser, useCases) {
584
584
  req.params.credentialId,
585
585
  userId
586
586
  );
587
- if (credential.user._id.toString() !== userId) {
587
+ if (credential.userId.toString() !== userId) {
588
588
  throw Boom.forbidden('Credential does not belong to user');
589
589
  }
590
590
 
@@ -12,28 +12,9 @@ const {
12
12
  const {
13
13
  IntegrationMappingRepositoryInterface,
14
14
  } = require('./integration-mapping-repository-interface');
15
- const { DocumentDBEncryptionService } = require('../../database/documentdb-encryption-service');
16
-
17
- /**
18
- * IntegrationMapping repository for DocumentDB.
19
- * Uses DocumentDBEncryptionService for field-level encryption.
20
- *
21
- * Encrypted fields:
22
- * - IntegrationMapping.mapping
23
- *
24
- * SECURITY CRITICAL: Mapping data may contain API keys, secrets, and sensitive configuration.
25
- *
26
- * DEFENSIVE CHECK PATTERN:
27
- * Methods that return documents (upsertMapping, updateMapping) include defensive checks
28
- * to verify the document exists after write operations. This catches:
29
- * - Race conditions (document deleted between write and read)
30
- * - Silent write failures due to permissions or storage issues
31
- * - DocumentDB replication consistency issues
32
- *
33
- * @see credential-repository-documentdb.js for the same pattern
34
- * @see DocumentDBEncryptionService
35
- * @see encryption-schema-registry.js
36
- */
15
+ const {
16
+ DocumentDBEncryptionService,
17
+ } = require('../../database/documentdb-encryption-service');
37
18
  class IntegrationMappingRepositoryDocumentDB extends IntegrationMappingRepositoryInterface {
38
19
  constructor() {
39
20
  super();
@@ -46,27 +27,34 @@ class IntegrationMappingRepositoryDocumentDB extends IntegrationMappingRepositor
46
27
  const doc = await findOne(this.prisma, 'IntegrationMapping', filter);
47
28
  if (!doc) return null;
48
29
 
49
- // Decrypt sensitive fields using service
50
- const decryptedMapping = await this.encryptionService.decryptFields('IntegrationMapping', doc);
30
+ const decryptedMapping = await this.encryptionService.decryptFields(
31
+ 'IntegrationMapping',
32
+ doc
33
+ );
51
34
  return this._mapMapping(decryptedMapping);
52
35
  }
53
36
 
54
37
  async upsertMapping(integrationId, sourceId, mapping) {
55
38
  const filter = this._compositeFilter(integrationId, sourceId);
56
- const existing = await findOne(this.prisma, 'IntegrationMapping', filter);
39
+ const existing = await findOne(
40
+ this.prisma,
41
+ 'IntegrationMapping',
42
+ filter
43
+ );
57
44
  const now = new Date();
58
45
 
59
46
  if (existing) {
60
- // Decrypt existing mapping data first
61
- const decryptedExisting = await this.encryptionService.decryptFields('IntegrationMapping', existing);
47
+ const decryptedExisting =
48
+ await this.encryptionService.decryptFields(
49
+ 'IntegrationMapping',
50
+ existing
51
+ );
62
52
 
63
- // Build update document
64
53
  const updateDocument = {
65
- mapping, // Plain text mapping
54
+ mapping,
66
55
  updatedAt: now,
67
56
  };
68
57
 
69
- // Encrypt before storing
70
58
  const encryptedUpdate = await this.encryptionService.encryptFields(
71
59
  'IntegrationMapping',
72
60
  { mapping: updateDocument.mapping }
@@ -84,54 +72,73 @@ class IntegrationMappingRepositoryDocumentDB extends IntegrationMappingRepositor
84
72
  }
85
73
  );
86
74
 
87
- // Read back and decrypt
88
- const updated = await findOne(this.prisma, 'IntegrationMapping', { _id: existing._id });
75
+ const updated = await findOne(this.prisma, 'IntegrationMapping', {
76
+ _id: existing._id,
77
+ });
89
78
  if (!updated) {
90
- console.error('[IntegrationMappingRepositoryDocumentDB] Mapping not found after update', {
91
- mappingId: fromObjectId(existing._id),
92
- integrationId,
93
- sourceId,
94
- });
79
+ console.error(
80
+ '[IntegrationMappingRepositoryDocumentDB] Mapping not found after update',
81
+ {
82
+ mappingId: fromObjectId(existing._id),
83
+ integrationId,
84
+ sourceId,
85
+ }
86
+ );
95
87
  throw new Error(
96
88
  'Failed to update mapping: Document not found after update. ' +
97
- 'This indicates a database consistency issue.'
89
+ 'This indicates a database consistency issue.'
98
90
  );
99
91
  }
100
- const decryptedMapping = await this.encryptionService.decryptFields('IntegrationMapping', updated);
92
+ const decryptedMapping = await this.encryptionService.decryptFields(
93
+ 'IntegrationMapping',
94
+ updated
95
+ );
101
96
  return this._mapMapping(decryptedMapping);
102
97
  }
103
98
 
104
- // Build plain text document
105
99
  const plainDocument = {
106
100
  integrationId: toObjectId(integrationId),
107
- sourceId: sourceId === null || sourceId === undefined ? null : String(sourceId),
101
+ sourceId:
102
+ sourceId === null || sourceId === undefined
103
+ ? null
104
+ : String(sourceId),
108
105
  mapping,
109
106
  createdAt: now,
110
107
  updatedAt: now,
111
108
  };
112
109
 
113
- // Encrypt before storing
114
110
  const encryptedDocument = await this.encryptionService.encryptFields(
115
111
  'IntegrationMapping',
116
112
  plainDocument
117
113
  );
118
114
 
119
- const insertedId = await insertOne(this.prisma, 'IntegrationMapping', encryptedDocument);
115
+ const insertedId = await insertOne(
116
+ this.prisma,
117
+ 'IntegrationMapping',
118
+ encryptedDocument
119
+ );
120
120
 
121
- // Read back and decrypt
122
- const created = await findOne(this.prisma, 'IntegrationMapping', { _id: insertedId });
121
+ const created = await findOne(this.prisma, 'IntegrationMapping', {
122
+ _id: insertedId,
123
+ });
123
124
  if (!created) {
124
- console.error('[IntegrationMappingRepositoryDocumentDB] Mapping not found after insert', {
125
- insertedId: fromObjectId(insertedId),
126
- integrationId,
127
- sourceId,
128
- });
125
+ console.error(
126
+ '[IntegrationMappingRepositoryDocumentDB] Mapping not found after insert',
127
+ {
128
+ insertedId: fromObjectId(insertedId),
129
+ integrationId,
130
+ sourceId,
131
+ }
132
+ );
129
133
  throw new Error(
130
134
  'Failed to create mapping: Document not found after insert. ' +
131
- 'This indicates a database consistency issue.'
135
+ 'This indicates a database consistency issue.'
132
136
  );
133
137
  }
134
- const decryptedMapping = await this.encryptionService.decryptFields('IntegrationMapping', created);
138
+ const decryptedMapping = await this.encryptionService.decryptFields(
139
+ 'IntegrationMapping',
140
+ created
141
+ );
135
142
  return this._mapMapping(decryptedMapping);
136
143
  }
137
144
 
@@ -141,9 +148,10 @@ class IntegrationMappingRepositoryDocumentDB extends IntegrationMappingRepositor
141
148
  if (integrationObjectId) filter.integrationId = integrationObjectId;
142
149
  const docs = await findMany(this.prisma, 'IntegrationMapping', filter);
143
150
 
144
- // Decrypt sensitive fields for each document
145
151
  const decryptedDocs = await Promise.all(
146
- docs.map(doc => this.encryptionService.decryptFields('IntegrationMapping', doc))
152
+ docs.map((doc) =>
153
+ this.encryptionService.decryptFields('IntegrationMapping', doc)
154
+ )
147
155
  );
148
156
 
149
157
  return decryptedDocs.map((doc) => this._mapMapping(doc));
@@ -151,7 +159,11 @@ class IntegrationMappingRepositoryDocumentDB extends IntegrationMappingRepositor
151
159
 
152
160
  async deleteMapping(integrationId, sourceId) {
153
161
  const filter = this._compositeFilter(integrationId, sourceId);
154
- const result = await deleteOne(this.prisma, 'IntegrationMapping', filter);
162
+ const result = await deleteOne(
163
+ this.prisma,
164
+ 'IntegrationMapping',
165
+ filter
166
+ );
155
167
  const deleted = result?.n ?? 0;
156
168
  return { acknowledged: true, deletedCount: deleted };
157
169
  }
@@ -171,11 +183,15 @@ class IntegrationMappingRepositoryDocumentDB extends IntegrationMappingRepositor
171
183
  async findMappingById(id) {
172
184
  const objectId = toObjectId(id);
173
185
  if (!objectId) return null;
174
- const doc = await findOne(this.prisma, 'IntegrationMapping', { _id: objectId });
186
+ const doc = await findOne(this.prisma, 'IntegrationMapping', {
187
+ _id: objectId,
188
+ });
175
189
  if (!doc) return null;
176
190
 
177
- // Decrypt sensitive fields using service
178
- const decryptedMapping = await this.encryptionService.decryptFields('IntegrationMapping', doc);
191
+ const decryptedMapping = await this.encryptionService.decryptFields(
192
+ 'IntegrationMapping',
193
+ doc
194
+ );
179
195
  return this._mapMapping(decryptedMapping);
180
196
  }
181
197
 
@@ -183,22 +199,26 @@ class IntegrationMappingRepositoryDocumentDB extends IntegrationMappingRepositor
183
199
  const objectId = toObjectId(id);
184
200
  if (!objectId) return null;
185
201
 
186
- const existing = await findOne(this.prisma, 'IntegrationMapping', { _id: objectId });
202
+ const existing = await findOne(this.prisma, 'IntegrationMapping', {
203
+ _id: objectId,
204
+ });
187
205
  if (!existing) return null;
188
206
 
189
- // Decrypt existing mapping data first
190
- const decryptedExisting = await this.encryptionService.decryptFields('IntegrationMapping', existing);
207
+ const decryptedExisting = await this.encryptionService.decryptFields(
208
+ 'IntegrationMapping',
209
+ existing
210
+ );
191
211
 
192
- // Merge updates - if mapping is provided, use it, otherwise keep existing
193
- const mergedMapping = updates.mapping !== undefined ? updates.mapping : decryptedExisting.mapping;
212
+ const mergedMapping =
213
+ updates.mapping !== undefined
214
+ ? updates.mapping
215
+ : decryptedExisting.mapping;
194
216
 
195
- // Build update with other fields
196
217
  const updateDocument = {
197
218
  ...updates,
198
219
  updatedAt: new Date(),
199
220
  };
200
221
 
201
- // Encrypt mapping before storing
202
222
  if (mergedMapping !== undefined) {
203
223
  const encryptedUpdate = await this.encryptionService.encryptFields(
204
224
  'IntegrationMapping',
@@ -216,18 +236,25 @@ class IntegrationMappingRepositoryDocumentDB extends IntegrationMappingRepositor
216
236
  }
217
237
  );
218
238
 
219
- // Read back and decrypt
220
- const updated = await findOne(this.prisma, 'IntegrationMapping', { _id: objectId });
239
+ const updated = await findOne(this.prisma, 'IntegrationMapping', {
240
+ _id: objectId,
241
+ });
221
242
  if (!updated) {
222
- console.error('[IntegrationMappingRepositoryDocumentDB] Mapping not found after update', {
223
- mappingId: fromObjectId(objectId),
224
- });
243
+ console.error(
244
+ '[IntegrationMappingRepositoryDocumentDB] Mapping not found after update',
245
+ {
246
+ mappingId: fromObjectId(objectId),
247
+ }
248
+ );
225
249
  throw new Error(
226
250
  'Failed to update mapping: Document not found after update. ' +
227
- 'This indicates a database consistency issue.'
251
+ 'This indicates a database consistency issue.'
228
252
  );
229
253
  }
230
- const decryptedMapping = await this.encryptionService.decryptFields('IntegrationMapping', updated);
254
+ const decryptedMapping = await this.encryptionService.decryptFields(
255
+ 'IntegrationMapping',
256
+ updated
257
+ );
231
258
  return this._mapMapping(decryptedMapping);
232
259
  }
233
260
 
@@ -254,5 +281,3 @@ class IntegrationMappingRepositoryDocumentDB extends IntegrationMappingRepositor
254
281
  }
255
282
 
256
283
  module.exports = { IntegrationMappingRepositoryDocumentDB };
257
-
258
-
@@ -15,11 +15,6 @@ const {
15
15
  DocumentDBEncryptionService,
16
16
  } = require('../../database/documentdb-encryption-service');
17
17
 
18
- /**
19
- * @see credential-repository-documentdb.js for the same pattern
20
- * @see integration-mapping-repository-documentdb.js for the same pattern
21
- * @see encryption-schema-registry.js for custom schema configuration
22
- */
23
18
  class ProcessRepositoryDocumentDB extends ProcessRepositoryInterface {
24
19
  constructor() {
25
20
  super();
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--canary.503.d72d514.0",
4
+ "version": "2.0.0--canary.503.ce72072.0",
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--canary.503.d72d514.0",
42
- "@friggframework/prettier-config": "2.0.0--canary.503.d72d514.0",
43
- "@friggframework/test": "2.0.0--canary.503.d72d514.0",
41
+ "@friggframework/eslint-config": "2.0.0--canary.503.ce72072.0",
42
+ "@friggframework/prettier-config": "2.0.0--canary.503.ce72072.0",
43
+ "@friggframework/test": "2.0.0--canary.503.ce72072.0",
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": "d72d5146178da9d183e93525478ba84807399bcc"
83
+ "gitHead": "ce72072f759299e7fd70fc2c9fd29811d3d727a6"
84
84
  }