@flowerforce/flowerbase 1.7.6-beta.3 → 1.7.6-beta.5
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/dist/auth/providers/anon-user/controller.d.ts.map +1 -1
- package/dist/auth/providers/anon-user/controller.js +1 -0
- package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
- package/dist/auth/providers/custom-function/controller.js +3 -1
- package/dist/auth/providers/local-userpass/controller.d.ts.map +1 -1
- package/dist/auth/providers/local-userpass/controller.js +30 -8
- package/dist/auth/utils.d.ts +1 -0
- package/dist/auth/utils.d.ts.map +1 -1
- package/dist/auth/utils.js +1 -0
- package/dist/features/endpoints/utils.d.ts.map +1 -1
- package/dist/features/endpoints/utils.js +3 -0
- package/dist/features/triggers/interface.d.ts +1 -1
- package/dist/features/triggers/interface.d.ts.map +1 -1
- package/dist/features/triggers/utils.d.ts.map +1 -1
- package/dist/features/triggers/utils.js +60 -0
- package/dist/utils/context/helpers.d.ts.map +1 -1
- package/dist/utils/context/helpers.js +3 -2
- package/dist/utils/context/index.d.ts.map +1 -1
- package/dist/utils/context/index.js +4 -2
- package/package.json +1 -1
- package/src/auth/providers/anon-user/controller.ts +1 -0
- package/src/auth/providers/custom-function/controller.ts +6 -1
- package/src/auth/providers/local-userpass/__tests__/controller.test.ts +200 -0
- package/src/auth/providers/local-userpass/controller.ts +44 -9
- package/src/auth/utils.ts +1 -0
- package/src/features/endpoints/__tests__/utils.test.ts +65 -0
- package/src/features/endpoints/utils.ts +3 -0
- package/src/features/triggers/interface.ts +1 -1
- package/src/features/triggers/utils.ts +60 -0
- package/src/utils/__tests__/contextExecuteCompatibility.test.ts +27 -1
- package/src/utils/__tests__/generateContextData.test.ts +5 -1
- package/src/utils/context/helpers.ts +3 -2
- package/src/utils/context/index.ts +4 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../../src/auth/providers/anon-user/controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAOzC;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../../src/auth/providers/anon-user/controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAOzC;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,eAAe,iBAyE5D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../../src/auth/providers/custom-function/controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAUzC;;;;GAIG;AACH,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../../src/auth/providers/custom-function/controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAUzC;;;;GAIG;AACH,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,eAAe,iBA0IlE"}
|
|
@@ -111,15 +111,17 @@ function customFunctionController(app) {
|
|
|
111
111
|
user_data: Object.assign(Object.assign({}, (user || {})), { id: authUser._id.toString(), email: authUser.email }),
|
|
112
112
|
custom_data: Object.assign({}, (user || {}))
|
|
113
113
|
};
|
|
114
|
+
const now = new Date();
|
|
114
115
|
const refreshToken = this.createRefreshToken(currentUserData);
|
|
115
116
|
const refreshTokenHash = (0, crypto_1.hashToken)(refreshToken);
|
|
116
117
|
yield authDb.collection(refreshTokensCollection).insertOne({
|
|
117
118
|
userId: authUser._id,
|
|
118
119
|
tokenHash: refreshTokenHash,
|
|
119
|
-
createdAt:
|
|
120
|
+
createdAt: now,
|
|
120
121
|
expiresAt: new Date(Date.now() + refreshTokenTtlMs),
|
|
121
122
|
revokedAt: null
|
|
122
123
|
});
|
|
124
|
+
yield authDb.collection(authCollection).updateOne({ _id: authUser._id }, { $set: { lastLoginAt: now } });
|
|
123
125
|
return {
|
|
124
126
|
access_token: this.createAccessToken(currentUserData),
|
|
125
127
|
refresh_token: refreshToken,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../../src/auth/providers/local-userpass/controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../../src/auth/providers/local-userpass/controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAuCzC;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,eAAe,iBAsZjE"}
|
|
@@ -81,7 +81,7 @@ function localUserPassController(app) {
|
|
|
81
81
|
const currentFunction = functionsList[resetPasswordConfig.resetFunctionName];
|
|
82
82
|
const baseArgs = { token, tokenId, email, password, username: email };
|
|
83
83
|
const args = Array.isArray(extraArguments) ? [baseArgs, ...extraArguments] : [baseArgs];
|
|
84
|
-
yield (0, context_1.GenerateContext)({
|
|
84
|
+
const response = yield (0, context_1.GenerateContext)({
|
|
85
85
|
args,
|
|
86
86
|
app,
|
|
87
87
|
rules: {},
|
|
@@ -89,10 +89,32 @@ function localUserPassController(app) {
|
|
|
89
89
|
currentFunction,
|
|
90
90
|
functionName: resetPasswordConfig.resetFunctionName,
|
|
91
91
|
functionsList,
|
|
92
|
-
services
|
|
92
|
+
services,
|
|
93
|
+
runAsSystem: true
|
|
93
94
|
});
|
|
94
|
-
|
|
95
|
+
const resetStatus = response === null || response === void 0 ? void 0 : response.status;
|
|
96
|
+
if (resetStatus === 'success') {
|
|
97
|
+
if (!password) {
|
|
98
|
+
throw new Error(utils_1.AUTH_ERRORS.INVALID_RESET_FUNCTION_RESPONSE);
|
|
99
|
+
}
|
|
100
|
+
const hashedPassword = yield (0, crypto_1.hashPassword)(password);
|
|
101
|
+
yield authDb.collection(authCollection).updateOne({ email }, {
|
|
102
|
+
$set: {
|
|
103
|
+
password: hashedPassword
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
yield (authDb === null || authDb === void 0 ? void 0 : authDb.collection(resetPasswordCollection).deleteOne({ email }));
|
|
107
|
+
return { status: 'success' };
|
|
108
|
+
}
|
|
109
|
+
if (resetStatus === 'pending') {
|
|
110
|
+
return { status: 'pending' };
|
|
111
|
+
}
|
|
112
|
+
if (resetStatus === 'fail') {
|
|
113
|
+
throw new Error(utils_1.AUTH_ERRORS.INVALID_RESET_PARAMS);
|
|
114
|
+
}
|
|
115
|
+
throw new Error(utils_1.AUTH_ERRORS.INVALID_RESET_FUNCTION_RESPONSE);
|
|
95
116
|
}
|
|
117
|
+
return { status: 'pending' };
|
|
96
118
|
});
|
|
97
119
|
/**
|
|
98
120
|
* Endpoint for user registration.
|
|
@@ -214,15 +236,17 @@ function localUserPassController(app) {
|
|
|
214
236
|
if (authUser && authUser.status !== 'confirmed') {
|
|
215
237
|
throw new Error(utils_1.AUTH_ERRORS.USER_NOT_CONFIRMED);
|
|
216
238
|
}
|
|
239
|
+
const now = new Date();
|
|
217
240
|
const refreshToken = this.createRefreshToken(userWithCustomData);
|
|
218
241
|
const refreshTokenHash = (0, crypto_1.hashToken)(refreshToken);
|
|
219
242
|
yield authDb.collection(refreshTokensCollection).insertOne({
|
|
220
243
|
userId: authUser._id,
|
|
221
244
|
tokenHash: refreshTokenHash,
|
|
222
|
-
createdAt:
|
|
245
|
+
createdAt: now,
|
|
223
246
|
expiresAt: new Date(Date.now() + refreshTokenTtlMs),
|
|
224
247
|
revokedAt: null
|
|
225
248
|
});
|
|
249
|
+
yield authDb.collection(authCollection).updateOne({ _id: authUser._id }, { $set: { lastLoginAt: now } });
|
|
226
250
|
return {
|
|
227
251
|
access_token: this.createAccessToken(userWithCustomData),
|
|
228
252
|
refresh_token: refreshToken,
|
|
@@ -271,11 +295,9 @@ function localUserPassController(app) {
|
|
|
271
295
|
res.status(429);
|
|
272
296
|
return { message: 'Too many requests' };
|
|
273
297
|
}
|
|
274
|
-
yield handleResetPasswordRequest(req.body.email, req.body.password, req.body.arguments);
|
|
298
|
+
const result = yield handleResetPasswordRequest(req.body.email, req.body.password, req.body.arguments);
|
|
275
299
|
res.status(202);
|
|
276
|
-
return
|
|
277
|
-
status: 'ok'
|
|
278
|
-
};
|
|
300
|
+
return result;
|
|
279
301
|
});
|
|
280
302
|
});
|
|
281
303
|
/**
|
package/dist/auth/utils.d.ts
CHANGED
|
@@ -146,6 +146,7 @@ export declare enum AUTH_ERRORS {
|
|
|
146
146
|
INVALID_TOKEN = "Invalid refresh token provided",
|
|
147
147
|
INVALID_RESET_PARAMS = "Invalid token or tokenId provided",
|
|
148
148
|
MISSING_RESET_FUNCTION = "Missing reset function",
|
|
149
|
+
INVALID_RESET_FUNCTION_RESPONSE = "Invalid reset function response",
|
|
149
150
|
USER_NOT_CONFIRMED = "User not confirmed"
|
|
150
151
|
}
|
|
151
152
|
export interface AuthConfig {
|
package/dist/auth/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/auth/utils.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,cAAc;;;CAA4C,CAAC;AACxE,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;CAexB,CAAA;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;CAc7B,CAAA;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;CAgB7B,CAAA;AAED,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;CAWhC,CAAA;AAED,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;CAU/B,CAAA;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;;;CAAoB,CAAA;AAE7C,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;CAe/B,CAAA;AAED,oBAAY,cAAc;IACxB,KAAK,WAAW;IAChB,YAAY,cAAc;IAC1B,OAAO,aAAa;IACpB,OAAO,aAAa;IACpB,OAAO,aAAa;IACpB,KAAK,gBAAgB;IACrB,UAAU,gBAAgB;IAC1B,aAAa,WAAW;IACxB,UAAU,sBAAsB;CACjC;AAED,oBAAY,WAAW;IACrB,mBAAmB,wBAAwB;IAC3C,aAAa,mCAAmC;IAChD,oBAAoB,sCAAsC;IAC1D,sBAAsB,2BAA2B;IACjD,kBAAkB,uBAAuB;CAC1C;AAED,MAAM,WAAW,UAAU;IACzB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB,EAAE,aAAa,CAAA;IAC/B,iBAAiB,EAAE,cAAc,CAAA;IACjC,WAAW,CAAC,EAAE,QAAQ,CAAA;CACvB;AAED,UAAU,MAAM;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;CAClB;AACD,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,iBAAiB,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE;QACN,kBAAkB,EAAE,MAAM,CAAA;KAC3B,CAAA;CACF;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,WAAW,CAAA;IACjB,IAAI,EAAE,WAAW,CAAA;IACjB,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,MAAM;IACrB,WAAW,EAAE,OAAO,CAAA;IACpB,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,iBAAiB,EAAE,MAAM,CAAA;IACzB,gBAAgB,EAAE,MAAM,CAAA;IACxB,uBAAuB,EAAE,OAAO,CAAA;IAChC,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAA;IAChB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,aAAa,EAAE,MAAM,CAAA;IACrB,eAAe,EAAE,MAAM,CAAA;IACvB,aAAa,EAAE,MAAM,CAAA;IACrB,8BAA8B,EAAE,MAAM,CAAA;CACvC;AAMD;;;GAGG;AACH,eAAO,MAAM,cAAc,QAAO,UAuCjC,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAO,oBAarC,CAAA;AAED,eAAO,MAAM,gBAAgB,GAAI,eAAW,WAG3C,CAAA"}
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/auth/utils.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,cAAc;;;CAA4C,CAAC;AACxE,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;CAexB,CAAA;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;CAc7B,CAAA;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;CAgB7B,CAAA;AAED,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;CAWhC,CAAA;AAED,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;CAU/B,CAAA;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;;;CAAoB,CAAA;AAE7C,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;CAe/B,CAAA;AAED,oBAAY,cAAc;IACxB,KAAK,WAAW;IAChB,YAAY,cAAc;IAC1B,OAAO,aAAa;IACpB,OAAO,aAAa;IACpB,OAAO,aAAa;IACpB,KAAK,gBAAgB;IACrB,UAAU,gBAAgB;IAC1B,aAAa,WAAW;IACxB,UAAU,sBAAsB;CACjC;AAED,oBAAY,WAAW;IACrB,mBAAmB,wBAAwB;IAC3C,aAAa,mCAAmC;IAChD,oBAAoB,sCAAsC;IAC1D,sBAAsB,2BAA2B;IACjD,+BAA+B,oCAAoC;IACnE,kBAAkB,uBAAuB;CAC1C;AAED,MAAM,WAAW,UAAU;IACzB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB,EAAE,aAAa,CAAA;IAC/B,iBAAiB,EAAE,cAAc,CAAA;IACjC,WAAW,CAAC,EAAE,QAAQ,CAAA;CACvB;AAED,UAAU,MAAM;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;CAClB;AACD,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,iBAAiB,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE;QACN,kBAAkB,EAAE,MAAM,CAAA;KAC3B,CAAA;CACF;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,WAAW,CAAA;IACjB,IAAI,EAAE,WAAW,CAAA;IACjB,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,MAAM;IACrB,WAAW,EAAE,OAAO,CAAA;IACpB,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,iBAAiB,EAAE,MAAM,CAAA;IACzB,gBAAgB,EAAE,MAAM,CAAA;IACxB,uBAAuB,EAAE,OAAO,CAAA;IAChC,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAA;IAChB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,aAAa,EAAE,MAAM,CAAA;IACrB,eAAe,EAAE,MAAM,CAAA;IACvB,aAAa,EAAE,MAAM,CAAA;IACrB,8BAA8B,EAAE,MAAM,CAAA;CACvC;AAMD;;;GAGG;AACH,eAAO,MAAM,cAAc,QAAO,UAuCjC,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAO,oBAarC,CAAA;AAED,eAAO,MAAM,gBAAgB,GAAI,eAAW,WAG3C,CAAA"}
|
package/dist/auth/utils.js
CHANGED
|
@@ -115,6 +115,7 @@ var AUTH_ERRORS;
|
|
|
115
115
|
AUTH_ERRORS["INVALID_TOKEN"] = "Invalid refresh token provided";
|
|
116
116
|
AUTH_ERRORS["INVALID_RESET_PARAMS"] = "Invalid token or tokenId provided";
|
|
117
117
|
AUTH_ERRORS["MISSING_RESET_FUNCTION"] = "Missing reset function";
|
|
118
|
+
AUTH_ERRORS["INVALID_RESET_FUNCTION_RESPONSE"] = "Invalid reset function response";
|
|
118
119
|
AUTH_ERRORS["USER_NOT_CONFIRMED"] = "User not confirmed";
|
|
119
120
|
})(AUTH_ERRORS || (exports.AUTH_ERRORS = AUTH_ERRORS = {}));
|
|
120
121
|
const resolveAppPath = () => { var _a, _b, _c; return (_c = (_a = process.env.FLOWERBASE_APP_PATH) !== null && _a !== void 0 ? _a : (_b = require.main) === null || _b === void 0 ? void 0 : _b.path) !== null && _c !== void 0 ? _c : process.cwd(); };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/features/endpoints/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAKvE,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAE9D;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAU,gBAAuB,KAAG,OAAO,CAAC,SAAS,CA+B9E,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAC3B,KAAK,eAAe,EACpB,SAAS,UAAU,CAAC,OAAO,eAAe,CAAC,EAC3C,UAAU,MAAM;;;;;;;CAkDhB,CAAA;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAI,kEAM7B,qBAAqB,MACR,KAAK,cAAc,EAAE,KAAK,YAAY,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/features/endpoints/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAKvE,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAE9D;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAU,gBAAuB,KAAG,OAAO,CAAC,SAAS,CA+B9E,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAC3B,KAAK,eAAe,EACpB,SAAS,UAAU,CAAC,OAAO,eAAe,CAAC,EAC3C,UAAU,MAAM;;;;;;;CAkDhB,CAAA;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAI,kEAM7B,qBAAqB,MACR,KAAK,cAAc,EAAE,KAAK,YAAY,gBAgDrD,CAAA"}
|
|
@@ -135,6 +135,9 @@ const generateHandler = ({ app, currentFunction, functionName, functionsList, ru
|
|
|
135
135
|
setStatusCode: (code) => {
|
|
136
136
|
res.status(code);
|
|
137
137
|
},
|
|
138
|
+
setHeader: (name, value) => {
|
|
139
|
+
res.header(name, value);
|
|
140
|
+
},
|
|
138
141
|
setBody: (body) => {
|
|
139
142
|
customResponseBody.data = body;
|
|
140
143
|
}
|
|
@@ -22,7 +22,7 @@ type Config = {
|
|
|
22
22
|
isAutoTrigger?: boolean;
|
|
23
23
|
match: Record<string, unknown>;
|
|
24
24
|
operation_types: string[];
|
|
25
|
-
operation_type?: 'CREATE' | 'DELETE' | 'LOGOUT';
|
|
25
|
+
operation_type?: 'CREATE' | 'DELETE' | 'LOGIN' | 'LOGOUT';
|
|
26
26
|
providers?: string[];
|
|
27
27
|
project: Record<string, unknown>;
|
|
28
28
|
service_name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../../src/features/triggers/interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACnD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAE5D,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,WAAW,CAAA;IACjB,QAAQ,EAAE,OAAO,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,gBAAgB,EAAE;QAChB,QAAQ,EAAE;YACR,MAAM,EAAE;gBACN,aAAa,EAAE,MAAM,CAAA;aACtB,CAAA;SACF,CAAA;KACF,CAAA;CACF;AAED,KAAK,MAAM,GAAG;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,EAAE,OAAO,CAAA;IACtB,2BAA2B,EAAE,OAAO,CAAA;IACpC,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,cAAc,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../../src/features/triggers/interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACnD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAE5D,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,WAAW,CAAA;IACjB,QAAQ,EAAE,OAAO,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,gBAAgB,EAAE;QAChB,QAAQ,EAAE;YACR,MAAM,EAAE;gBACN,aAAa,EAAE,MAAM,CAAA;aACtB,CAAA;SACF,CAAA;KACF,CAAA;CACF;AAED,KAAK,MAAM,GAAG;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,EAAE,OAAO,CAAA;IACtB,2BAA2B,EAAE,OAAO,CAAA;IACpC,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,cAAc,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAA;IACzD,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAChC,YAAY,EAAE,MAAM,CAAA;IACpB,mBAAmB,EAAE,OAAO,CAAA;IAC5B,sBAAsB,EAAE,OAAO,CAAA;IAC/B,SAAS,EAAE,OAAO,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,gBAAgB,CAAA;AACrE,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,EAAE,CAAA;AAE/D,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,QAAQ,CAAA;IACxB,GAAG,EAAE,eAAe,CAAA;IACpB,QAAQ,EAAE,QAAQ,CAAA;IAClB,aAAa,EAAE,SAAS,CAAA;IACxB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,WAAW,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;CACrB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/features/triggers/utils.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,aAAa,EAAW,QAAQ,EAAE,MAAM,aAAa,CAAA;AA0E9D;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GAAU,gBAAuB,KAAG,OAAO,CAAC,QAAQ,CAkB5E,CAAA;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/features/triggers/utils.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,aAAa,EAAW,QAAQ,EAAE,MAAM,aAAa,CAAA;AA0E9D;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GAAU,gBAAuB,KAAG,OAAO,CAAC,QAAQ,CAkB5E,CAAA;AA2qBD,eAAO,MAAM,gBAAgB;kHAppB1B,aAAa;iHA4jBb,aAAa;uHA1cb,aAAa;CAsiBf,CAAA"}
|
|
@@ -170,6 +170,7 @@ const handleCronTrigger = (_a) => __awaiter(void 0, [_a], void 0, function* ({ c
|
|
|
170
170
|
const mapOpInverse = {
|
|
171
171
|
CREATE: ['insert', 'update', 'replace'],
|
|
172
172
|
DELETE: ['delete'],
|
|
173
|
+
LOGIN: ['insert', 'update'],
|
|
173
174
|
LOGOUT: ['update'],
|
|
174
175
|
};
|
|
175
176
|
const normalizeOperationTypes = (operationTypes = []) => operationTypes.map((op) => op.toLowerCase());
|
|
@@ -270,6 +271,8 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
|
|
|
270
271
|
const isUpdate = operationType === 'update';
|
|
271
272
|
const isReplace = operationType === 'replace';
|
|
272
273
|
const isDelete = operationType === 'delete';
|
|
274
|
+
const isLoginInsert = isInsert && !!(fullDocument === null || fullDocument === void 0 ? void 0 : fullDocument.lastLoginAt);
|
|
275
|
+
const isLoginUpdate = isUpdate && !!updatedFields && 'lastLoginAt' in updatedFields;
|
|
273
276
|
const isLogoutUpdate = isUpdate && !!updatedFields && 'lastLogoutAt' in updatedFields;
|
|
274
277
|
let confirmedCandidate = false;
|
|
275
278
|
let confirmedDocument = fullDocument;
|
|
@@ -353,6 +356,63 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
|
|
|
353
356
|
}
|
|
354
357
|
return;
|
|
355
358
|
}
|
|
359
|
+
if (operation_type === 'LOGIN') {
|
|
360
|
+
if (!isLoginInsert && !isLoginUpdate) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
let loginDocument = fullDocument !== null && fullDocument !== void 0 ? fullDocument : confirmedDocument;
|
|
364
|
+
if (!loginDocument && (documentKey === null || documentKey === void 0 ? void 0 : documentKey._id)) {
|
|
365
|
+
loginDocument = (yield collection.findOne({
|
|
366
|
+
_id: documentKey._id
|
|
367
|
+
}));
|
|
368
|
+
}
|
|
369
|
+
if (!matchesProviderFilter(loginDocument, providerFilter)) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const userData = buildUserData(loginDocument);
|
|
373
|
+
if (!userData) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const op = {
|
|
377
|
+
operationType: 'LOGIN',
|
|
378
|
+
fullDocument,
|
|
379
|
+
fullDocumentBeforeChange,
|
|
380
|
+
documentKey,
|
|
381
|
+
updateDescription
|
|
382
|
+
};
|
|
383
|
+
try {
|
|
384
|
+
emitTriggerEvent({
|
|
385
|
+
status: 'fired',
|
|
386
|
+
triggerName,
|
|
387
|
+
triggerType,
|
|
388
|
+
functionName,
|
|
389
|
+
meta: Object.assign(Object.assign({}, baseMeta), { event: 'LOGIN' })
|
|
390
|
+
});
|
|
391
|
+
yield (0, context_1.GenerateContext)({
|
|
392
|
+
args: [Object.assign({ user: userData }, op)],
|
|
393
|
+
app,
|
|
394
|
+
rules: state_1.StateManager.select("rules"),
|
|
395
|
+
user: {}, // TODO from currentUser ??
|
|
396
|
+
currentFunction: triggerHandler,
|
|
397
|
+
functionName,
|
|
398
|
+
functionsList,
|
|
399
|
+
services,
|
|
400
|
+
runAsSystem: true
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
catch (error) {
|
|
404
|
+
emitTriggerEvent({
|
|
405
|
+
status: 'error',
|
|
406
|
+
triggerName,
|
|
407
|
+
triggerType,
|
|
408
|
+
functionName,
|
|
409
|
+
meta: Object.assign(Object.assign({}, baseMeta), { event: 'LOGIN' }),
|
|
410
|
+
error
|
|
411
|
+
});
|
|
412
|
+
console.log("🚀 ~ handleAuthenticationTrigger ~ error:", error);
|
|
413
|
+
}
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
356
416
|
if (isDelete) {
|
|
357
417
|
if (isAutoTrigger || operation_type !== 'DELETE') {
|
|
358
418
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/utils/context/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAG1C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAA;AAEvD,KAAK,QAAQ,GAAG;IACd,MAAM,EAAE,CACN,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACzC,MAAM,CAAA;IACX,MAAM,EAAE,CACN,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GAAG,MAAM,EACpB,YAAY,CAAC,EAAE,OAAO,EACtB,sBAAsB,CAAC,EAAE,MAAM,EAAE,KAC9B,OAAO,CAAA;CACb,CAAA;AAgFD;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAAI,4GAUjC,yBAAyB;;;;;;;;;;;;;uBA4DP,SAAS;yBAGP,SAAS;;;;;;;;;;;;;;;;;;uBAcb,MAAM;;;;;;+BA5CU,MAAM,OAAO,QAAQ;;;;sCA1HrC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8BAsGT,CAAC;iCAAa,CAAC;;;;;;;;;;;;;;;;;;;kCAtGP,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAsGT,CAAC;6BAAa,CAAC;;;;;;;;;;;;;;;;;;kCAtGP,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAsGT,CAAC;6BAAa,CAAC;;;;;;;;;;;;;;;4BAyEF,MAAM,OAAO,aAAa,WAAW,SAAS;;;
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/utils/context/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAG1C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAA;AAEvD,KAAK,QAAQ,GAAG;IACd,MAAM,EAAE,CACN,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACzC,MAAM,CAAA;IACX,MAAM,EAAE,CACN,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GAAG,MAAM,EACpB,YAAY,CAAC,EAAE,OAAO,EACtB,sBAAsB,CAAC,EAAE,MAAM,EAAE,KAC9B,OAAO,CAAA;CACb,CAAA;AAgFD;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAAI,4GAUjC,yBAAyB;;;;;;;;;;;;;uBA4DP,SAAS;yBAGP,SAAS;;;;;;;;;;;;;;;;;;uBAcb,MAAM;;;;;;+BA5CU,MAAM,OAAO,QAAQ;;;;sCA1HrC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8BAsGT,CAAC;iCAAa,CAAC;;;;;;;;;;;;;;;;;;;kCAtGP,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAsGT,CAAC;6BAAa,CAAC;;;;;;;;;;;;;;;;;;kCAtGP,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAsGT,CAAC;6BAAa,CAAC;;;;;;;;;;;;;;;4BAyEF,MAAM,OAAO,aAAa,WAAW,SAAS;;;CAkBrE,CAAA"}
|
|
@@ -176,16 +176,17 @@ const generateContextData = ({ user, services, app, rules, currentFunction, func
|
|
|
176
176
|
https: getService('api'),
|
|
177
177
|
functions: {
|
|
178
178
|
execute: (name, ...args) => {
|
|
179
|
-
const
|
|
179
|
+
const targetFunction = functionsList[name];
|
|
180
180
|
return GenerateContextSync({
|
|
181
181
|
args,
|
|
182
182
|
app,
|
|
183
183
|
rules,
|
|
184
184
|
user,
|
|
185
|
-
currentFunction,
|
|
185
|
+
currentFunction: targetFunction,
|
|
186
186
|
functionName: String(name),
|
|
187
187
|
functionsList,
|
|
188
188
|
services,
|
|
189
|
+
runAsSystem: currentFunction.run_as_system,
|
|
189
190
|
deserializeArgs: false
|
|
190
191
|
});
|
|
191
192
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/context/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AA4JnD;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CAAC,EACpC,IAAI,EACJ,GAAG,EACH,KAAK,EACL,IAAI,EACJ,eAAe,EACf,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,eAAsB,EACtB,OAAO,EACP,OAAO,EACR,EAAE,qBAAqB,GAAG,OAAO,CAAC,OAAO,CAAC,CA2G1C;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,GAAG,EACH,KAAK,EACL,IAAI,EACJ,eAAe,EACf,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,eAAsB,EACtB,OAAO,EACR,EAAE,qBAAqB,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/context/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AA4JnD;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CAAC,EACpC,IAAI,EACJ,GAAG,EACH,KAAK,EACL,IAAI,EACJ,eAAe,EACf,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,eAAsB,EACtB,OAAO,EACP,OAAO,EACR,EAAE,qBAAqB,GAAG,OAAO,CAAC,OAAO,CAAC,CA2G1C;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,GAAG,EACH,KAAK,EACL,IAAI,EACJ,eAAe,EACf,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,eAAsB,EACtB,OAAO,EACR,EAAE,qBAAqB,GAAG,OAAO,CA6BjC"}
|
|
@@ -153,7 +153,8 @@ function GenerateContext(_a) {
|
|
|
153
153
|
if (!currentFunction)
|
|
154
154
|
return;
|
|
155
155
|
const functionsQueue = state_1.StateManager.select("functionsQueue");
|
|
156
|
-
const
|
|
156
|
+
const effectiveRunAsSystem = Boolean(runAsSystem || currentFunction.run_as_system);
|
|
157
|
+
const functionToRun = Object.assign(Object.assign({}, currentFunction), { run_as_system: effectiveRunAsSystem });
|
|
157
158
|
const run = () => __awaiter(this, void 0, void 0, function* () {
|
|
158
159
|
var _a;
|
|
159
160
|
const contextData = (0, helpers_1.generateContextData)({
|
|
@@ -232,7 +233,8 @@ function GenerateContextSync({ args, app, rules, user, currentFunction, function
|
|
|
232
233
|
var _a;
|
|
233
234
|
if (!currentFunction)
|
|
234
235
|
return;
|
|
235
|
-
const
|
|
236
|
+
const effectiveRunAsSystem = Boolean(runAsSystem || currentFunction.run_as_system);
|
|
237
|
+
const functionToRun = Object.assign(Object.assign({}, currentFunction), { run_as_system: effectiveRunAsSystem });
|
|
236
238
|
const contextData = (0, helpers_1.generateContextData)({
|
|
237
239
|
user,
|
|
238
240
|
services,
|
package/package.json
CHANGED
|
@@ -128,15 +128,20 @@ export async function customFunctionController(app: FastifyInstance) {
|
|
|
128
128
|
...(user || {})
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
+
const now = new Date()
|
|
131
132
|
const refreshToken = this.createRefreshToken(currentUserData)
|
|
132
133
|
const refreshTokenHash = hashToken(refreshToken)
|
|
133
134
|
await authDb.collection(refreshTokensCollection).insertOne({
|
|
134
135
|
userId: authUser._id,
|
|
135
136
|
tokenHash: refreshTokenHash,
|
|
136
|
-
createdAt:
|
|
137
|
+
createdAt: now,
|
|
137
138
|
expiresAt: new Date(Date.now() + refreshTokenTtlMs),
|
|
138
139
|
revokedAt: null
|
|
139
140
|
})
|
|
141
|
+
await authDb.collection(authCollection!).updateOne(
|
|
142
|
+
{ _id: authUser._id },
|
|
143
|
+
{ $set: { lastLoginAt: now } }
|
|
144
|
+
)
|
|
140
145
|
return {
|
|
141
146
|
access_token: this.createAccessToken(currentUserData),
|
|
142
147
|
refresh_token: refreshToken,
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
jest.mock('../../../../constants', () => ({
|
|
2
|
+
AUTH_CONFIG: {
|
|
3
|
+
authCollection: 'auth_users',
|
|
4
|
+
refreshTokensCollection: 'refresh_tokens',
|
|
5
|
+
resetPasswordCollection: 'reset_password_requests',
|
|
6
|
+
userCollection: 'users',
|
|
7
|
+
user_id_field: 'id',
|
|
8
|
+
authProviders: {
|
|
9
|
+
'local-userpass': {
|
|
10
|
+
disabled: false
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
resetPasswordConfig: {
|
|
14
|
+
runResetFunction: true,
|
|
15
|
+
resetFunctionName: 'customReset'
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
AUTH_DB_NAME: 'test-auth-db',
|
|
19
|
+
DB_NAME: 'test-db',
|
|
20
|
+
DEFAULT_CONFIG: {
|
|
21
|
+
RESET_PASSWORD_TTL_SECONDS: 3600,
|
|
22
|
+
AUTH_RATE_LIMIT_WINDOW_MS: 60000,
|
|
23
|
+
AUTH_LOGIN_MAX_ATTEMPTS: 5,
|
|
24
|
+
AUTH_REGISTER_MAX_ATTEMPTS: 5,
|
|
25
|
+
AUTH_RESET_MAX_ATTEMPTS: 5,
|
|
26
|
+
REFRESH_TOKEN_TTL_DAYS: 1
|
|
27
|
+
}
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
jest.mock('../../../../state', () => ({
|
|
31
|
+
StateManager: {
|
|
32
|
+
select: jest.fn((key: string) => {
|
|
33
|
+
if (key === 'functions') {
|
|
34
|
+
return {
|
|
35
|
+
customReset: { name: 'customReset', code: 'exports = async () => ({ status: "success" })' }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (key === 'services') {
|
|
39
|
+
return {}
|
|
40
|
+
}
|
|
41
|
+
return {}
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
}))
|
|
45
|
+
|
|
46
|
+
jest.mock('../../../../utils/context', () => ({
|
|
47
|
+
GenerateContext: jest.fn()
|
|
48
|
+
}))
|
|
49
|
+
|
|
50
|
+
jest.mock('../../../../utils/crypto', () => ({
|
|
51
|
+
comparePassword: jest.fn(),
|
|
52
|
+
generateToken: jest.fn(() => 'generated-token'),
|
|
53
|
+
hashPassword: jest.fn(async (password: string) => `hashed:${password}`),
|
|
54
|
+
hashToken: jest.fn(() => 'hashed-token')
|
|
55
|
+
}))
|
|
56
|
+
|
|
57
|
+
import { AUTH_ERRORS } from '../../../utils'
|
|
58
|
+
import { localUserPassController } from '../controller'
|
|
59
|
+
import { GenerateContext } from '../../../../utils/context'
|
|
60
|
+
import { hashPassword } from '../../../../utils/crypto'
|
|
61
|
+
|
|
62
|
+
describe('localUserPassController reset call', () => {
|
|
63
|
+
const buildApp = () => {
|
|
64
|
+
let resetCallHandler:
|
|
65
|
+
| ((req: { body: { email: string; password: string; arguments?: unknown[] }; ip: string }, res: { status: jest.Mock }) => Promise<unknown>)
|
|
66
|
+
| undefined
|
|
67
|
+
|
|
68
|
+
const authUsersCollection = {
|
|
69
|
+
findOne: jest.fn().mockResolvedValue({
|
|
70
|
+
_id: 'auth-user-1',
|
|
71
|
+
email: 'john@doe.com',
|
|
72
|
+
password: 'old-hash'
|
|
73
|
+
}),
|
|
74
|
+
updateOne: jest.fn().mockResolvedValue({ acknowledged: true })
|
|
75
|
+
}
|
|
76
|
+
const resetCollection = {
|
|
77
|
+
createIndex: jest.fn().mockResolvedValue('ok'),
|
|
78
|
+
updateOne: jest.fn().mockResolvedValue({ acknowledged: true }),
|
|
79
|
+
deleteOne: jest.fn().mockResolvedValue({ acknowledged: true }),
|
|
80
|
+
findOne: jest.fn()
|
|
81
|
+
}
|
|
82
|
+
const refreshCollection = {
|
|
83
|
+
createIndex: jest.fn().mockResolvedValue('ok'),
|
|
84
|
+
insertOne: jest.fn()
|
|
85
|
+
}
|
|
86
|
+
const usersCollection = {
|
|
87
|
+
findOne: jest.fn()
|
|
88
|
+
}
|
|
89
|
+
const db = {
|
|
90
|
+
collection: jest.fn((name: string) => {
|
|
91
|
+
if (name === 'auth_users') return authUsersCollection
|
|
92
|
+
if (name === 'reset_password_requests') return resetCollection
|
|
93
|
+
if (name === 'refresh_tokens') return refreshCollection
|
|
94
|
+
if (name === 'users') return usersCollection
|
|
95
|
+
return {}
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
const app = {
|
|
99
|
+
mongo: { client: { db: jest.fn().mockReturnValue(db) } },
|
|
100
|
+
post: jest.fn((path: string, _opts: unknown, handler: typeof resetCallHandler) => {
|
|
101
|
+
if (path === '/reset/call') {
|
|
102
|
+
resetCallHandler = handler
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { app, authUsersCollection, resetCollection, resetCallHandlerRef: () => resetCallHandler }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
beforeEach(() => {
|
|
111
|
+
jest.clearAllMocks()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('hashes and applies the password when the custom reset function returns success', async () => {
|
|
115
|
+
;(GenerateContext as jest.Mock).mockResolvedValue({ status: 'success' })
|
|
116
|
+
const { app, authUsersCollection, resetCollection, resetCallHandlerRef } = buildApp()
|
|
117
|
+
|
|
118
|
+
await localUserPassController(app as never)
|
|
119
|
+
|
|
120
|
+
const res = { status: jest.fn() }
|
|
121
|
+
const result = await resetCallHandlerRef()?.(
|
|
122
|
+
{
|
|
123
|
+
body: { email: 'john@doe.com', password: 'new-secret', arguments: ['extra'] },
|
|
124
|
+
ip: '127.0.0.1'
|
|
125
|
+
},
|
|
126
|
+
res
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
expect(GenerateContext).toHaveBeenCalledWith(expect.objectContaining({
|
|
130
|
+
args: [
|
|
131
|
+
{
|
|
132
|
+
token: 'generated-token',
|
|
133
|
+
tokenId: 'generated-token',
|
|
134
|
+
email: 'john@doe.com',
|
|
135
|
+
password: 'new-secret',
|
|
136
|
+
username: 'john@doe.com'
|
|
137
|
+
},
|
|
138
|
+
'extra'
|
|
139
|
+
],
|
|
140
|
+
runAsSystem: true
|
|
141
|
+
}))
|
|
142
|
+
expect(hashPassword).toHaveBeenCalledWith('new-secret')
|
|
143
|
+
expect(authUsersCollection.updateOne).toHaveBeenCalledWith(
|
|
144
|
+
{ email: 'john@doe.com' },
|
|
145
|
+
{ $set: { password: 'hashed:new-secret' } }
|
|
146
|
+
)
|
|
147
|
+
expect(resetCollection.deleteOne).toHaveBeenCalledWith({ email: 'john@doe.com' })
|
|
148
|
+
expect(res.status).toHaveBeenCalledWith(202)
|
|
149
|
+
expect(result).toEqual({ status: 'success' })
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('returns pending without changing the password when the custom reset function returns pending', async () => {
|
|
153
|
+
;(GenerateContext as jest.Mock).mockResolvedValue({ status: 'pending' })
|
|
154
|
+
const { app, authUsersCollection, resetCollection, resetCallHandlerRef } = buildApp()
|
|
155
|
+
|
|
156
|
+
await localUserPassController(app as never)
|
|
157
|
+
|
|
158
|
+
const res = { status: jest.fn() }
|
|
159
|
+
const result = await resetCallHandlerRef()?.(
|
|
160
|
+
{
|
|
161
|
+
body: { email: 'john@doe.com', password: 'new-secret' },
|
|
162
|
+
ip: '127.0.0.1'
|
|
163
|
+
},
|
|
164
|
+
res
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
expect(hashPassword).not.toHaveBeenCalled()
|
|
168
|
+
expect(authUsersCollection.updateOne).not.toHaveBeenCalledWith(
|
|
169
|
+
{ email: 'john@doe.com' },
|
|
170
|
+
expect.objectContaining({ $set: { password: expect.any(String) } })
|
|
171
|
+
)
|
|
172
|
+
expect(resetCollection.deleteOne).not.toHaveBeenCalled()
|
|
173
|
+
expect(res.status).toHaveBeenCalledWith(202)
|
|
174
|
+
expect(result).toEqual({ status: 'pending' })
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('rejects the request when the custom reset function returns fail', async () => {
|
|
178
|
+
;(GenerateContext as jest.Mock).mockResolvedValue({ status: 'fail' })
|
|
179
|
+
const { app, authUsersCollection, resetCollection, resetCallHandlerRef } = buildApp()
|
|
180
|
+
|
|
181
|
+
await localUserPassController(app as never)
|
|
182
|
+
|
|
183
|
+
const res = { status: jest.fn() }
|
|
184
|
+
|
|
185
|
+
await expect(
|
|
186
|
+
resetCallHandlerRef()?.(
|
|
187
|
+
{
|
|
188
|
+
body: { email: 'john@doe.com', password: 'new-secret' },
|
|
189
|
+
ip: '127.0.0.1'
|
|
190
|
+
},
|
|
191
|
+
res
|
|
192
|
+
)
|
|
193
|
+
).rejects.toThrow(AUTH_ERRORS.INVALID_RESET_PARAMS)
|
|
194
|
+
|
|
195
|
+
expect(hashPassword).not.toHaveBeenCalled()
|
|
196
|
+
expect(authUsersCollection.updateOne).not.toHaveBeenCalled()
|
|
197
|
+
expect(resetCollection.deleteOne).not.toHaveBeenCalled()
|
|
198
|
+
expect(res.status).not.toHaveBeenCalled()
|
|
199
|
+
})
|
|
200
|
+
})
|
|
@@ -27,6 +27,8 @@ import {
|
|
|
27
27
|
|
|
28
28
|
const rateLimitStore = new Map<string, number[]>()
|
|
29
29
|
|
|
30
|
+
type ResetFunctionResult = { status?: 'success' | 'pending' | 'fail' }
|
|
31
|
+
|
|
30
32
|
const isRateLimited = (key: string, maxAttempts: number, windowMs: number) => {
|
|
31
33
|
const now = Date.now()
|
|
32
34
|
const existing = rateLimitStore.get(key) ?? []
|
|
@@ -106,7 +108,7 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
106
108
|
const currentFunction = functionsList[resetPasswordConfig.resetFunctionName]
|
|
107
109
|
const baseArgs = { token, tokenId, email, password, username: email }
|
|
108
110
|
const args = Array.isArray(extraArguments) ? [baseArgs, ...extraArguments] : [baseArgs]
|
|
109
|
-
await GenerateContext({
|
|
111
|
+
const response = await GenerateContext({
|
|
110
112
|
args,
|
|
111
113
|
app,
|
|
112
114
|
rules: {},
|
|
@@ -114,11 +116,41 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
114
116
|
currentFunction,
|
|
115
117
|
functionName: resetPasswordConfig.resetFunctionName,
|
|
116
118
|
functionsList,
|
|
117
|
-
services
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
services,
|
|
120
|
+
runAsSystem: true
|
|
121
|
+
}) as ResetFunctionResult
|
|
122
|
+
const resetStatus = response?.status
|
|
123
|
+
|
|
124
|
+
if (resetStatus === 'success') {
|
|
125
|
+
if (!password) {
|
|
126
|
+
throw new Error(AUTH_ERRORS.INVALID_RESET_FUNCTION_RESPONSE)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const hashedPassword = await hashPassword(password)
|
|
130
|
+
await authDb.collection(authCollection!).updateOne(
|
|
131
|
+
{ email },
|
|
132
|
+
{
|
|
133
|
+
$set: {
|
|
134
|
+
password: hashedPassword
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
await authDb?.collection(resetPasswordCollection).deleteOne({ email })
|
|
139
|
+
return { status: 'success' as const }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (resetStatus === 'pending') {
|
|
143
|
+
return { status: 'pending' as const }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (resetStatus === 'fail') {
|
|
147
|
+
throw new Error(AUTH_ERRORS.INVALID_RESET_PARAMS)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
throw new Error(AUTH_ERRORS.INVALID_RESET_FUNCTION_RESPONSE)
|
|
120
151
|
}
|
|
121
152
|
|
|
153
|
+
return { status: 'pending' as const }
|
|
122
154
|
}
|
|
123
155
|
|
|
124
156
|
/**
|
|
@@ -283,15 +315,20 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
283
315
|
throw new Error(AUTH_ERRORS.USER_NOT_CONFIRMED)
|
|
284
316
|
}
|
|
285
317
|
|
|
318
|
+
const now = new Date()
|
|
286
319
|
const refreshToken = this.createRefreshToken(userWithCustomData)
|
|
287
320
|
const refreshTokenHash = hashToken(refreshToken)
|
|
288
321
|
await authDb.collection(refreshTokensCollection).insertOne({
|
|
289
322
|
userId: authUser._id,
|
|
290
323
|
tokenHash: refreshTokenHash,
|
|
291
|
-
createdAt:
|
|
324
|
+
createdAt: now,
|
|
292
325
|
expiresAt: new Date(Date.now() + refreshTokenTtlMs),
|
|
293
326
|
revokedAt: null
|
|
294
327
|
})
|
|
328
|
+
await authDb.collection(authCollection!).updateOne(
|
|
329
|
+
{ _id: authUser._id },
|
|
330
|
+
{ $set: { lastLoginAt: now } }
|
|
331
|
+
)
|
|
295
332
|
|
|
296
333
|
return {
|
|
297
334
|
access_token: this.createAccessToken(userWithCustomData),
|
|
@@ -347,15 +384,13 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
347
384
|
res.status(429)
|
|
348
385
|
return { message: 'Too many requests' }
|
|
349
386
|
}
|
|
350
|
-
await handleResetPasswordRequest(
|
|
387
|
+
const result = await handleResetPasswordRequest(
|
|
351
388
|
req.body.email,
|
|
352
389
|
req.body.password,
|
|
353
390
|
req.body.arguments
|
|
354
391
|
)
|
|
355
392
|
res.status(202)
|
|
356
|
-
return
|
|
357
|
-
status: 'ok'
|
|
358
|
-
}
|
|
393
|
+
return result
|
|
359
394
|
}
|
|
360
395
|
)
|
|
361
396
|
|
package/src/auth/utils.ts
CHANGED
|
@@ -117,6 +117,7 @@ export enum AUTH_ERRORS {
|
|
|
117
117
|
INVALID_TOKEN = 'Invalid refresh token provided',
|
|
118
118
|
INVALID_RESET_PARAMS = 'Invalid token or tokenId provided',
|
|
119
119
|
MISSING_RESET_FUNCTION = 'Missing reset function',
|
|
120
|
+
INVALID_RESET_FUNCTION_RESPONSE = 'Invalid reset function response',
|
|
120
121
|
USER_NOT_CONFIRMED = 'User not confirmed'
|
|
121
122
|
}
|
|
122
123
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { GenerateContext } from '../../../utils/context'
|
|
2
|
+
import { generateHandler } from '../utils'
|
|
3
|
+
|
|
4
|
+
jest.mock('../../../utils/context', () => ({
|
|
5
|
+
GenerateContext: jest.fn()
|
|
6
|
+
}))
|
|
7
|
+
|
|
8
|
+
const mockedGenerateContext = jest.mocked(GenerateContext)
|
|
9
|
+
|
|
10
|
+
describe('generateHandler', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
mockedGenerateContext.mockReset()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('allows endpoint functions to set custom response headers', async () => {
|
|
16
|
+
mockedGenerateContext.mockImplementation(async ({ args }) => {
|
|
17
|
+
const [, response] = args as [
|
|
18
|
+
{ body: { text: () => string; rawBody: Buffer | string | undefined } },
|
|
19
|
+
{
|
|
20
|
+
setStatusCode: (code: number) => void
|
|
21
|
+
setHeader: (name: string, value: string | number | readonly string[]) => void
|
|
22
|
+
setBody: (body: unknown) => void
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
response.setStatusCode(201)
|
|
27
|
+
response.setHeader('Content-Type', 'application/json')
|
|
28
|
+
response.setHeader('Cache-Control', 'no-store')
|
|
29
|
+
response.setBody(JSON.stringify({ ok: true }))
|
|
30
|
+
|
|
31
|
+
return { ignored: true }
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const handler = generateHandler({
|
|
35
|
+
app: {} as any,
|
|
36
|
+
currentFunction: { code: 'module.exports = function () {}' } as any,
|
|
37
|
+
functionName: 'endpointHandler',
|
|
38
|
+
functionsList: {
|
|
39
|
+
endpointHandler: { code: 'module.exports = function () {}' }
|
|
40
|
+
} as any,
|
|
41
|
+
http_method: 'POST',
|
|
42
|
+
rulesList: {} as any
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const res = {
|
|
46
|
+
status: jest.fn(),
|
|
47
|
+
header: jest.fn(),
|
|
48
|
+
send: jest.fn((body) => body)
|
|
49
|
+
} as any
|
|
50
|
+
|
|
51
|
+
const response = await handler({
|
|
52
|
+
body: { hello: 'world' },
|
|
53
|
+
headers: { accept: 'application/json' },
|
|
54
|
+
query: { page: '1' },
|
|
55
|
+
rawBody: '{"hello":"world"}',
|
|
56
|
+
user: { id: 'user-1' }
|
|
57
|
+
} as any, res)
|
|
58
|
+
|
|
59
|
+
expect(res.status).toHaveBeenCalledWith(201)
|
|
60
|
+
expect(res.header).toHaveBeenCalledWith('Content-Type', 'application/json')
|
|
61
|
+
expect(res.header).toHaveBeenCalledWith('Cache-Control', 'no-store')
|
|
62
|
+
expect(res.send).toHaveBeenCalledWith(JSON.stringify({ ok: true }))
|
|
63
|
+
expect(response).toBe(JSON.stringify({ ok: true }))
|
|
64
|
+
})
|
|
65
|
+
})
|
|
@@ -138,6 +138,9 @@ export const generateHandler = ({
|
|
|
138
138
|
setStatusCode: (code: number) => {
|
|
139
139
|
res.status(code)
|
|
140
140
|
},
|
|
141
|
+
setHeader: (name: string, value: string | number | readonly string[]) => {
|
|
142
|
+
res.header(name, value)
|
|
143
|
+
},
|
|
141
144
|
setBody: (body: unknown) => {
|
|
142
145
|
customResponseBody.data = body
|
|
143
146
|
}
|
|
@@ -24,7 +24,7 @@ type Config = {
|
|
|
24
24
|
isAutoTrigger?: boolean
|
|
25
25
|
match: Record<string, unknown>
|
|
26
26
|
operation_types: string[]
|
|
27
|
-
operation_type?: 'CREATE' | 'DELETE' | 'LOGOUT'
|
|
27
|
+
operation_type?: 'CREATE' | 'DELETE' | 'LOGIN' | 'LOGOUT'
|
|
28
28
|
providers?: string[]
|
|
29
29
|
project: Record<string, unknown>
|
|
30
30
|
service_name: string
|
|
@@ -172,6 +172,7 @@ const handleCronTrigger = async ({
|
|
|
172
172
|
const mapOpInverse = {
|
|
173
173
|
CREATE: ['insert', 'update', 'replace'],
|
|
174
174
|
DELETE: ['delete'],
|
|
175
|
+
LOGIN: ['insert', 'update'],
|
|
175
176
|
LOGOUT: ['update'],
|
|
176
177
|
}
|
|
177
178
|
|
|
@@ -306,6 +307,8 @@ const handleAuthenticationTrigger = async ({
|
|
|
306
307
|
const isUpdate = operationType === 'update'
|
|
307
308
|
const isReplace = operationType === 'replace'
|
|
308
309
|
const isDelete = operationType === 'delete'
|
|
310
|
+
const isLoginInsert = isInsert && !!(fullDocument as Record<string, unknown> | null)?.lastLoginAt
|
|
311
|
+
const isLoginUpdate = isUpdate && !!updatedFields && 'lastLoginAt' in updatedFields
|
|
309
312
|
const isLogoutUpdate = isUpdate && !!updatedFields && 'lastLogoutAt' in updatedFields
|
|
310
313
|
|
|
311
314
|
let confirmedCandidate = false
|
|
@@ -397,6 +400,63 @@ const handleAuthenticationTrigger = async ({
|
|
|
397
400
|
return
|
|
398
401
|
}
|
|
399
402
|
|
|
403
|
+
if (operation_type === 'LOGIN') {
|
|
404
|
+
if (!isLoginInsert && !isLoginUpdate) {
|
|
405
|
+
return
|
|
406
|
+
}
|
|
407
|
+
let loginDocument = fullDocument ?? confirmedDocument
|
|
408
|
+
if (!loginDocument && documentKey?._id) {
|
|
409
|
+
loginDocument = await collection.findOne({
|
|
410
|
+
_id: documentKey._id
|
|
411
|
+
}) as Record<string, unknown> | null
|
|
412
|
+
}
|
|
413
|
+
if (!matchesProviderFilter(loginDocument, providerFilter)) {
|
|
414
|
+
return
|
|
415
|
+
}
|
|
416
|
+
const userData = buildUserData(loginDocument)
|
|
417
|
+
if (!userData) {
|
|
418
|
+
return
|
|
419
|
+
}
|
|
420
|
+
const op = {
|
|
421
|
+
operationType: 'LOGIN',
|
|
422
|
+
fullDocument,
|
|
423
|
+
fullDocumentBeforeChange,
|
|
424
|
+
documentKey,
|
|
425
|
+
updateDescription
|
|
426
|
+
}
|
|
427
|
+
try {
|
|
428
|
+
emitTriggerEvent({
|
|
429
|
+
status: 'fired',
|
|
430
|
+
triggerName,
|
|
431
|
+
triggerType,
|
|
432
|
+
functionName,
|
|
433
|
+
meta: { ...baseMeta, event: 'LOGIN' }
|
|
434
|
+
})
|
|
435
|
+
await GenerateContext({
|
|
436
|
+
args: [{ user: userData, ...op }],
|
|
437
|
+
app,
|
|
438
|
+
rules: StateManager.select("rules"),
|
|
439
|
+
user: {}, // TODO from currentUser ??
|
|
440
|
+
currentFunction: triggerHandler,
|
|
441
|
+
functionName,
|
|
442
|
+
functionsList,
|
|
443
|
+
services,
|
|
444
|
+
runAsSystem: true
|
|
445
|
+
})
|
|
446
|
+
} catch (error) {
|
|
447
|
+
emitTriggerEvent({
|
|
448
|
+
status: 'error',
|
|
449
|
+
triggerName,
|
|
450
|
+
triggerType,
|
|
451
|
+
functionName,
|
|
452
|
+
meta: { ...baseMeta, event: 'LOGIN' },
|
|
453
|
+
error
|
|
454
|
+
})
|
|
455
|
+
console.log("🚀 ~ handleAuthenticationTrigger ~ error:", error)
|
|
456
|
+
}
|
|
457
|
+
return
|
|
458
|
+
}
|
|
459
|
+
|
|
400
460
|
if (isDelete) {
|
|
401
461
|
if (isAutoTrigger || operation_type !== 'DELETE') {
|
|
402
462
|
return
|
|
@@ -4,7 +4,7 @@ import { Functions } from '../../features/functions/interface'
|
|
|
4
4
|
const mockServices = {
|
|
5
5
|
api: jest.fn().mockReturnValue({}),
|
|
6
6
|
aws: jest.fn().mockReturnValue({}),
|
|
7
|
-
'mongodb-atlas': jest.fn()
|
|
7
|
+
'mongodb-atlas': jest.fn((_app, options) => options ?? {})
|
|
8
8
|
} as any
|
|
9
9
|
|
|
10
10
|
describe('context.functions.execute compatibility', () => {
|
|
@@ -91,4 +91,30 @@ describe('context.functions.execute compatibility', () => {
|
|
|
91
91
|
|
|
92
92
|
expect(result).toBe(true)
|
|
93
93
|
})
|
|
94
|
+
|
|
95
|
+
it('propagates run_as_system to child functions executed through context.functions.execute', () => {
|
|
96
|
+
const functionsList = {
|
|
97
|
+
caller: {
|
|
98
|
+
run_as_system: true,
|
|
99
|
+
code: 'module.exports = function() { return context.functions.execute("target") }'
|
|
100
|
+
},
|
|
101
|
+
target: {
|
|
102
|
+
run_as_system: false,
|
|
103
|
+
code: 'module.exports = function() { return context.services.get("mongodb-atlas").run_as_system }'
|
|
104
|
+
}
|
|
105
|
+
} as Functions
|
|
106
|
+
|
|
107
|
+
const result = GenerateContextSync({
|
|
108
|
+
args: [],
|
|
109
|
+
app: {} as any,
|
|
110
|
+
rules: {} as any,
|
|
111
|
+
user: {} as any,
|
|
112
|
+
currentFunction: functionsList.caller,
|
|
113
|
+
functionsList,
|
|
114
|
+
services: mockServices,
|
|
115
|
+
functionName: 'caller'
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
expect(result).toBe(true)
|
|
119
|
+
})
|
|
94
120
|
})
|
|
@@ -79,7 +79,11 @@ describe('generateContextData', () => {
|
|
|
79
79
|
mockErrorLog.mockRestore()
|
|
80
80
|
|
|
81
81
|
context.functions.execute('test')
|
|
82
|
-
expect(GenerateContextSyncMock).
|
|
82
|
+
expect(GenerateContextSyncMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
83
|
+
currentFunction,
|
|
84
|
+
functionName: 'test',
|
|
85
|
+
runAsSystem: currentFunction.run_as_system
|
|
86
|
+
}))
|
|
83
87
|
|
|
84
88
|
const token = jwt.sign(
|
|
85
89
|
{ sub: 'user', role: 'admin' },
|
|
@@ -205,16 +205,17 @@ export const generateContextData = ({
|
|
|
205
205
|
https: getService('api'),
|
|
206
206
|
functions: {
|
|
207
207
|
execute: (name: keyof typeof functionsList, ...args: Arguments) => {
|
|
208
|
-
const
|
|
208
|
+
const targetFunction = functionsList[name] as Function
|
|
209
209
|
return GenerateContextSync({
|
|
210
210
|
args,
|
|
211
211
|
app,
|
|
212
212
|
rules,
|
|
213
213
|
user,
|
|
214
|
-
currentFunction,
|
|
214
|
+
currentFunction: targetFunction,
|
|
215
215
|
functionName: String(name),
|
|
216
216
|
functionsList,
|
|
217
217
|
services,
|
|
218
|
+
runAsSystem: currentFunction.run_as_system,
|
|
218
219
|
deserializeArgs: false
|
|
219
220
|
})
|
|
220
221
|
}
|
|
@@ -189,8 +189,8 @@ export async function GenerateContext({
|
|
|
189
189
|
if (!currentFunction) return
|
|
190
190
|
|
|
191
191
|
const functionsQueue = StateManager.select("functionsQueue")
|
|
192
|
-
|
|
193
|
-
const functionToRun = { run_as_system:
|
|
192
|
+
const effectiveRunAsSystem = Boolean(runAsSystem || currentFunction.run_as_system)
|
|
193
|
+
const functionToRun = { ...currentFunction, run_as_system: effectiveRunAsSystem }
|
|
194
194
|
|
|
195
195
|
const run = async () => {
|
|
196
196
|
|
|
@@ -309,7 +309,8 @@ export function GenerateContextSync({
|
|
|
309
309
|
}: GenerateContextParams): unknown {
|
|
310
310
|
if (!currentFunction) return
|
|
311
311
|
|
|
312
|
-
const
|
|
312
|
+
const effectiveRunAsSystem = Boolean(runAsSystem || currentFunction.run_as_system)
|
|
313
|
+
const functionToRun = { ...currentFunction, run_as_system: effectiveRunAsSystem }
|
|
313
314
|
const contextData = generateContextData({
|
|
314
315
|
user,
|
|
315
316
|
services,
|