@friggframework/core 2.0.0-next.70 → 2.0.0-next.72
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/assertions/index.js +0 -3
- package/core/create-handler.js +1 -1
- package/database/index.js +5 -45
- package/database/repositories/health-check-repository-documentdb.js +13 -9
- package/database/repositories/health-check-repository-mongodb.js +22 -22
- package/database/utils/mongodb-collection-utils.js +15 -12
- package/database/utils/mongodb-schema-init.js +5 -3
- package/handlers/app-handler-helpers.js +1 -0
- package/index.js +4 -24
- package/modules/index.js +0 -2
- package/modules/module.js +12 -5
- package/modules/requester/oauth-2.js +17 -5
- package/package.json +7 -6
- package/syncs/manager.js +2 -2
- package/types/associations/index.d.ts +0 -17
- package/types/database/index.d.ts +10 -2
- package/types/encrypt/index.d.ts +5 -3
- package/types/integrations/index.d.ts +1 -2
- package/types/module-plugin/index.d.ts +14 -12
- package/types/syncs/index.d.ts +0 -15
- package/assertions/is-equal.js +0 -17
- package/associations/model.js +0 -54
- package/database/models/IndividualUser.js +0 -76
- package/database/models/OrganizationUser.js +0 -29
- package/database/models/UserModel.js +0 -7
- package/database/models/WebsocketConnection.js +0 -55
- package/database/models/readme.md +0 -1
- package/database/mongoose.js +0 -5
- package/database/repositories/health-check-repository.js +0 -108
- package/encrypt/test-encrypt.js +0 -105
- package/modules/entity.js +0 -46
- package/syncs/model.js +0 -62
package/assertions/index.js
CHANGED
package/core/create-handler.js
CHANGED
|
@@ -29,7 +29,7 @@ const createHandler = (optionByName = {}) => {
|
|
|
29
29
|
// If enabled (i.e. if SECRET_ARN is set in process.env) Fetch secrets from AWS Secrets Manager, and set them as environment variables.
|
|
30
30
|
await secretsToEnv();
|
|
31
31
|
|
|
32
|
-
// Helps
|
|
32
|
+
// Helps reuse the database connection. Lowers response times.
|
|
33
33
|
context.callbackWaitsForEmptyEventLoop = false;
|
|
34
34
|
|
|
35
35
|
// Run the Lambda
|
package/database/index.js
CHANGED
|
@@ -1,65 +1,25 @@
|
|
|
1
|
-
//todo: probably most of this file content can be removed
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Database Module Index
|
|
5
|
-
* Exports
|
|
3
|
+
* Exports Prisma client, connection utilities, and repositories
|
|
6
4
|
*
|
|
7
5
|
* Note: Frigg uses the Repository pattern for data access.
|
|
8
|
-
*
|
|
6
|
+
* Use repositories for data operations:
|
|
9
7
|
* - SyncRepository (syncs/sync-repository.js)
|
|
10
8
|
* - IntegrationRepository (integrations/integration-repository.js)
|
|
11
9
|
* - CredentialRepository (credential/credential-repository.js)
|
|
12
10
|
* etc.
|
|
13
11
|
*/
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
let _mongoose = null;
|
|
17
|
-
let _IndividualUser = null;
|
|
18
|
-
let _OrganizationUser = null;
|
|
19
|
-
let _UserModel = null;
|
|
20
|
-
let _WebsocketConnection = null;
|
|
21
|
-
|
|
22
|
-
// Prisma exports (always available)
|
|
23
|
-
const { prisma } = require('./prisma');
|
|
13
|
+
const { prisma, connectPrisma, disconnectPrisma } = require('./prisma');
|
|
24
14
|
const { TokenRepository } = require('../token/repositories/token-repository');
|
|
25
15
|
const {
|
|
26
16
|
WebsocketConnectionRepository,
|
|
27
17
|
} = require('../websocket/repositories/websocket-connection-repository');
|
|
28
18
|
|
|
29
19
|
module.exports = {
|
|
30
|
-
// Lazy-loaded mongoose exports (only load when accessed)
|
|
31
|
-
get mongoose() {
|
|
32
|
-
if (!_mongoose) {
|
|
33
|
-
_mongoose = require('./mongoose').mongoose;
|
|
34
|
-
}
|
|
35
|
-
return _mongoose;
|
|
36
|
-
},
|
|
37
|
-
get IndividualUser() {
|
|
38
|
-
if (!_IndividualUser) {
|
|
39
|
-
_IndividualUser = require('./models/IndividualUser').IndividualUser;
|
|
40
|
-
}
|
|
41
|
-
return _IndividualUser;
|
|
42
|
-
},
|
|
43
|
-
get OrganizationUser() {
|
|
44
|
-
if (!_OrganizationUser) {
|
|
45
|
-
_OrganizationUser = require('./models/OrganizationUser').OrganizationUser;
|
|
46
|
-
}
|
|
47
|
-
return _OrganizationUser;
|
|
48
|
-
},
|
|
49
|
-
get UserModel() {
|
|
50
|
-
if (!_UserModel) {
|
|
51
|
-
_UserModel = require('./models/UserModel').UserModel;
|
|
52
|
-
}
|
|
53
|
-
return _UserModel;
|
|
54
|
-
},
|
|
55
|
-
get WebsocketConnection() {
|
|
56
|
-
if (!_WebsocketConnection) {
|
|
57
|
-
_WebsocketConnection = require('./models/WebsocketConnection').WebsocketConnection;
|
|
58
|
-
}
|
|
59
|
-
return _WebsocketConnection;
|
|
60
|
-
},
|
|
61
|
-
// Prisma (always available)
|
|
62
20
|
prisma,
|
|
21
|
+
connectPrisma,
|
|
22
|
+
disconnectPrisma,
|
|
63
23
|
TokenRepository,
|
|
64
24
|
WebsocketConnectionRepository,
|
|
65
25
|
};
|
|
@@ -49,17 +49,21 @@ class HealthCheckRepositoryDocumentDB extends HealthCheckRepositoryInterface {
|
|
|
49
49
|
*/
|
|
50
50
|
async pingDatabase(maxTimeMS = 2000) {
|
|
51
51
|
const pingStart = Date.now();
|
|
52
|
+
let timeoutId;
|
|
52
53
|
|
|
53
|
-
const timeoutPromise = new Promise((_, reject) =>
|
|
54
|
-
setTimeout(() => reject(new Error('Database ping timeout')), maxTimeMS)
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
await Promise.race([
|
|
58
|
-
this.prisma.$runCommandRaw({ ping: 1 }),
|
|
59
|
-
timeoutPromise,
|
|
60
|
-
]);
|
|
54
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
55
|
+
timeoutId = setTimeout(() => reject(new Error('Database ping timeout')), maxTimeMS);
|
|
56
|
+
});
|
|
61
57
|
|
|
62
|
-
|
|
58
|
+
try {
|
|
59
|
+
await Promise.race([
|
|
60
|
+
this.prisma.$runCommandRaw({ ping: 1 }),
|
|
61
|
+
timeoutPromise,
|
|
62
|
+
]);
|
|
63
|
+
return Date.now() - pingStart;
|
|
64
|
+
} finally {
|
|
65
|
+
clearTimeout(timeoutId);
|
|
66
|
+
}
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
async createCredential(credentialData) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const { mongoose } = require('../mongoose');
|
|
2
1
|
const {
|
|
3
2
|
HealthCheckRepositoryInterface,
|
|
4
3
|
} = require('./health-check-repository-interface');
|
|
@@ -19,7 +18,7 @@ class HealthCheckRepositoryMongoDB extends HealthCheckRepositoryInterface {
|
|
|
19
18
|
async getDatabaseConnectionState() {
|
|
20
19
|
let isConnected = false;
|
|
21
20
|
let stateName = 'unknown';
|
|
22
|
-
|
|
21
|
+
|
|
23
22
|
try {
|
|
24
23
|
await this.prisma.$runCommandRaw({ ping: 1 });
|
|
25
24
|
isConnected = true;
|
|
@@ -29,7 +28,6 @@ class HealthCheckRepositoryMongoDB extends HealthCheckRepositoryInterface {
|
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
return {
|
|
32
|
-
readyState: isConnected ? 1 : 0,
|
|
33
31
|
readyState: isConnected ? 1 : 0,
|
|
34
32
|
stateName,
|
|
35
33
|
isConnected,
|
|
@@ -38,22 +36,21 @@ class HealthCheckRepositoryMongoDB extends HealthCheckRepositoryInterface {
|
|
|
38
36
|
|
|
39
37
|
async pingDatabase(maxTimeMS = 2000) {
|
|
40
38
|
const pingStart = Date.now();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const timeoutPromise = new Promise((_, reject) =>
|
|
44
|
-
setTimeout(() => reject(new Error('Database ping timeout')), maxTimeMS)
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
// Race between the database ping and the timeout
|
|
48
|
-
await Promise.race([
|
|
49
|
-
prisma.$queryRaw`SELECT 1`.catch(() => {
|
|
50
|
-
// For MongoDB, use runCommandRaw instead
|
|
51
|
-
return prisma.$runCommandRaw({ ping: 1 });
|
|
52
|
-
}),
|
|
53
|
-
timeoutPromise
|
|
54
|
-
]);
|
|
39
|
+
let timeoutId;
|
|
40
|
+
|
|
41
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
42
|
+
timeoutId = setTimeout(() => reject(new Error('Database ping timeout')), maxTimeMS);
|
|
43
|
+
});
|
|
55
44
|
|
|
56
|
-
|
|
45
|
+
try {
|
|
46
|
+
await Promise.race([
|
|
47
|
+
this.prisma.$runCommandRaw({ ping: 1 }),
|
|
48
|
+
timeoutPromise
|
|
49
|
+
]);
|
|
50
|
+
return Date.now() - pingStart;
|
|
51
|
+
} finally {
|
|
52
|
+
clearTimeout(timeoutId);
|
|
53
|
+
}
|
|
57
54
|
}
|
|
58
55
|
|
|
59
56
|
async createCredential(credentialData) {
|
|
@@ -69,14 +66,17 @@ class HealthCheckRepositoryMongoDB extends HealthCheckRepositoryInterface {
|
|
|
69
66
|
}
|
|
70
67
|
|
|
71
68
|
/**
|
|
69
|
+
* Get raw credential from database bypassing Prisma encryption extension.
|
|
70
|
+
* Uses findRaw() to query MongoDB directly.
|
|
72
71
|
* @param {string} id
|
|
73
72
|
* @returns {Promise<Object|null>}
|
|
74
73
|
*/
|
|
75
74
|
async getRawCredentialById(id) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
if (!id) return null;
|
|
76
|
+
const results = await this.prisma.credential.findRaw({
|
|
77
|
+
filter: { _id: { $oid: id } },
|
|
78
|
+
});
|
|
79
|
+
return results[0] || null;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
async deleteCredential(id) {
|
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
* handling the constraint that collections cannot be created inside
|
|
6
6
|
* multi-document transactions.
|
|
7
7
|
*
|
|
8
|
+
* Uses Prisma's $runCommandRaw to execute MongoDB admin commands.
|
|
9
|
+
*
|
|
8
10
|
* @see https://github.com/prisma/prisma/issues/8305
|
|
9
11
|
* @see https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations
|
|
10
12
|
*/
|
|
11
13
|
|
|
12
|
-
const {
|
|
14
|
+
const { prisma } = require('../prisma');
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Ensures a MongoDB collection exists
|
|
@@ -30,20 +32,19 @@ const { mongoose } = require('../mongoose');
|
|
|
30
32
|
*/
|
|
31
33
|
async function ensureCollectionExists(collectionName) {
|
|
32
34
|
try {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
const result = await prisma.$runCommandRaw({
|
|
36
|
+
listCollections: 1,
|
|
37
|
+
filter: { name: collectionName },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const collections = result.cursor?.firstBatch || [];
|
|
36
41
|
|
|
37
42
|
if (collections.length === 0) {
|
|
38
|
-
|
|
39
|
-
await mongoose.connection.db.createCollection(collectionName);
|
|
43
|
+
await prisma.$runCommandRaw({ create: collectionName });
|
|
40
44
|
console.log(`Created MongoDB collection: ${collectionName}`);
|
|
41
45
|
}
|
|
42
46
|
} catch (error) {
|
|
43
|
-
// Collection might already exist due to race condition, or other error
|
|
44
|
-
// Log warning but don't fail - let subsequent operations handle errors
|
|
45
47
|
if (error.codeName === 'NamespaceExists') {
|
|
46
|
-
// This is expected in race conditions, silently continue
|
|
47
48
|
return;
|
|
48
49
|
}
|
|
49
50
|
console.warn(`Error ensuring collection ${collectionName} exists:`, error.message);
|
|
@@ -73,10 +74,12 @@ async function ensureCollectionsExist(collectionNames) {
|
|
|
73
74
|
*/
|
|
74
75
|
async function collectionExists(collectionName) {
|
|
75
76
|
try {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
const result = await prisma.$runCommandRaw({
|
|
78
|
+
listCollections: 1,
|
|
79
|
+
filter: { name: collectionName },
|
|
80
|
+
});
|
|
79
81
|
|
|
82
|
+
const collections = result.cursor?.firstBatch || [];
|
|
80
83
|
return collections.length > 0;
|
|
81
84
|
} catch (error) {
|
|
82
85
|
console.error(`Error checking if collection ${collectionName} exists:`, error.message);
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* @see https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
const {
|
|
19
|
+
const { prisma } = require('../prisma');
|
|
20
20
|
const { ensureCollectionsExist } = require('./mongodb-collection-utils');
|
|
21
21
|
const { getCollectionsFromSchemaSync } = require('./prisma-schema-parser');
|
|
22
22
|
const config = require('../config');
|
|
@@ -53,8 +53,10 @@ async function initializeMongoDBSchema() {
|
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
//
|
|
57
|
-
|
|
56
|
+
// Verify database connectivity via Prisma ping
|
|
57
|
+
try {
|
|
58
|
+
await prisma.$runCommandRaw({ ping: 1 });
|
|
59
|
+
} catch (error) {
|
|
58
60
|
throw new Error(
|
|
59
61
|
'Cannot initialize MongoDB schema - database not connected. ' +
|
|
60
62
|
'Call connectPrisma() before initializeMongoDBSchema()'
|
|
@@ -32,6 +32,7 @@ const createApp = (applyMiddleware) => {
|
|
|
32
32
|
flushDebugLog(boomError);
|
|
33
33
|
res.status(statusCode).json({ error: 'Internal Server Error' });
|
|
34
34
|
} else {
|
|
35
|
+
console.warn(`[Frigg] ${req.method} ${req.path} -> ${statusCode}: ${err.message}`);
|
|
35
36
|
res.status(statusCode).json({ error: err.message });
|
|
36
37
|
}
|
|
37
38
|
});
|
package/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const {
|
|
2
|
-
expectShallowEqualDbObject,
|
|
3
2
|
get,
|
|
4
3
|
getAll,
|
|
5
4
|
verifyType,
|
|
@@ -14,17 +13,9 @@ const {
|
|
|
14
13
|
createHandler,
|
|
15
14
|
} = require('./core/index');
|
|
16
15
|
const {
|
|
17
|
-
mongoose,
|
|
18
|
-
connectToDatabase,
|
|
19
|
-
disconnectFromDatabase,
|
|
20
|
-
createObjectId,
|
|
21
|
-
IndividualUser,
|
|
22
|
-
OrganizationUser,
|
|
23
|
-
State,
|
|
24
|
-
Token,
|
|
25
|
-
UserModel,
|
|
26
|
-
WebsocketConnection,
|
|
27
16
|
prisma,
|
|
17
|
+
connectPrisma,
|
|
18
|
+
disconnectPrisma,
|
|
28
19
|
TokenRepository,
|
|
29
20
|
WebsocketConnectionRepository,
|
|
30
21
|
} = require('./database/index');
|
|
@@ -95,13 +86,10 @@ const {
|
|
|
95
86
|
const application = require('./application');
|
|
96
87
|
const utils = require('./utils');
|
|
97
88
|
|
|
98
|
-
// const {Sync } = require('./syncs/model');
|
|
99
|
-
|
|
100
89
|
const { QueuerUtil } = require('./queues');
|
|
101
90
|
|
|
102
91
|
module.exports = {
|
|
103
92
|
// assertions
|
|
104
|
-
expectShallowEqualDbObject,
|
|
105
93
|
get,
|
|
106
94
|
getAll,
|
|
107
95
|
verifyType,
|
|
@@ -116,17 +104,9 @@ module.exports = {
|
|
|
116
104
|
createHandler,
|
|
117
105
|
|
|
118
106
|
// database
|
|
119
|
-
mongoose,
|
|
120
|
-
connectToDatabase,
|
|
121
|
-
disconnectFromDatabase,
|
|
122
|
-
createObjectId,
|
|
123
|
-
IndividualUser,
|
|
124
|
-
OrganizationUser,
|
|
125
|
-
State,
|
|
126
|
-
Token,
|
|
127
|
-
UserModel,
|
|
128
|
-
WebsocketConnection,
|
|
129
107
|
prisma,
|
|
108
|
+
connectPrisma,
|
|
109
|
+
disconnectPrisma,
|
|
130
110
|
TokenRepository,
|
|
131
111
|
WebsocketConnectionRepository,
|
|
132
112
|
createUserRepository,
|
package/modules/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const { Entity } = require('./entity');
|
|
2
1
|
const { ApiKeyRequester } = require('./requester/api-key');
|
|
3
2
|
const { BasicAuthRequester } = require('./requester/basic');
|
|
4
3
|
const { OAuth2Requester } = require('./requester/oauth-2');
|
|
@@ -7,7 +6,6 @@ const { ModuleConstants } = require('./ModuleConstants');
|
|
|
7
6
|
const { ModuleFactory } = require('./module-factory');
|
|
8
7
|
|
|
9
8
|
module.exports = {
|
|
10
|
-
Entity,
|
|
11
9
|
ApiKeyRequester,
|
|
12
10
|
BasicAuthRequester,
|
|
13
11
|
OAuth2Requester,
|
package/modules/module.js
CHANGED
|
@@ -42,7 +42,9 @@ class Module extends Delegate {
|
|
|
42
42
|
const apiParams = {
|
|
43
43
|
...this.definition.env,
|
|
44
44
|
delegate: this,
|
|
45
|
-
...(this.credential?.data
|
|
45
|
+
...(this.credential?.data
|
|
46
|
+
? this.apiParamsFromCredential(this.credential.data)
|
|
47
|
+
: {}), // Handle case when credential is undefined
|
|
46
48
|
...this.apiParamsFromEntity(this.entity),
|
|
47
49
|
};
|
|
48
50
|
this.api = new this.apiClass(apiParams);
|
|
@@ -100,10 +102,15 @@ class Module extends Delegate {
|
|
|
100
102
|
this.api,
|
|
101
103
|
this.userId
|
|
102
104
|
);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
const apiParams = this.apiParamsFromCredential(this.api);
|
|
106
|
+
|
|
107
|
+
if (!apiParams.refresh_token && this.api.isRefreshable) {
|
|
108
|
+
console.warn(
|
|
109
|
+
`[Frigg] No refresh_token in apiParams for module ${this.name}.`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
Object.assign(credentialDetails.details, apiParams);
|
|
107
114
|
credentialDetails.details.authIsValid = true;
|
|
108
115
|
|
|
109
116
|
const persisted = await this.credentialRepository.upsertCredential(
|
|
@@ -30,7 +30,6 @@ const { ModuleConstants } = require('../ModuleConstants');
|
|
|
30
30
|
* await api.getTokenFromClientCredentials();
|
|
31
31
|
*/
|
|
32
32
|
class OAuth2Requester extends Requester {
|
|
33
|
-
|
|
34
33
|
static requesterType = ModuleConstants.authType.oauth2;
|
|
35
34
|
|
|
36
35
|
/**
|
|
@@ -118,6 +117,16 @@ class OAuth2Requester extends Requester {
|
|
|
118
117
|
const newRefreshToken = get(params, 'refresh_token', null);
|
|
119
118
|
if (newRefreshToken !== null) {
|
|
120
119
|
this.refresh_token = newRefreshToken;
|
|
120
|
+
} else {
|
|
121
|
+
if (this.refresh_token) {
|
|
122
|
+
console.log(
|
|
123
|
+
'[Frigg] No refresh_token in response, preserving existing'
|
|
124
|
+
);
|
|
125
|
+
} else {
|
|
126
|
+
console.log(
|
|
127
|
+
'[Frigg] Current refresh_token is null and no new refresh_token in response'
|
|
128
|
+
);
|
|
129
|
+
}
|
|
121
130
|
}
|
|
122
131
|
const accessExpiresIn = get(params, 'expires_in', null);
|
|
123
132
|
const refreshExpiresIn = get(
|
|
@@ -128,7 +137,9 @@ class OAuth2Requester extends Requester {
|
|
|
128
137
|
|
|
129
138
|
this.accessTokenExpire = new Date(Date.now() + accessExpiresIn * 1000);
|
|
130
139
|
if (refreshExpiresIn !== null) {
|
|
131
|
-
this.refreshTokenExpire = new Date(
|
|
140
|
+
this.refreshTokenExpire = new Date(
|
|
141
|
+
Date.now() + refreshExpiresIn * 1000
|
|
142
|
+
);
|
|
132
143
|
}
|
|
133
144
|
|
|
134
145
|
await this.notify(this.DLGT_TOKEN_UPDATE);
|
|
@@ -237,6 +248,7 @@ class OAuth2Requester extends Requester {
|
|
|
237
248
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
238
249
|
},
|
|
239
250
|
};
|
|
251
|
+
console.log('[Frigg] Refreshing access token with options');
|
|
240
252
|
const response = await this._post(options, false);
|
|
241
253
|
await this.setTokens(response);
|
|
242
254
|
return response;
|
|
@@ -284,7 +296,7 @@ class OAuth2Requester extends Requester {
|
|
|
284
296
|
*/
|
|
285
297
|
async refreshAuth() {
|
|
286
298
|
try {
|
|
287
|
-
console.log('[
|
|
299
|
+
console.log('[Frigg] Starting token refresh', {
|
|
288
300
|
grant_type: this.grant_type,
|
|
289
301
|
has_refresh_token: !!this.refresh_token,
|
|
290
302
|
has_client_id: !!this.client_id,
|
|
@@ -300,10 +312,10 @@ class OAuth2Requester extends Requester {
|
|
|
300
312
|
} else {
|
|
301
313
|
await this.getTokenFromClientCredentials();
|
|
302
314
|
}
|
|
303
|
-
console.log('[
|
|
315
|
+
console.log('[Frigg] Token refresh succeeded');
|
|
304
316
|
return true;
|
|
305
317
|
} catch (error) {
|
|
306
|
-
console.error('[
|
|
318
|
+
console.error('[Frigg] Token refresh failed', {
|
|
307
319
|
error_message: error?.message,
|
|
308
320
|
error_name: error?.name,
|
|
309
321
|
response_status: error?.response?.status,
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/core",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0-next.
|
|
4
|
+
"version": "2.0.0-next.72",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.588.0",
|
|
8
8
|
"@aws-sdk/client-lambda": "^3.714.0",
|
|
9
|
+
"@aws-sdk/client-scheduler": "^3.1002.0",
|
|
9
10
|
"@aws-sdk/client-sqs": "^3.588.0",
|
|
10
11
|
"@hapi/boom": "^10.0.1",
|
|
11
12
|
"bcryptjs": "^2.4.3",
|
|
@@ -20,7 +21,7 @@
|
|
|
20
21
|
"fs-extra": "^11.2.0",
|
|
21
22
|
"lodash": "4.17.21",
|
|
22
23
|
"lodash.get": "^4.4.2",
|
|
23
|
-
"
|
|
24
|
+
"mongodb": "^4.17.0",
|
|
24
25
|
"node-fetch": "^2.6.7",
|
|
25
26
|
"serverless-http": "^2.7.0",
|
|
26
27
|
"uuid": "^9.0.1"
|
|
@@ -38,9 +39,9 @@
|
|
|
38
39
|
}
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
|
-
"@friggframework/eslint-config": "2.0.0-next.
|
|
42
|
-
"@friggframework/prettier-config": "2.0.0-next.
|
|
43
|
-
"@friggframework/test": "2.0.0-next.
|
|
42
|
+
"@friggframework/eslint-config": "2.0.0-next.72",
|
|
43
|
+
"@friggframework/prettier-config": "2.0.0-next.72",
|
|
44
|
+
"@friggframework/test": "2.0.0-next.72",
|
|
44
45
|
"@prisma/client": "^6.17.0",
|
|
45
46
|
"@types/lodash": "4.17.15",
|
|
46
47
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
@@ -80,5 +81,5 @@
|
|
|
80
81
|
"publishConfig": {
|
|
81
82
|
"access": "public"
|
|
82
83
|
},
|
|
83
|
-
"gitHead": "
|
|
84
|
+
"gitHead": "d655a19777f535775f2f67b5f17511eb438b300b"
|
|
84
85
|
}
|
package/syncs/manager.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
2
|
const moment = require('moment');
|
|
3
|
-
const
|
|
3
|
+
const { ObjectId } = require('mongodb');
|
|
4
4
|
const SyncObject = require('./sync');
|
|
5
5
|
const { debug } = require('packages/logs');
|
|
6
6
|
const { get } = require('../assertions');
|
|
@@ -314,7 +314,7 @@ class SyncManager {
|
|
|
314
314
|
|
|
315
315
|
async createSyncDBObject(objArr, entities) {
|
|
316
316
|
const entityIds = entities.map(
|
|
317
|
-
(ent) => ({ $elemMatch: { $eq:
|
|
317
|
+
(ent) => ({ $elemMatch: { $eq: new ObjectId(ent) } })
|
|
318
318
|
// return {"$elemMatch": {"$eq": ent}};
|
|
319
319
|
);
|
|
320
320
|
const dataIdentifiers = [];
|
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
declare module "@friggframework/associations/model" {
|
|
2
|
-
import { Model } from "mongoose";
|
|
3
|
-
|
|
4
|
-
export class Association extends Model {
|
|
5
|
-
integrationId: string;
|
|
6
|
-
name: string;
|
|
7
|
-
type: string;
|
|
8
|
-
primaryObject: string;
|
|
9
|
-
objects: {
|
|
10
|
-
entityId: string;
|
|
11
|
-
objectType: string;
|
|
12
|
-
objId: string;
|
|
13
|
-
metadata?: object;
|
|
14
|
-
}[];
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
1
|
declare module "@friggframework/associations/association" {
|
|
19
2
|
export default class Association implements IFriggAssociation {
|
|
20
3
|
data: any;
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
declare module "@friggframework/database
|
|
2
|
-
export
|
|
1
|
+
declare module "@friggframework/database" {
|
|
2
|
+
export const prisma: any;
|
|
3
|
+
export function connectPrisma(): Promise<any>;
|
|
4
|
+
export function disconnectPrisma(): Promise<void>;
|
|
5
|
+
export class TokenRepository {
|
|
6
|
+
constructor(params: { prismaClient: any });
|
|
7
|
+
}
|
|
8
|
+
export class WebsocketConnectionRepository {
|
|
9
|
+
constructor(params: { prismaClient: any });
|
|
10
|
+
}
|
|
3
11
|
}
|
package/types/encrypt/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
declare module "@friggframework/encrypt" {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
export class Cryptor {
|
|
3
|
+
constructor(params: { shouldUseAws?: boolean });
|
|
4
|
+
encrypt(plaintext: string): Promise<string>;
|
|
5
|
+
decrypt(ciphertext: string): Promise<string>;
|
|
6
|
+
}
|
|
5
7
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
declare module "@friggframework/integrations" {
|
|
2
2
|
import { Delegate, IFriggDelegate } from "@friggframework/core";
|
|
3
|
-
import { Model } from "mongoose";
|
|
4
3
|
|
|
5
|
-
export
|
|
4
|
+
export interface Integration {
|
|
6
5
|
entities: any[];
|
|
7
6
|
userId: string;
|
|
8
7
|
status: string; // IntegrationStatus
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
declare module "@friggframework/module-plugin" {
|
|
2
|
-
import { Model } from "mongoose";
|
|
3
2
|
import { Delegate, IFriggDelegate } from "@friggframework/core";
|
|
4
3
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
export interface Credential {
|
|
5
|
+
id?: string;
|
|
6
|
+
userId?: string;
|
|
7
|
+
authIsValid?: boolean;
|
|
8
|
+
externalId?: string;
|
|
9
|
+
data?: any;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
interface
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
externalId
|
|
12
|
+
export interface Entity {
|
|
13
|
+
id?: string;
|
|
14
|
+
credentialId?: string;
|
|
15
|
+
userId?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
moduleName?: string;
|
|
18
|
+
externalId?: string;
|
|
19
|
+
data?: any;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
export type MappedEntity = Entity & { id: string; type: any };
|
package/types/syncs/index.d.ts
CHANGED
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
declare module "@friggframework/syncs/model" {
|
|
2
|
-
import { Model } from "mongoose";
|
|
3
|
-
|
|
4
|
-
export class Sync extends Model {
|
|
5
|
-
entities: any[];
|
|
6
|
-
hash: string;
|
|
7
|
-
name: string;
|
|
8
|
-
dataIdentifiers: {
|
|
9
|
-
entity: any;
|
|
10
|
-
id: object;
|
|
11
|
-
hash: string;
|
|
12
|
-
}[];
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
1
|
declare module "@friggframework/syncs/manager" {
|
|
17
2
|
import Sync from "@friggframework/syncs/sync";
|
|
18
3
|
|
package/assertions/is-equal.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
const expectShallowEqualDbObject = (modelObject, compareObject) => {
|
|
2
|
-
for (const key in compareObject) {
|
|
3
|
-
let objVal = modelObject[key];
|
|
4
|
-
|
|
5
|
-
if (objVal instanceof Date) {
|
|
6
|
-
objVal = objVal.toISOString();
|
|
7
|
-
} else if (objVal instanceof mongoose.Types.ObjectId) {
|
|
8
|
-
objVal = objVal._id.toString();
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
expect(compareObject[key]).toBe(objVal);
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
// TODO not sure how much this is needed, but could rewrite with _.isEqualWith for deep equality with custom checks.
|
|
16
|
-
|
|
17
|
-
module.exports = { expectShallowEqualDbObject };
|
package/associations/model.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
const mongoose = require("mongoose");
|
|
2
|
-
|
|
3
|
-
const schema = new mongoose.Schema({
|
|
4
|
-
integration: {
|
|
5
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
6
|
-
ref: "Integration",
|
|
7
|
-
required: true,
|
|
8
|
-
},
|
|
9
|
-
name: { type: String, required: true },
|
|
10
|
-
type: {
|
|
11
|
-
type: String,
|
|
12
|
-
enum: ["ONE_TO_MANY", "ONE_TO_ONE", "MANY_TO_ONE"],
|
|
13
|
-
required: true,
|
|
14
|
-
},
|
|
15
|
-
primaryObject: { type: String, required: true },
|
|
16
|
-
objects: [
|
|
17
|
-
{
|
|
18
|
-
entity: {
|
|
19
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
20
|
-
ref: "Entity",
|
|
21
|
-
required: true,
|
|
22
|
-
},
|
|
23
|
-
objectType: { type: String, required: true },
|
|
24
|
-
objId: { type: String, required: true },
|
|
25
|
-
metadata: { type: Object, required: false },
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
schema.statics({
|
|
31
|
-
addAssociation: async function (id, object) {
|
|
32
|
-
return this.update({ _id: id }, { $push: { objects: object } });
|
|
33
|
-
},
|
|
34
|
-
findAssociation: async function (name, dataIdentifierHash) {
|
|
35
|
-
const syncList = await this.list({
|
|
36
|
-
name: name,
|
|
37
|
-
"dataIdentifiers.hash": dataIdentifierHash,
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
if (syncList.length === 1) {
|
|
41
|
-
return syncList[0];
|
|
42
|
-
} else if (syncList.length === 0) {
|
|
43
|
-
return null;
|
|
44
|
-
} else {
|
|
45
|
-
throw new Error(
|
|
46
|
-
`there are multiple sync objects with the name ${name}, for entities [${entities}]`
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const Association =
|
|
53
|
-
mongoose.models.Association || mongoose.model("Association", schema);
|
|
54
|
-
module.exports = { Association };
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
const { mongoose } = require('../mongoose');
|
|
2
|
-
const bcrypt = require('bcryptjs');
|
|
3
|
-
const { UserModel: Parent } = require('./UserModel');
|
|
4
|
-
|
|
5
|
-
const collectionName = 'IndividualUser';
|
|
6
|
-
|
|
7
|
-
const schema = new mongoose.Schema({
|
|
8
|
-
email: { type: String },
|
|
9
|
-
username: { type: String, unique: true },
|
|
10
|
-
hashword: { type: String },
|
|
11
|
-
appUserId: { type: String },
|
|
12
|
-
organizationUser: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
schema.pre('save', async function () {
|
|
16
|
-
if (this.hashword) {
|
|
17
|
-
this.hashword = await bcrypt.hashSync(
|
|
18
|
-
this.hashword,
|
|
19
|
-
parseInt(this.schema.statics.decimals)
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
schema.static({
|
|
25
|
-
decimals: 10,
|
|
26
|
-
update: async function (id, options) {
|
|
27
|
-
if ('password' in options) {
|
|
28
|
-
options.hashword = await bcrypt.hashSync(
|
|
29
|
-
options.password,
|
|
30
|
-
parseInt(this.decimals)
|
|
31
|
-
);
|
|
32
|
-
delete options.password;
|
|
33
|
-
}
|
|
34
|
-
return this.findOneAndUpdate(
|
|
35
|
-
{_id: id},
|
|
36
|
-
options,
|
|
37
|
-
{new: true, useFindAndModify: true}
|
|
38
|
-
);
|
|
39
|
-
},
|
|
40
|
-
getUserByUsername: async function (username) {
|
|
41
|
-
let getByUser;
|
|
42
|
-
try{
|
|
43
|
-
getByUser = await this.find({username});
|
|
44
|
-
} catch (e) {
|
|
45
|
-
console.log('oops')
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (getByUser.length > 1) {
|
|
49
|
-
throw new Error(
|
|
50
|
-
'Unique username or email? Please reach out to our developers'
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (getByUser.length === 1) {
|
|
55
|
-
return getByUser[0];
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
getUserByAppUserId: async function (appUserId) {
|
|
59
|
-
const getByUser = await this.find({ appUserId });
|
|
60
|
-
|
|
61
|
-
if (getByUser.length > 1) {
|
|
62
|
-
throw new Error(
|
|
63
|
-
'Supposedly using a unique appUserId? Please reach out to our developers'
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (getByUser.length === 1) {
|
|
69
|
-
return getByUser[0];
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
const IndividualUser = Parent.discriminators?.IndividualUser || Parent.discriminator(collectionName, schema);
|
|
75
|
-
|
|
76
|
-
module.exports = {IndividualUser};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
const { mongoose } = require('../mongoose');
|
|
2
|
-
const { UserModel: Parent } = require('./UserModel');
|
|
3
|
-
|
|
4
|
-
const collectionName = 'OrganizationUser';
|
|
5
|
-
|
|
6
|
-
const schema = new mongoose.Schema({
|
|
7
|
-
appOrgId: { type: String, required: true, unique: true },
|
|
8
|
-
name: { type: String },
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
schema.static({
|
|
12
|
-
getUserByAppOrgId: async function (appOrgId) {
|
|
13
|
-
const getByUser = await this.find({ appOrgId });
|
|
14
|
-
|
|
15
|
-
if (getByUser.length > 1) {
|
|
16
|
-
throw new Error(
|
|
17
|
-
'Supposedly using a unique appOrgId? Please reach out to our developers'
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (getByUser.length === 1) {
|
|
22
|
-
return getByUser[0];
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
const OrganizationUser = Parent.discriminators?.OrganizationUser || Parent.discriminator(collectionName, schema);
|
|
28
|
-
|
|
29
|
-
module.exports = {OrganizationUser};
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
const { mongoose } = require('../mongoose');
|
|
2
|
-
const {
|
|
3
|
-
ApiGatewayManagementApiClient,
|
|
4
|
-
PostToConnectionCommand,
|
|
5
|
-
} = require('@aws-sdk/client-apigatewaymanagementapi');
|
|
6
|
-
|
|
7
|
-
const schema = new mongoose.Schema({
|
|
8
|
-
connectionId: { type: mongoose.Schema.Types.String },
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
// Add a static method to get active connections
|
|
12
|
-
schema.statics.getActiveConnections = async function () {
|
|
13
|
-
try {
|
|
14
|
-
// Return empty array if websockets are not configured
|
|
15
|
-
if (!process.env.WEBSOCKET_API_ENDPOINT) {
|
|
16
|
-
return [];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const connections = await this.find({}, 'connectionId');
|
|
20
|
-
return connections.map((conn) => ({
|
|
21
|
-
connectionId: conn.connectionId,
|
|
22
|
-
send: async (data) => {
|
|
23
|
-
const apigwManagementApi = new ApiGatewayManagementApiClient({
|
|
24
|
-
endpoint: process.env.WEBSOCKET_API_ENDPOINT,
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const command = new PostToConnectionCommand({
|
|
29
|
-
ConnectionId: conn.connectionId,
|
|
30
|
-
Data: JSON.stringify(data),
|
|
31
|
-
});
|
|
32
|
-
await apigwManagementApi.send(command);
|
|
33
|
-
} catch (error) {
|
|
34
|
-
if (error.statusCode === 410 || error.$metadata?.httpStatusCode === 410) {
|
|
35
|
-
console.log(`Stale connection ${conn.connectionId}`);
|
|
36
|
-
await this.deleteOne({
|
|
37
|
-
connectionId: conn.connectionId,
|
|
38
|
-
});
|
|
39
|
-
} else {
|
|
40
|
-
throw error;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
}));
|
|
45
|
-
} catch (error) {
|
|
46
|
-
console.error('Error getting active connections:', error);
|
|
47
|
-
throw error;
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const WebsocketConnection =
|
|
52
|
-
mongoose.models.WebsocketConnection ||
|
|
53
|
-
mongoose.model('WebsocketConnection', schema);
|
|
54
|
-
|
|
55
|
-
module.exports = { WebsocketConnection };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
// todo: we need to get rid of this entire models folder
|
package/database/mongoose.js
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
const { prisma } = require('../prisma');
|
|
2
|
-
const { mongoose } = require('../mongoose');
|
|
3
|
-
const {
|
|
4
|
-
HealthCheckRepositoryInterface,
|
|
5
|
-
} = require('./health-check-repository-interface');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Repository for Health Check database operations.
|
|
9
|
-
* Provides atomic database operations for health testing.
|
|
10
|
-
*
|
|
11
|
-
* Follows DDD/Hexagonal Architecture:
|
|
12
|
-
* - Infrastructure Layer (this repository)
|
|
13
|
-
* - Pure database operations only, no business logic
|
|
14
|
-
* - Used by Application Layer (Use Cases)
|
|
15
|
-
*
|
|
16
|
-
* Works identically for both MongoDB and PostgreSQL:
|
|
17
|
-
* - Uses Prisma for database operations
|
|
18
|
-
* - Encryption happens transparently via Prisma extension
|
|
19
|
-
* - Both MongoDB and PostgreSQL use same Prisma API
|
|
20
|
-
*
|
|
21
|
-
* Migration from Mongoose to Prisma:
|
|
22
|
-
* - Replaced Mongoose models with Prisma client
|
|
23
|
-
* - Uses Credential model for encryption testing
|
|
24
|
-
* - Maintains same method signatures for compatibility
|
|
25
|
-
*/
|
|
26
|
-
class HealthCheckRepository extends HealthCheckRepositoryInterface {
|
|
27
|
-
constructor() {
|
|
28
|
-
super();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Get database connection state
|
|
33
|
-
* @returns {Object} Object with readyState, stateName, and isConnected
|
|
34
|
-
*/
|
|
35
|
-
getDatabaseConnectionState() {
|
|
36
|
-
const stateMap = {
|
|
37
|
-
0: 'disconnected',
|
|
38
|
-
1: 'connected',
|
|
39
|
-
2: 'connecting',
|
|
40
|
-
3: 'disconnecting',
|
|
41
|
-
};
|
|
42
|
-
const readyState = mongoose.connection.readyState;
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
readyState,
|
|
46
|
-
stateName: stateMap[readyState],
|
|
47
|
-
isConnected: readyState === 1,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Ping the database to verify connectivity
|
|
53
|
-
* @param {number} maxTimeMS - Maximum time to wait for ping response
|
|
54
|
-
* @returns {Promise<number>} Response time in milliseconds
|
|
55
|
-
* @throws {Error} If database is not connected or ping fails
|
|
56
|
-
*/
|
|
57
|
-
async pingDatabase(maxTimeMS = 2000) {
|
|
58
|
-
const pingStart = Date.now();
|
|
59
|
-
await mongoose.connection.db.admin().ping({ maxTimeMS });
|
|
60
|
-
return Date.now() - pingStart;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Create a test credential for encryption testing
|
|
65
|
-
* @param {Object} credentialData - Credential data to create
|
|
66
|
-
* @returns {Promise<Object>} Created credential
|
|
67
|
-
*/
|
|
68
|
-
async createCredential(credentialData) {
|
|
69
|
-
return await prisma.credential.create({
|
|
70
|
-
data: credentialData,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Find a credential by ID
|
|
76
|
-
* @param {string} id - Credential ID
|
|
77
|
-
* @returns {Promise<Object|null>} Found credential or null
|
|
78
|
-
*/
|
|
79
|
-
async findCredentialById(id) {
|
|
80
|
-
return await prisma.credential.findUnique({
|
|
81
|
-
where: { id },
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Get raw credential from database bypassing Prisma encryption extension
|
|
87
|
-
* @param {string} id - Credential ID
|
|
88
|
-
* @returns {Promise<Object|null>} Raw credential from database
|
|
89
|
-
*/
|
|
90
|
-
async getRawCredentialById(id) {
|
|
91
|
-
return await mongoose.connection.db
|
|
92
|
-
.collection('credentials')
|
|
93
|
-
.findOne({ _id: id });
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Delete a credential by ID
|
|
98
|
-
* @param {string} id - Credential ID
|
|
99
|
-
* @returns {Promise<void>}
|
|
100
|
-
*/
|
|
101
|
-
async deleteCredential(id) {
|
|
102
|
-
await prisma.credential.delete({
|
|
103
|
-
where: { id },
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
module.exports = { HealthCheckRepository };
|
package/encrypt/test-encrypt.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
const { mongoose } = require('../database/mongoose');
|
|
2
|
-
const crypto = require('crypto');
|
|
3
|
-
|
|
4
|
-
const hexPattern = /^[a-f0-9]+$/i; // match hex strings of length >= 1
|
|
5
|
-
|
|
6
|
-
// Test that an encrypted secret value appears to have valid values (without actually decrypting it).
|
|
7
|
-
function expectValidSecret(secret) {
|
|
8
|
-
const parts = secret.split(':');
|
|
9
|
-
const keyId = Buffer.from(parts[0], 'base64').toString();
|
|
10
|
-
const iv = parts[1];
|
|
11
|
-
const encryptedText = parts[2];
|
|
12
|
-
const encryptedKey = Buffer.from(parts[3], 'base64').toString();
|
|
13
|
-
|
|
14
|
-
expect(iv).toHaveLength(32);
|
|
15
|
-
expect(iv).toMatch(hexPattern);
|
|
16
|
-
expect(encryptedText).toHaveLength(14);
|
|
17
|
-
expect(encryptedText).toMatch(hexPattern);
|
|
18
|
-
|
|
19
|
-
// Keys from AWS start with Karn and have a different format.
|
|
20
|
-
if (keyId.startsWith('arn:aws')) {
|
|
21
|
-
expect(keyId).toBe(
|
|
22
|
-
`arn:aws:kms:us-east-1:000000000000:key/${process.env.KMS_KEY_ARN}`
|
|
23
|
-
);
|
|
24
|
-
// The length here is a sanity check. Seems they are always within this range.
|
|
25
|
-
expect(encryptedKey.length).toBeGreaterThanOrEqual(85);
|
|
26
|
-
expect(encryptedKey.length).toBeLessThanOrEqual(140);
|
|
27
|
-
} else {
|
|
28
|
-
const { AES_KEY_ID, DEPRECATED_AES_KEY_ID } = process.env;
|
|
29
|
-
expect([AES_KEY_ID, DEPRECATED_AES_KEY_ID]).toContain(keyId);
|
|
30
|
-
|
|
31
|
-
const encryptedKeyParts = encryptedKey.split(':');
|
|
32
|
-
const iv2 = encryptedKeyParts[0];
|
|
33
|
-
const encryptedKeyPart = encryptedKeyParts[1];
|
|
34
|
-
|
|
35
|
-
expect(iv2).toHaveLength(32);
|
|
36
|
-
expect(iv2).toMatch(hexPattern);
|
|
37
|
-
expect(encryptedKeyPart).toHaveLength(64);
|
|
38
|
-
expect(encryptedKeyPart).toMatch(hexPattern);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Load and validate a raw test document compared to a Mongoose document object.
|
|
43
|
-
async function expectValidRawDoc(Model, doc) {
|
|
44
|
-
const rawDoc = await expectValidRawDocById(Model, doc._id);
|
|
45
|
-
|
|
46
|
-
expect(rawDoc.notSecret.toString()).toBe(doc.notSecret.toString());
|
|
47
|
-
expect(rawDoc).not.toHaveProperty('secret', doc.secret);
|
|
48
|
-
|
|
49
|
-
return rawDoc;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Load and validate a raw test document by ID.
|
|
53
|
-
async function expectValidRawDocById(Model, _id) {
|
|
54
|
-
const rawDoc = await Model.collection.findOne({ _id });
|
|
55
|
-
|
|
56
|
-
expect(rawDoc).toHaveProperty('notSecret');
|
|
57
|
-
expect(rawDoc).toHaveProperty('secret');
|
|
58
|
-
expectValidSecret(rawDoc.secret);
|
|
59
|
-
|
|
60
|
-
return rawDoc;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Create a clean test model, so that the plug-in can be reinitialized.
|
|
64
|
-
function createModel() {
|
|
65
|
-
const randomHex = crypto.randomBytes(16).toString('hex');
|
|
66
|
-
const schema = new mongoose.Schema({
|
|
67
|
-
secret: { type: String, lhEncrypt: true },
|
|
68
|
-
notSecret: { type: mongoose.Schema.Types.ObjectId },
|
|
69
|
-
'deeply.nested.secret': { type: String, lhEncrypt: true },
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
schema.plugin(Encrypt);
|
|
73
|
-
|
|
74
|
-
const Model = mongoose.model(`EncryptTest_${randomHex}`, schema);
|
|
75
|
-
return { schema, Model };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Save and validate a test doc.
|
|
79
|
-
async function saveTestDocument(Model) {
|
|
80
|
-
const notSecret = new mongoose.Types.ObjectId();
|
|
81
|
-
const secret = 'abcdefg';
|
|
82
|
-
const doc = new Model({ notSecret, secret });
|
|
83
|
-
|
|
84
|
-
expect(doc).toHaveProperty('notSecret');
|
|
85
|
-
expect(doc.notSecret.toString()).toBe(notSecret.toString());
|
|
86
|
-
expect(doc).toHaveProperty('secret');
|
|
87
|
-
expect(doc.secret).toBe(secret);
|
|
88
|
-
|
|
89
|
-
await doc.save();
|
|
90
|
-
|
|
91
|
-
expect(doc).toHaveProperty('notSecret');
|
|
92
|
-
expect(doc.notSecret.toString()).toBe(notSecret.toString());
|
|
93
|
-
expect(doc).toHaveProperty('secret');
|
|
94
|
-
expect(doc.secret).toBe(secret);
|
|
95
|
-
|
|
96
|
-
return { doc, secret, notSecret };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
module.exports = {
|
|
100
|
-
expectValidSecret,
|
|
101
|
-
expectValidRawDoc,
|
|
102
|
-
expectValidRawDocById,
|
|
103
|
-
createModel,
|
|
104
|
-
saveTestDocument,
|
|
105
|
-
};
|
package/modules/entity.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
const { mongoose } = require('../database/mongoose');
|
|
2
|
-
const schema = new mongoose.Schema(
|
|
3
|
-
{
|
|
4
|
-
credential: {
|
|
5
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
6
|
-
ref: 'Credential',
|
|
7
|
-
required: false,
|
|
8
|
-
},
|
|
9
|
-
user: {
|
|
10
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
11
|
-
ref: 'User',
|
|
12
|
-
required: false,
|
|
13
|
-
},
|
|
14
|
-
name: { type: String },
|
|
15
|
-
moduleName: { type: String },
|
|
16
|
-
externalId: { type: String },
|
|
17
|
-
},
|
|
18
|
-
{ timestamps: true }
|
|
19
|
-
);
|
|
20
|
-
|
|
21
|
-
schema.static({
|
|
22
|
-
findByUserId: async function (userId) {
|
|
23
|
-
const entities = await this.find({ user: userId });
|
|
24
|
-
if (entities.length === 0) {
|
|
25
|
-
return null;
|
|
26
|
-
} else if (entities.length === 1) {
|
|
27
|
-
return entities[0];
|
|
28
|
-
} else {
|
|
29
|
-
throw new Error('multiple entities with same userId');
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
findAllByUserId(userId) {
|
|
33
|
-
return this.find({ user: userId });
|
|
34
|
-
},
|
|
35
|
-
upsert: async function (filter, obj) {
|
|
36
|
-
return this.findOneAndUpdate(filter, obj, {
|
|
37
|
-
new: true,
|
|
38
|
-
upsert: true,
|
|
39
|
-
setDefaultsOnInsert: true,
|
|
40
|
-
});
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const Entity = mongoose.models.Entity || mongoose.model('Entity', schema);
|
|
45
|
-
|
|
46
|
-
module.exports = { Entity };
|
package/syncs/model.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
const mongoose = require("mongoose");
|
|
2
|
-
|
|
3
|
-
const schema = new mongoose.Schema({
|
|
4
|
-
entities: [
|
|
5
|
-
{ type: mongoose.Schema.Types.ObjectId, ref: "Entity", required: true },
|
|
6
|
-
],
|
|
7
|
-
hash: { type: String, required: true },
|
|
8
|
-
name: { type: String, required: true },
|
|
9
|
-
dataIdentifiers: [
|
|
10
|
-
{
|
|
11
|
-
entity: {
|
|
12
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
13
|
-
ref: "Entity",
|
|
14
|
-
required: true,
|
|
15
|
-
},
|
|
16
|
-
id: { type: Object, required: true },
|
|
17
|
-
hash: { type: String, required: true },
|
|
18
|
-
},
|
|
19
|
-
],
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
schema.statics({
|
|
23
|
-
getSyncObject: async function (name, dataIdentifier, entity) {
|
|
24
|
-
// const syncList = await this.list({name:name,entities: {"$in": entities}, "entityIds.idHash":entityIdHash });
|
|
25
|
-
const syncList = await this.find({
|
|
26
|
-
name: name,
|
|
27
|
-
dataIdentifiers: { $elemMatch: { id: dataIdentifier, entity } },
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
if (syncList.length === 1) {
|
|
31
|
-
return syncList[0];
|
|
32
|
-
} else if (syncList.length === 0) {
|
|
33
|
-
return null;
|
|
34
|
-
} else {
|
|
35
|
-
throw new Error(
|
|
36
|
-
`There are multiple sync objects with the name ${name}, for entities [${syncList[0].entities}] [${syncList[1].entities}]`
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
addDataIdentifier: async function (id, dataIdentifier) {
|
|
42
|
-
return await this.update(
|
|
43
|
-
{ _id: id },
|
|
44
|
-
{},
|
|
45
|
-
{ dataIdentifiers: dataIdentifier }
|
|
46
|
-
);
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
getEntityObjIdForEntityIdFromObject: function (syncObj, entityId) {
|
|
50
|
-
for (let dataIdentifier of syncObj.dataIdentifiers) {
|
|
51
|
-
if (dataIdentifier.entity.toString() === entityId) {
|
|
52
|
-
return dataIdentifier.id;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
throw new Error(
|
|
56
|
-
`Sync object does not have DataIdentifier for entityId: ${entityId}`
|
|
57
|
-
);
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const Sync = mongoose.models.Sync || mongoose.model("Sync", schema);
|
|
62
|
-
module.exports = { Sync };
|