@friggframework/core 2.0.0-next.54 → 2.0.0-next.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/application/commands/credential-commands.js +1 -1
- package/core/create-handler.js +12 -0
- package/credential/repositories/credential-repository-documentdb.js +81 -77
- package/credential/repositories/credential-repository-mongo.js +16 -54
- package/credential/repositories/credential-repository-postgres.js +14 -41
- package/credential/use-cases/get-credential-for-user.js +7 -3
- package/database/encryption/README.md +126 -21
- package/database/encryption/encryption-schema-registry.js +83 -2
- package/errors/client-safe-error.js +26 -0
- package/errors/fetch-error.js +2 -1
- package/errors/index.js +2 -0
- package/integrations/integration-router.js +6 -6
- package/integrations/repositories/integration-mapping-repository-documentdb.js +178 -33
- package/integrations/repositories/integration-repository-documentdb.js +21 -0
- package/integrations/repositories/process-repository-documentdb.js +143 -41
- package/modules/requester/api-key.js +24 -8
- package/package.json +5 -5
- package/token/repositories/token-repository-documentdb.js +20 -8
- package/token/repositories/token-repository-mongo.js +10 -3
- package/token/repositories/token-repository-postgres.js +10 -3
- package/user/repositories/user-repository-documentdb.js +177 -37
- package/user/repositories/user-repository-mongo.js +3 -2
- package/user/repositories/user-repository-postgres.js +3 -2
- package/user/use-cases/login-user.js +1 -1
package/core/create-handler.js
CHANGED
|
@@ -39,6 +39,18 @@ const createHandler = (optionByName = {}) => {
|
|
|
39
39
|
|
|
40
40
|
// Don't leak implementation details to end users.
|
|
41
41
|
if (isUserFacingResponse) {
|
|
42
|
+
// Allow client-safe errors to pass through with their actual message
|
|
43
|
+
if (error.isClientSafe === true) {
|
|
44
|
+
const statusCode = error.statusCode || 400;
|
|
45
|
+
return {
|
|
46
|
+
statusCode,
|
|
47
|
+
body: JSON.stringify({
|
|
48
|
+
error: error.message,
|
|
49
|
+
}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Hide other errors with generic message
|
|
42
54
|
return {
|
|
43
55
|
statusCode: 500,
|
|
44
56
|
body: JSON.stringify({
|
|
@@ -10,7 +10,9 @@ const {
|
|
|
10
10
|
const {
|
|
11
11
|
CredentialRepositoryInterface,
|
|
12
12
|
} = require('./credential-repository-interface');
|
|
13
|
-
const {
|
|
13
|
+
const {
|
|
14
|
+
DocumentDBEncryptionService,
|
|
15
|
+
} = require('../../database/documentdb-encryption-service');
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Credential repository for DocumentDB.
|
|
@@ -20,7 +22,6 @@ const { DocumentDBEncryptionService } = require('../../database/documentdb-encry
|
|
|
20
22
|
* - Credential.data.access_token
|
|
21
23
|
* - Credential.data.refresh_token
|
|
22
24
|
* - Credential.data.id_token
|
|
23
|
-
* - Credential.data.domain
|
|
24
25
|
*
|
|
25
26
|
* SECURITY CRITICAL: All OAuth credentials must be encrypted at rest.
|
|
26
27
|
*
|
|
@@ -40,8 +41,10 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
|
|
|
40
41
|
const doc = await findOne(this.prisma, 'Credential', { _id: objectId });
|
|
41
42
|
if (!doc) return null;
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
const decryptedCredential = await this.encryptionService.decryptFields(
|
|
45
|
+
'Credential',
|
|
46
|
+
doc
|
|
47
|
+
);
|
|
45
48
|
return this._mapCredentialById(decryptedCredential);
|
|
46
49
|
}
|
|
47
50
|
|
|
@@ -63,16 +66,19 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
|
|
|
63
66
|
async deleteCredentialById(credentialId) {
|
|
64
67
|
const objectId = toObjectId(credentialId);
|
|
65
68
|
if (!objectId) return { acknowledged: true, deletedCount: 0 };
|
|
66
|
-
const result = await deleteOne(this.prisma, 'Credential', {
|
|
69
|
+
const result = await deleteOne(this.prisma, 'Credential', {
|
|
70
|
+
_id: objectId,
|
|
71
|
+
});
|
|
67
72
|
const deleted = result?.n ?? 0;
|
|
68
73
|
return { acknowledged: true, deletedCount: deleted };
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
async upsertCredential(credentialDetails) {
|
|
72
77
|
const { identifiers, details } = credentialDetails;
|
|
73
|
-
if (!identifiers)
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
if (!identifiers)
|
|
79
|
+
throw new Error('identifiers required to upsert credential');
|
|
80
|
+
if (!identifiers.userId) {
|
|
81
|
+
throw new Error('userId required in identifiers');
|
|
76
82
|
}
|
|
77
83
|
if (!identifiers.externalId) {
|
|
78
84
|
throw new Error(
|
|
@@ -82,32 +88,29 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
|
|
|
82
88
|
|
|
83
89
|
const filter = this._buildIdentifierFilter(identifiers);
|
|
84
90
|
const existing = await findOne(this.prisma, 'Credential', filter);
|
|
85
|
-
|
|
86
|
-
const {
|
|
87
|
-
user,
|
|
88
|
-
userId,
|
|
89
|
-
authIsValid,
|
|
90
|
-
externalId,
|
|
91
|
-
...oauthData
|
|
92
|
-
} = details || {};
|
|
93
|
-
|
|
94
91
|
const now = new Date();
|
|
95
92
|
|
|
93
|
+
const { authIsValid, ...oauthData } = details || {};
|
|
94
|
+
|
|
96
95
|
if (existing) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
const decryptedExisting =
|
|
97
|
+
await this.encryptionService.decryptFields(
|
|
98
|
+
'Credential',
|
|
99
|
+
existing
|
|
100
|
+
);
|
|
101
|
+
const mergedData = {
|
|
102
|
+
...(decryptedExisting.data || {}),
|
|
103
|
+
...oauthData,
|
|
104
|
+
};
|
|
100
105
|
|
|
101
|
-
// Build update document
|
|
102
106
|
const updateDocument = {
|
|
103
|
-
userId:
|
|
104
|
-
externalId:
|
|
105
|
-
authIsValid: authIsValid
|
|
107
|
+
userId: existing.userId,
|
|
108
|
+
externalId: existing.externalId,
|
|
109
|
+
authIsValid: authIsValid,
|
|
106
110
|
data: mergedData,
|
|
107
111
|
updatedAt: now,
|
|
108
112
|
};
|
|
109
113
|
|
|
110
|
-
// Encrypt before storing
|
|
111
114
|
const encryptedUpdate = await this.encryptionService.encryptFields(
|
|
112
115
|
'Credential',
|
|
113
116
|
{ data: updateDocument.data }
|
|
@@ -128,33 +131,44 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
|
|
|
128
131
|
}
|
|
129
132
|
);
|
|
130
133
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
|
141
|
+
);
|
|
134
142
|
return this._mapCredential(decryptedCredential);
|
|
135
143
|
}
|
|
136
144
|
|
|
137
|
-
// Build plain text document
|
|
138
145
|
const plainDocument = {
|
|
139
|
-
userId:
|
|
140
|
-
externalId:
|
|
141
|
-
authIsValid: authIsValid
|
|
142
|
-
data: oauthData,
|
|
146
|
+
userId: identifiers.userId,
|
|
147
|
+
externalId: identifiers.externalId,
|
|
148
|
+
authIsValid: details.authIsValid,
|
|
149
|
+
data: { ...oauthData },
|
|
143
150
|
createdAt: now,
|
|
144
151
|
updatedAt: now,
|
|
145
152
|
};
|
|
146
153
|
|
|
147
|
-
// Encrypt before storing
|
|
148
154
|
const encryptedDocument = await this.encryptionService.encryptFields(
|
|
149
155
|
'Credential',
|
|
150
156
|
plainDocument
|
|
151
157
|
);
|
|
152
158
|
|
|
153
|
-
const insertedId = await insertOne(
|
|
159
|
+
const insertedId = await insertOne(
|
|
160
|
+
this.prisma,
|
|
161
|
+
'Credential',
|
|
162
|
+
encryptedDocument
|
|
163
|
+
);
|
|
154
164
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
165
|
+
const created = await findOne(this.prisma, 'Credential', {
|
|
166
|
+
_id: insertedId,
|
|
167
|
+
});
|
|
168
|
+
const decryptedCredential = await this.encryptionService.decryptFields(
|
|
169
|
+
'Credential',
|
|
170
|
+
created
|
|
171
|
+
);
|
|
158
172
|
return this._mapCredential(decryptedCredential);
|
|
159
173
|
}
|
|
160
174
|
|
|
@@ -163,39 +177,37 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
|
|
|
163
177
|
const credential = await findOne(this.prisma, 'Credential', query);
|
|
164
178
|
if (!credential) return null;
|
|
165
179
|
|
|
166
|
-
|
|
167
|
-
|
|
180
|
+
const decryptedCredential = await this.encryptionService.decryptFields(
|
|
181
|
+
'Credential',
|
|
182
|
+
credential
|
|
183
|
+
);
|
|
168
184
|
return this._mapCredential(decryptedCredential);
|
|
169
185
|
}
|
|
170
186
|
|
|
171
187
|
async updateCredential(credentialId, updates) {
|
|
172
188
|
const objectId = toObjectId(credentialId);
|
|
173
189
|
if (!objectId) return null;
|
|
174
|
-
const existing = await findOne(this.prisma, 'Credential', {
|
|
190
|
+
const existing = await findOne(this.prisma, 'Credential', {
|
|
191
|
+
_id: objectId,
|
|
192
|
+
});
|
|
175
193
|
if (!existing) return null;
|
|
176
194
|
|
|
177
|
-
const {
|
|
178
|
-
user,
|
|
179
|
-
userId,
|
|
180
|
-
authIsValid,
|
|
181
|
-
externalId,
|
|
182
|
-
...oauthData
|
|
183
|
-
} = updates || {};
|
|
195
|
+
const { authIsValid, ...oauthData } = updates || {};
|
|
184
196
|
|
|
185
|
-
|
|
186
|
-
|
|
197
|
+
const decryptedExisting = await this.encryptionService.decryptFields(
|
|
198
|
+
'Credential',
|
|
199
|
+
existing
|
|
200
|
+
);
|
|
187
201
|
const mergedData = { ...(decryptedExisting.data || {}), ...oauthData };
|
|
188
202
|
|
|
189
|
-
// Build update document
|
|
190
203
|
const updateDocument = {
|
|
191
|
-
userId:
|
|
192
|
-
externalId:
|
|
193
|
-
authIsValid: authIsValid
|
|
204
|
+
userId: existing.userId,
|
|
205
|
+
externalId: existing.externalId,
|
|
206
|
+
authIsValid: authIsValid,
|
|
194
207
|
data: mergedData,
|
|
195
208
|
updatedAt: new Date(),
|
|
196
209
|
};
|
|
197
210
|
|
|
198
|
-
// Encrypt before storing
|
|
199
211
|
const encryptedUpdate = await this.encryptionService.encryptFields(
|
|
200
212
|
'Credential',
|
|
201
213
|
{ data: updateDocument.data }
|
|
@@ -216,9 +228,13 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
|
|
|
216
228
|
}
|
|
217
229
|
);
|
|
218
230
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
231
|
+
const updated = await findOne(this.prisma, 'Credential', {
|
|
232
|
+
_id: objectId,
|
|
233
|
+
});
|
|
234
|
+
const decryptedCredential = await this.encryptionService.decryptFields(
|
|
235
|
+
'Credential',
|
|
236
|
+
updated
|
|
237
|
+
);
|
|
222
238
|
return this._mapCredential(decryptedCredential);
|
|
223
239
|
}
|
|
224
240
|
|
|
@@ -228,9 +244,8 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
|
|
|
228
244
|
const idObj = toObjectId(identifiers._id || identifiers.id);
|
|
229
245
|
if (idObj) filter._id = idObj;
|
|
230
246
|
}
|
|
231
|
-
if (identifiers.
|
|
232
|
-
|
|
233
|
-
if (userObj) filter.userId = userObj;
|
|
247
|
+
if (identifiers.userId) {
|
|
248
|
+
filter.userId = identifiers.userId;
|
|
234
249
|
}
|
|
235
250
|
if (identifiers.externalId !== undefined) {
|
|
236
251
|
filter.externalId = identifiers.externalId;
|
|
@@ -245,9 +260,8 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
|
|
|
245
260
|
const idObj = toObjectId(filter.credentialId || filter.id);
|
|
246
261
|
if (idObj) query._id = idObj;
|
|
247
262
|
}
|
|
248
|
-
if (filter.
|
|
249
|
-
|
|
250
|
-
if (userObj) query.userId = userObj;
|
|
263
|
+
if (filter.userId !== undefined) {
|
|
264
|
+
query.userId = filter.userId;
|
|
251
265
|
}
|
|
252
266
|
if (filter.externalId !== undefined) {
|
|
253
267
|
query.externalId = filter.externalId;
|
|
@@ -256,15 +270,14 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
|
|
|
256
270
|
}
|
|
257
271
|
|
|
258
272
|
/**
|
|
259
|
-
* Map credential document to application format
|
|
260
|
-
* Used by findCredential, upsertCredential, updateCredential
|
|
273
|
+
* Map credential document to application format
|
|
261
274
|
* Matches MongoDB repository format
|
|
262
275
|
* @private
|
|
263
276
|
*/
|
|
264
277
|
_mapCredential(doc) {
|
|
265
278
|
const data = doc?.data || {};
|
|
266
279
|
const id = fromObjectId(doc?._id);
|
|
267
|
-
const userId =
|
|
280
|
+
const userId = doc?.userId;
|
|
268
281
|
return {
|
|
269
282
|
id,
|
|
270
283
|
userId,
|
|
@@ -274,20 +287,12 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
|
|
|
274
287
|
};
|
|
275
288
|
}
|
|
276
289
|
|
|
277
|
-
/**
|
|
278
|
-
* Map credential document with legacy fields for findCredentialById
|
|
279
|
-
* Includes _id and user fields for backward compatibility
|
|
280
|
-
* Matches MongoDB repository format
|
|
281
|
-
* @private
|
|
282
|
-
*/
|
|
283
290
|
_mapCredentialById(doc) {
|
|
284
291
|
const data = doc?.data || {};
|
|
285
292
|
const id = fromObjectId(doc?._id);
|
|
286
|
-
const userId =
|
|
293
|
+
const userId = doc?.userId;
|
|
287
294
|
return {
|
|
288
|
-
_id: id,
|
|
289
295
|
id,
|
|
290
|
-
user: userId,
|
|
291
296
|
userId,
|
|
292
297
|
externalId: doc?.externalId ?? null,
|
|
293
298
|
authIsValid: doc?.authIsValid ?? null,
|
|
@@ -297,4 +302,3 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
|
|
|
297
302
|
}
|
|
298
303
|
|
|
299
304
|
module.exports = { CredentialRepositoryDocumentDB };
|
|
300
|
-
|
|
@@ -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.
|
|
103
|
-
throw new Error('
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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:
|
|
137
|
-
externalId:
|
|
138
|
-
|
|
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
|
|
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
|
-
|
|
232
|
-
|
|
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:
|
|
248
|
-
externalId:
|
|
249
|
-
|
|
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
|
-
|
|
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.
|
|
122
|
-
throw new Error('
|
|
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
|
-
|
|
128
|
-
|
|
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 {
|
|
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(
|
|
148
|
-
externalId:
|
|
149
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
253
|
-
externalId:
|
|
254
|
-
|
|
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
|
|