@friggframework/core 2.0.0-next.53 → 2.0.0-next.55
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/CLAUDE.md +2 -1
- package/application/commands/credential-commands.js +1 -1
- package/application/commands/integration-commands.js +1 -1
- package/application/index.js +1 -1
- package/core/create-handler.js +12 -0
- package/credential/repositories/credential-repository-documentdb.js +304 -0
- package/credential/repositories/credential-repository-factory.js +8 -1
- 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/config.js +4 -4
- package/database/documentdb-encryption-service.js +330 -0
- package/database/documentdb-utils.js +136 -0
- package/database/encryption/README.md +50 -1
- package/database/encryption/documentdb-encryption-service.md +3270 -0
- package/database/encryption/encryption-schema-registry.js +46 -0
- package/database/prisma.js +7 -47
- package/database/repositories/health-check-repository-documentdb.js +134 -0
- package/database/repositories/health-check-repository-factory.js +6 -1
- package/database/repositories/health-check-repository-interface.js +29 -34
- package/database/repositories/health-check-repository-mongodb.js +1 -3
- package/database/use-cases/check-database-state-use-case.js +3 -3
- package/database/use-cases/run-database-migration-use-case.js +6 -4
- package/database/use-cases/trigger-database-migration-use-case.js +2 -2
- package/database/utils/mongodb-schema-init.js +5 -5
- package/database/utils/prisma-runner.js +15 -9
- package/errors/client-safe-error.js +26 -0
- package/errors/fetch-error.js +2 -1
- package/errors/index.js +2 -0
- package/generated/prisma-mongodb/edge.js +3 -3
- package/generated/prisma-mongodb/index.d.ts +10 -4
- package/generated/prisma-mongodb/index.js +3 -3
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +1 -3
- package/generated/prisma-mongodb/wasm.js +2 -2
- package/generated/prisma-postgresql/edge.js +3 -3
- package/generated/prisma-postgresql/index.d.ts +10 -4
- package/generated/prisma-postgresql/index.js +3 -3
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +1 -3
- package/generated/prisma-postgresql/wasm.js +2 -2
- package/handlers/routers/db-migration.js +2 -3
- package/handlers/routers/health.js +0 -3
- package/handlers/workers/db-migration.js +8 -8
- package/integrations/integration-router.js +6 -6
- package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
- package/integrations/repositories/integration-mapping-repository-factory.js +8 -1
- package/integrations/repositories/integration-repository-documentdb.js +210 -0
- package/integrations/repositories/integration-repository-factory.js +8 -1
- package/integrations/repositories/process-repository-documentdb.js +243 -0
- package/integrations/repositories/process-repository-factory.js +8 -1
- package/modules/repositories/module-repository-documentdb.js +307 -0
- package/modules/repositories/module-repository-factory.js +8 -1
- package/package.json +5 -5
- package/prisma-mongodb/schema.prisma +1 -3
- package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +69 -0
- package/prisma-postgresql/schema.prisma +1 -3
- package/syncs/repositories/sync-repository-documentdb.js +240 -0
- package/syncs/repositories/sync-repository-factory.js +6 -1
- package/token/repositories/token-repository-documentdb.js +137 -0
- package/token/repositories/token-repository-factory.js +8 -1
- 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 +432 -0
- package/user/repositories/user-repository-factory.js +6 -1
- 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/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
- package/websocket/repositories/websocket-connection-repository-factory.js +8 -1
package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Warnings:
|
|
3
|
+
|
|
4
|
+
- You are about to drop the column `subType` on the `Credential` table. All the data in the column will be lost.
|
|
5
|
+
- You are about to drop the column `subType` on the `Entity` table. All the data in the column will be lost.
|
|
6
|
+
- A unique constraint covering the columns `[username,appUserId]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
|
7
|
+
|
|
8
|
+
*/
|
|
9
|
+
-- DropIndex
|
|
10
|
+
DROP INDEX "User_appOrgId_key";
|
|
11
|
+
|
|
12
|
+
-- DropIndex
|
|
13
|
+
DROP INDEX "User_email_key";
|
|
14
|
+
|
|
15
|
+
-- DropIndex
|
|
16
|
+
DROP INDEX "User_username_key";
|
|
17
|
+
|
|
18
|
+
-- AlterTable
|
|
19
|
+
ALTER TABLE "Credential" DROP COLUMN "subType";
|
|
20
|
+
|
|
21
|
+
-- AlterTable
|
|
22
|
+
ALTER TABLE "Entity" DROP COLUMN "subType";
|
|
23
|
+
|
|
24
|
+
-- CreateTable
|
|
25
|
+
CREATE TABLE "Process" (
|
|
26
|
+
"id" SERIAL NOT NULL,
|
|
27
|
+
"userId" INTEGER NOT NULL,
|
|
28
|
+
"integrationId" INTEGER NOT NULL,
|
|
29
|
+
"name" TEXT NOT NULL,
|
|
30
|
+
"type" TEXT NOT NULL,
|
|
31
|
+
"state" TEXT NOT NULL,
|
|
32
|
+
"context" JSONB NOT NULL DEFAULT '{}',
|
|
33
|
+
"results" JSONB NOT NULL DEFAULT '{}',
|
|
34
|
+
"parentProcessId" INTEGER,
|
|
35
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
36
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
37
|
+
|
|
38
|
+
CONSTRAINT "Process_pkey" PRIMARY KEY ("id")
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
-- CreateIndex
|
|
42
|
+
CREATE INDEX "Process_userId_idx" ON "Process"("userId");
|
|
43
|
+
|
|
44
|
+
-- CreateIndex
|
|
45
|
+
CREATE INDEX "Process_integrationId_idx" ON "Process"("integrationId");
|
|
46
|
+
|
|
47
|
+
-- CreateIndex
|
|
48
|
+
CREATE INDEX "Process_type_idx" ON "Process"("type");
|
|
49
|
+
|
|
50
|
+
-- CreateIndex
|
|
51
|
+
CREATE INDEX "Process_state_idx" ON "Process"("state");
|
|
52
|
+
|
|
53
|
+
-- CreateIndex
|
|
54
|
+
CREATE INDEX "Process_name_idx" ON "Process"("name");
|
|
55
|
+
|
|
56
|
+
-- CreateIndex
|
|
57
|
+
CREATE INDEX "Process_parentProcessId_idx" ON "Process"("parentProcessId");
|
|
58
|
+
|
|
59
|
+
-- CreateIndex
|
|
60
|
+
CREATE UNIQUE INDEX "User_username_appUserId_key" ON "User"("username", "appUserId");
|
|
61
|
+
|
|
62
|
+
-- AddForeignKey
|
|
63
|
+
ALTER TABLE "Process" ADD CONSTRAINT "Process_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
64
|
+
|
|
65
|
+
-- AddForeignKey
|
|
66
|
+
ALTER TABLE "Process" ADD CONSTRAINT "Process_integrationId_fkey" FOREIGN KEY ("integrationId") REFERENCES "Integration"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
67
|
+
|
|
68
|
+
-- AddForeignKey
|
|
69
|
+
ALTER TABLE "Process" ADD CONSTRAINT "Process_parentProcessId_fkey" FOREIGN KEY ("parentProcessId") REFERENCES "Process"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
const { prisma } = require('../../database/prisma');
|
|
2
|
+
const {
|
|
3
|
+
toObjectId,
|
|
4
|
+
fromObjectId,
|
|
5
|
+
findMany,
|
|
6
|
+
findOne,
|
|
7
|
+
insertOne,
|
|
8
|
+
updateOne,
|
|
9
|
+
deleteOne,
|
|
10
|
+
} = require('../../database/documentdb-utils');
|
|
11
|
+
const { SyncRepositoryInterface } = require('./sync-repository-interface');
|
|
12
|
+
|
|
13
|
+
class SyncRepositoryDocumentDB extends SyncRepositoryInterface {
|
|
14
|
+
constructor() {
|
|
15
|
+
super();
|
|
16
|
+
this.prisma = prisma;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async getSyncObject(name, dataIdentifier, entity) {
|
|
20
|
+
const pipeline = [
|
|
21
|
+
{
|
|
22
|
+
$match: {
|
|
23
|
+
name,
|
|
24
|
+
dataIdentifiers: {
|
|
25
|
+
$elemMatch: {
|
|
26
|
+
idData: dataIdentifier,
|
|
27
|
+
entityId: toObjectId(entity),
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
$limit: 2,
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const result = await this.prisma.$runCommandRaw({
|
|
38
|
+
aggregate: 'Sync',
|
|
39
|
+
pipeline,
|
|
40
|
+
cursor: {},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const syncList = result?.cursor?.firstBatch || [];
|
|
44
|
+
|
|
45
|
+
if (syncList.length === 1) {
|
|
46
|
+
const doc = syncList[0];
|
|
47
|
+
return this._mapSync(doc);
|
|
48
|
+
} else if (syncList.length === 0) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
throw new Error(
|
|
52
|
+
`There are multiple sync objects with the name ${name}, for entities [${syncList[0]?.entities}] [${syncList[1]?.entities}]`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async upsertSync(filter, syncData) {
|
|
57
|
+
const query = this._convertFilter(filter);
|
|
58
|
+
const existing = await findOne(this.prisma, 'Sync', query);
|
|
59
|
+
|
|
60
|
+
const now = new Date();
|
|
61
|
+
const documentData = this._prepareSyncData(syncData, now);
|
|
62
|
+
|
|
63
|
+
if (existing) {
|
|
64
|
+
await updateOne(
|
|
65
|
+
this.prisma,
|
|
66
|
+
'Sync',
|
|
67
|
+
{ _id: existing._id },
|
|
68
|
+
{
|
|
69
|
+
$set: documentData,
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
const updated = await findOne(this.prisma, 'Sync', { _id: existing._id });
|
|
73
|
+
return this._mapSync(updated);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const insertedId = await insertOne(this.prisma, 'Sync', {
|
|
77
|
+
...documentData,
|
|
78
|
+
createdAt: now,
|
|
79
|
+
});
|
|
80
|
+
const created = await findOne(this.prisma, 'Sync', { _id: insertedId });
|
|
81
|
+
return this._mapSync(created);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async updateSync(id, updates) {
|
|
85
|
+
const objectId = toObjectId(id);
|
|
86
|
+
if (!objectId) return null;
|
|
87
|
+
const documentData = this._prepareSyncData(updates, new Date());
|
|
88
|
+
await updateOne(
|
|
89
|
+
this.prisma,
|
|
90
|
+
'Sync',
|
|
91
|
+
{ _id: objectId },
|
|
92
|
+
{
|
|
93
|
+
$set: documentData,
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
const updated = await findOne(this.prisma, 'Sync', { _id: objectId });
|
|
97
|
+
return updated ? this._mapSync(updated) : null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async addDataIdentifier(syncId, dataIdentifier) {
|
|
101
|
+
const syncObjectId = toObjectId(syncId);
|
|
102
|
+
if (!syncObjectId) return null;
|
|
103
|
+
const doc = await findOne(this.prisma, 'Sync', { _id: syncObjectId });
|
|
104
|
+
if (!doc) return null;
|
|
105
|
+
|
|
106
|
+
const identifiers = Array.isArray(doc.dataIdentifiers) ? [...doc.dataIdentifiers] : [];
|
|
107
|
+
identifiers.push({
|
|
108
|
+
syncId: syncObjectId,
|
|
109
|
+
entityId: toObjectId(dataIdentifier.entity),
|
|
110
|
+
idData: dataIdentifier.id,
|
|
111
|
+
hash: dataIdentifier.hash,
|
|
112
|
+
createdAt: new Date(),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
await updateOne(
|
|
116
|
+
this.prisma,
|
|
117
|
+
'Sync',
|
|
118
|
+
{ _id: syncObjectId },
|
|
119
|
+
{
|
|
120
|
+
$set: {
|
|
121
|
+
dataIdentifiers: identifiers,
|
|
122
|
+
updatedAt: new Date(),
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const updated = await findOne(this.prisma, 'Sync', { _id: syncObjectId });
|
|
128
|
+
return updated ? this._mapSync(updated) : null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
getEntityObjIdForEntityIdFromObject(syncObj, entityId) {
|
|
132
|
+
if (!syncObj || !Array.isArray(syncObj.dataIdentifiers)) {
|
|
133
|
+
throw new Error('Sync object must include dataIdentifiers');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const entry = syncObj.dataIdentifiers.find(
|
|
137
|
+
(identifier) => fromObjectId(identifier.entityId) === String(entityId)
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (entry) {
|
|
141
|
+
return entry.idData;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Sync object ${syncObj.id} does not contain a data identifier for entity ${entityId}`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async findSyncs(filter) {
|
|
150
|
+
const query = this._convertFilter(filter);
|
|
151
|
+
const docs = await findMany(this.prisma, 'Sync', query);
|
|
152
|
+
return docs.map((doc) => this._mapSync(doc));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async findOneSync(filter) {
|
|
156
|
+
const query = this._convertFilter(filter);
|
|
157
|
+
const doc = await findOne(this.prisma, 'Sync', query);
|
|
158
|
+
return doc ? this._mapSync(doc) : null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async deleteSync(id) {
|
|
162
|
+
const objectId = toObjectId(id);
|
|
163
|
+
if (!objectId) return { acknowledged: true, deletedCount: 0 };
|
|
164
|
+
const result = await deleteOne(this.prisma, 'Sync', { _id: objectId });
|
|
165
|
+
const deleted = result?.n ?? 0;
|
|
166
|
+
return { acknowledged: true, deletedCount: deleted };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
_convertFilter(filter = {}) {
|
|
170
|
+
const query = { ...filter };
|
|
171
|
+
if (filter._id || filter.id) {
|
|
172
|
+
const idObj = toObjectId(filter._id || filter.id);
|
|
173
|
+
if (idObj) query._id = idObj;
|
|
174
|
+
delete query._id;
|
|
175
|
+
delete query.id;
|
|
176
|
+
}
|
|
177
|
+
if (filter.integrationId) {
|
|
178
|
+
query.integrationId = toObjectId(filter.integrationId);
|
|
179
|
+
}
|
|
180
|
+
if (filter.entities) {
|
|
181
|
+
query.entityIds = (filter.entities || []).map((id) => toObjectId(id)).filter(Boolean);
|
|
182
|
+
delete query.entities;
|
|
183
|
+
}
|
|
184
|
+
return query;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
_prepareSyncData(data = {}, timestamp) {
|
|
188
|
+
const prepared = {};
|
|
189
|
+
if (data.integrationId !== undefined) {
|
|
190
|
+
prepared.integrationId = toObjectId(data.integrationId);
|
|
191
|
+
}
|
|
192
|
+
if (data.entities !== undefined || data.entityIds !== undefined) {
|
|
193
|
+
const list = data.entities !== undefined ? data.entities : data.entityIds;
|
|
194
|
+
prepared.entityIds = (list || []).map((id) => toObjectId(id)).filter(Boolean);
|
|
195
|
+
}
|
|
196
|
+
if (data.hash !== undefined) prepared.hash = data.hash;
|
|
197
|
+
if (data.name !== undefined) prepared.name = data.name;
|
|
198
|
+
if (data.context !== undefined) prepared.context = data.context;
|
|
199
|
+
if (data.results !== undefined) prepared.results = data.results;
|
|
200
|
+
if (timestamp) prepared.updatedAt = timestamp;
|
|
201
|
+
if (data.dataIdentifiers !== undefined) {
|
|
202
|
+
prepared.dataIdentifiers = (data.dataIdentifiers || []).map((identifier) => ({
|
|
203
|
+
syncId: toObjectId(identifier.syncId),
|
|
204
|
+
entityId: toObjectId(identifier.entityId),
|
|
205
|
+
idData: identifier.idData,
|
|
206
|
+
hash: identifier.hash,
|
|
207
|
+
createdAt: identifier.createdAt ? new Date(identifier.createdAt) : new Date(),
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
return prepared;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
_mapSync(doc) {
|
|
214
|
+
if (!doc) return null;
|
|
215
|
+
return {
|
|
216
|
+
id: fromObjectId(doc._id),
|
|
217
|
+
integrationId: doc.integrationId ? fromObjectId(doc.integrationId) : null,
|
|
218
|
+
entities: Array.isArray(doc.entityIds)
|
|
219
|
+
? doc.entityIds.map((id) => fromObjectId(id))
|
|
220
|
+
: [],
|
|
221
|
+
entityIds: Array.isArray(doc.entityIds)
|
|
222
|
+
? doc.entityIds.map((id) => fromObjectId(id))
|
|
223
|
+
: [],
|
|
224
|
+
hash: doc.hash ?? null,
|
|
225
|
+
name: doc.name ?? null,
|
|
226
|
+
dataIdentifiers: Array.isArray(doc.dataIdentifiers)
|
|
227
|
+
? doc.dataIdentifiers.map((identifier) => ({
|
|
228
|
+
syncId: identifier.syncId ? fromObjectId(identifier.syncId) : null,
|
|
229
|
+
entityId: identifier.entityId ? fromObjectId(identifier.entityId) : null,
|
|
230
|
+
idData: identifier.idData,
|
|
231
|
+
hash: identifier.hash,
|
|
232
|
+
}))
|
|
233
|
+
: [],
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
module.exports = { SyncRepositoryDocumentDB };
|
|
239
|
+
|
|
240
|
+
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { SyncRepositoryMongo } = require('./sync-repository-mongo');
|
|
2
2
|
const { SyncRepositoryPostgres } = require('./sync-repository-postgres');
|
|
3
|
+
const { SyncRepositoryDocumentDB } = require('./sync-repository-documentdb');
|
|
3
4
|
const config = require('../../database/config');
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -23,9 +24,12 @@ function createSyncRepository() {
|
|
|
23
24
|
case 'postgresql':
|
|
24
25
|
return new SyncRepositoryPostgres();
|
|
25
26
|
|
|
27
|
+
case 'documentdb':
|
|
28
|
+
return new SyncRepositoryDocumentDB();
|
|
29
|
+
|
|
26
30
|
default:
|
|
27
31
|
throw new Error(
|
|
28
|
-
`Unsupported database type: ${dbType}. Supported values: 'mongodb', 'postgresql'`
|
|
32
|
+
`Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
|
|
29
33
|
);
|
|
30
34
|
}
|
|
31
35
|
}
|
|
@@ -35,4 +39,5 @@ module.exports = {
|
|
|
35
39
|
// Export adapters for direct testing
|
|
36
40
|
SyncRepositoryMongo,
|
|
37
41
|
SyncRepositoryPostgres,
|
|
42
|
+
SyncRepositoryDocumentDB,
|
|
38
43
|
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const { prisma } = require('../../database/prisma');
|
|
2
|
+
const bcrypt = require('bcryptjs');
|
|
3
|
+
const {
|
|
4
|
+
toObjectId,
|
|
5
|
+
fromObjectId,
|
|
6
|
+
findMany,
|
|
7
|
+
findOne,
|
|
8
|
+
insertOne,
|
|
9
|
+
deleteOne,
|
|
10
|
+
deleteMany,
|
|
11
|
+
} = require('../../database/documentdb-utils');
|
|
12
|
+
const { TokenRepositoryInterface } = require('./token-repository-interface');
|
|
13
|
+
const { ClientSafeError } = require('../../errors');
|
|
14
|
+
|
|
15
|
+
const BCRYPT_ROUNDS = 10;
|
|
16
|
+
|
|
17
|
+
class TokenRepositoryDocumentDB extends TokenRepositoryInterface {
|
|
18
|
+
constructor() {
|
|
19
|
+
super();
|
|
20
|
+
this.prisma = prisma;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async createTokenWithExpire(userId, rawToken, minutes) {
|
|
24
|
+
const tokenHash = await bcrypt.hash(rawToken, BCRYPT_ROUNDS);
|
|
25
|
+
const expires = new Date(Date.now() + minutes * 60000);
|
|
26
|
+
const now = new Date();
|
|
27
|
+
const document = {
|
|
28
|
+
token: tokenHash,
|
|
29
|
+
expires,
|
|
30
|
+
userId: toObjectId(userId),
|
|
31
|
+
created: now,
|
|
32
|
+
};
|
|
33
|
+
const insertedId = await insertOne(this.prisma, 'Token', document);
|
|
34
|
+
const created = await findOne(this.prisma, 'Token', {
|
|
35
|
+
_id: insertedId,
|
|
36
|
+
});
|
|
37
|
+
return this._mapToken(created);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async validateAndGetToken(tokenObj) {
|
|
41
|
+
const objectId = toObjectId(tokenObj.id);
|
|
42
|
+
if (!objectId) {
|
|
43
|
+
throw new ClientSafeError(
|
|
44
|
+
'Invalid Token: Token does not exist',
|
|
45
|
+
401
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const record = await findOne(this.prisma, 'Token', { _id: objectId });
|
|
49
|
+
if (!record) {
|
|
50
|
+
throw new ClientSafeError(
|
|
51
|
+
'Invalid Token: Token does not exist',
|
|
52
|
+
401
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const isValid = await bcrypt.compare(tokenObj.token, record.token);
|
|
56
|
+
if (!isValid) {
|
|
57
|
+
throw new ClientSafeError(
|
|
58
|
+
'Invalid Token: Token does not match',
|
|
59
|
+
401
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (record.expires && new Date(record.expires) < new Date()) {
|
|
63
|
+
throw new ClientSafeError('Invalid Token: Token is expired', 401);
|
|
64
|
+
}
|
|
65
|
+
return this._mapToken(record);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async findTokenById(tokenId) {
|
|
69
|
+
const objectId = toObjectId(tokenId);
|
|
70
|
+
if (!objectId) return null;
|
|
71
|
+
const record = await findOne(this.prisma, 'Token', { _id: objectId });
|
|
72
|
+
return record ? this._mapToken(record) : null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async findTokensByUserId(userId) {
|
|
76
|
+
const objectId = toObjectId(userId);
|
|
77
|
+
const filter = objectId ? { userId: objectId } : {};
|
|
78
|
+
const records = await findMany(this.prisma, 'Token', filter);
|
|
79
|
+
return records.map((record) => this._mapToken(record));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async deleteToken(tokenId) {
|
|
83
|
+
const objectId = toObjectId(tokenId);
|
|
84
|
+
if (!objectId) return { acknowledged: true, deletedCount: 0 };
|
|
85
|
+
const result = await deleteOne(this.prisma, 'Token', { _id: objectId });
|
|
86
|
+
const deleted = result?.n ?? 0;
|
|
87
|
+
return { acknowledged: true, deletedCount: deleted };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async deleteExpiredTokens() {
|
|
91
|
+
const result = await deleteMany(this.prisma, 'Token', {
|
|
92
|
+
expires: { $lt: new Date() },
|
|
93
|
+
});
|
|
94
|
+
const deleted = result?.n ?? 0;
|
|
95
|
+
return { acknowledged: true, deletedCount: deleted };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async deleteTokensByUserId(userId) {
|
|
99
|
+
const objectId = toObjectId(userId);
|
|
100
|
+
if (!objectId) return { acknowledged: true, deletedCount: 0 };
|
|
101
|
+
const result = await deleteMany(this.prisma, 'Token', {
|
|
102
|
+
userId: objectId,
|
|
103
|
+
});
|
|
104
|
+
const deleted = result?.n ?? 0;
|
|
105
|
+
return { acknowledged: true, deletedCount: deleted };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
createJSONToken(token, rawToken) {
|
|
109
|
+
return JSON.stringify({
|
|
110
|
+
id: token.id,
|
|
111
|
+
token: rawToken,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
createBase64BufferToken(token, rawToken) {
|
|
116
|
+
const jsonVal = this.createJSONToken(token, rawToken);
|
|
117
|
+
return Buffer.from(jsonVal).toString('base64');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getJSONTokenFromBase64BufferToken(buffer) {
|
|
121
|
+
const tokenStr = Buffer.from(buffer.trim(), 'base64').toString('ascii');
|
|
122
|
+
return JSON.parse(tokenStr);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
_mapToken(record) {
|
|
126
|
+
if (!record) return null;
|
|
127
|
+
return {
|
|
128
|
+
id: fromObjectId(record._id),
|
|
129
|
+
token: record.token,
|
|
130
|
+
expires: record.expires ? new Date(record.expires) : null,
|
|
131
|
+
userId: fromObjectId(record.userId),
|
|
132
|
+
created: record.created ? new Date(record.created) : null,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = { TokenRepositoryDocumentDB };
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
const { TokenRepositoryMongo } = require('./token-repository-mongo');
|
|
2
2
|
const { TokenRepositoryPostgres } = require('./token-repository-postgres');
|
|
3
|
+
const {
|
|
4
|
+
TokenRepositoryDocumentDB,
|
|
5
|
+
} = require('./token-repository-documentdb');
|
|
3
6
|
const config = require('../../database/config');
|
|
4
7
|
|
|
5
8
|
/**
|
|
@@ -18,9 +21,12 @@ function createTokenRepository() {
|
|
|
18
21
|
case 'postgresql':
|
|
19
22
|
return new TokenRepositoryPostgres();
|
|
20
23
|
|
|
24
|
+
case 'documentdb':
|
|
25
|
+
return new TokenRepositoryDocumentDB();
|
|
26
|
+
|
|
21
27
|
default:
|
|
22
28
|
throw new Error(
|
|
23
|
-
`Unsupported database type: ${dbType}. Supported values: 'mongodb', 'postgresql'`
|
|
29
|
+
`Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
|
|
24
30
|
);
|
|
25
31
|
}
|
|
26
32
|
}
|
|
@@ -30,4 +36,5 @@ module.exports = {
|
|
|
30
36
|
// Export adapters for direct testing
|
|
31
37
|
TokenRepositoryMongo,
|
|
32
38
|
TokenRepositoryPostgres,
|
|
39
|
+
TokenRepositoryDocumentDB,
|
|
33
40
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { prisma } = require('../../database/prisma');
|
|
2
2
|
const bcrypt = require('bcryptjs');
|
|
3
3
|
const { TokenRepositoryInterface } = require('./token-repository-interface');
|
|
4
|
+
const { ClientSafeError } = require('../../errors');
|
|
4
5
|
|
|
5
6
|
const BCRYPT_ROUNDS = 10;
|
|
6
7
|
|
|
@@ -58,7 +59,10 @@ class TokenRepositoryMongo extends TokenRepositoryInterface {
|
|
|
58
59
|
});
|
|
59
60
|
|
|
60
61
|
if (!sessionToken) {
|
|
61
|
-
throw new
|
|
62
|
+
throw new ClientSafeError(
|
|
63
|
+
'Invalid Token: Token does not exist',
|
|
64
|
+
401
|
|
65
|
+
);
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
// Verify token hash matches
|
|
@@ -67,7 +71,10 @@ class TokenRepositoryMongo extends TokenRepositoryInterface {
|
|
|
67
71
|
sessionToken.token
|
|
68
72
|
);
|
|
69
73
|
if (!isValid) {
|
|
70
|
-
throw new
|
|
74
|
+
throw new ClientSafeError(
|
|
75
|
+
'Invalid Token: Token does not match',
|
|
76
|
+
401
|
|
77
|
+
);
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
// Check if token is expired
|
|
@@ -75,7 +82,7 @@ class TokenRepositoryMongo extends TokenRepositoryInterface {
|
|
|
75
82
|
sessionToken.expires &&
|
|
76
83
|
new Date(sessionToken.expires) < new Date()
|
|
77
84
|
) {
|
|
78
|
-
throw new
|
|
85
|
+
throw new ClientSafeError('Invalid Token: Token is expired', 401);
|
|
79
86
|
}
|
|
80
87
|
|
|
81
88
|
return sessionToken;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { prisma } = require('../../database/prisma');
|
|
2
2
|
const bcrypt = require('bcryptjs');
|
|
3
3
|
const { TokenRepositoryInterface } = require('./token-repository-interface');
|
|
4
|
+
const { ClientSafeError } = require('../../errors');
|
|
4
5
|
|
|
5
6
|
const BCRYPT_ROUNDS = 10;
|
|
6
7
|
|
|
@@ -92,7 +93,10 @@ class TokenRepositoryPostgres extends TokenRepositoryInterface {
|
|
|
92
93
|
});
|
|
93
94
|
|
|
94
95
|
if (!sessionToken) {
|
|
95
|
-
throw new
|
|
96
|
+
throw new ClientSafeError(
|
|
97
|
+
'Invalid Token: Token does not exist',
|
|
98
|
+
401
|
|
99
|
+
);
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
// Verify token hash matches
|
|
@@ -101,7 +105,10 @@ class TokenRepositoryPostgres extends TokenRepositoryInterface {
|
|
|
101
105
|
sessionToken.token
|
|
102
106
|
);
|
|
103
107
|
if (!isValid) {
|
|
104
|
-
throw new
|
|
108
|
+
throw new ClientSafeError(
|
|
109
|
+
'Invalid Token: Token does not match',
|
|
110
|
+
401
|
|
111
|
+
);
|
|
105
112
|
}
|
|
106
113
|
|
|
107
114
|
// Check if token is expired
|
|
@@ -109,7 +116,7 @@ class TokenRepositoryPostgres extends TokenRepositoryInterface {
|
|
|
109
116
|
sessionToken.expires &&
|
|
110
117
|
new Date(sessionToken.expires) < new Date()
|
|
111
118
|
) {
|
|
112
|
-
throw new
|
|
119
|
+
throw new ClientSafeError('Invalid Token: Token is expired', 401);
|
|
113
120
|
}
|
|
114
121
|
|
|
115
122
|
return this._convertTokenIds(sessionToken);
|