@flowerforce/flowerbase 1.2.1-beta.2 → 1.2.1-beta.21
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/README.md +37 -6
- package/dist/auth/controller.d.ts.map +1 -1
- package/dist/auth/controller.js +55 -4
- package/dist/auth/plugins/jwt.d.ts.map +1 -1
- package/dist/auth/plugins/jwt.js +52 -6
- package/dist/auth/providers/anon-user/controller.d.ts +8 -0
- package/dist/auth/providers/anon-user/controller.d.ts.map +1 -0
- package/dist/auth/providers/anon-user/controller.js +90 -0
- package/dist/auth/providers/anon-user/dtos.d.ts +10 -0
- package/dist/auth/providers/anon-user/dtos.d.ts.map +1 -0
- package/dist/auth/providers/anon-user/dtos.js +2 -0
- package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
- package/dist/auth/providers/custom-function/controller.js +35 -25
- package/dist/auth/providers/custom-function/dtos.d.ts +4 -1
- package/dist/auth/providers/custom-function/dtos.d.ts.map +1 -1
- package/dist/auth/providers/local-userpass/controller.d.ts.map +1 -1
- package/dist/auth/providers/local-userpass/controller.js +159 -73
- package/dist/auth/providers/local-userpass/dtos.d.ts +17 -2
- package/dist/auth/providers/local-userpass/dtos.d.ts.map +1 -1
- package/dist/auth/utils.d.ts +76 -14
- package/dist/auth/utils.d.ts.map +1 -1
- package/dist/auth/utils.js +55 -61
- package/dist/constants.d.ts +12 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +16 -4
- package/dist/features/functions/controller.d.ts.map +1 -1
- package/dist/features/functions/controller.js +31 -12
- package/dist/features/functions/dtos.d.ts +3 -0
- package/dist/features/functions/dtos.d.ts.map +1 -1
- package/dist/features/functions/interface.d.ts +3 -0
- package/dist/features/functions/interface.d.ts.map +1 -1
- package/dist/features/functions/utils.d.ts +3 -2
- package/dist/features/functions/utils.d.ts.map +1 -1
- package/dist/features/functions/utils.js +19 -7
- package/dist/features/triggers/index.d.ts.map +1 -1
- package/dist/features/triggers/index.js +49 -7
- package/dist/features/triggers/interface.d.ts +1 -0
- 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 +67 -26
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -13
- package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/index.js +72 -2
- package/dist/services/mongodb-atlas/model.d.ts +3 -2
- package/dist/services/mongodb-atlas/model.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/utils.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/utils.js +3 -1
- package/dist/shared/handleUserRegistration.d.ts.map +1 -1
- package/dist/shared/handleUserRegistration.js +66 -1
- package/dist/shared/models/handleUserRegistration.model.d.ts +2 -1
- package/dist/shared/models/handleUserRegistration.model.d.ts.map +1 -1
- package/dist/shared/models/handleUserRegistration.model.js +1 -0
- package/dist/utils/context/helpers.d.ts +6 -6
- package/dist/utils/context/helpers.d.ts.map +1 -1
- package/dist/utils/context/index.d.ts +1 -1
- package/dist/utils/context/index.d.ts.map +1 -1
- package/dist/utils/context/index.js +176 -9
- package/dist/utils/context/interface.d.ts +1 -1
- package/dist/utils/context/interface.d.ts.map +1 -1
- package/dist/utils/crypto/index.d.ts +1 -0
- package/dist/utils/crypto/index.d.ts.map +1 -1
- package/dist/utils/crypto/index.js +6 -2
- package/dist/utils/initializer/exposeRoutes.js +1 -1
- package/dist/utils/initializer/registerPlugins.d.ts.map +1 -1
- package/dist/utils/initializer/registerPlugins.js +12 -4
- package/dist/utils/roles/helpers.js +2 -1
- package/dist/utils/rules-matcher/utils.d.ts.map +1 -1
- package/dist/utils/rules-matcher/utils.js +3 -0
- package/package.json +1 -2
- package/src/auth/controller.ts +71 -5
- package/src/auth/plugins/jwt.test.ts +93 -0
- package/src/auth/plugins/jwt.ts +67 -8
- package/src/auth/providers/anon-user/controller.ts +91 -0
- package/src/auth/providers/anon-user/dtos.ts +10 -0
- package/src/auth/providers/custom-function/controller.ts +40 -31
- package/src/auth/providers/custom-function/dtos.ts +5 -1
- package/src/auth/providers/local-userpass/controller.ts +211 -101
- package/src/auth/providers/local-userpass/dtos.ts +20 -2
- package/src/auth/utils.ts +66 -83
- package/src/constants.ts +14 -2
- package/src/features/functions/controller.ts +42 -12
- package/src/features/functions/dtos.ts +3 -0
- package/src/features/functions/interface.ts +3 -0
- package/src/features/functions/utils.ts +29 -8
- package/src/features/triggers/index.ts +44 -1
- package/src/features/triggers/interface.ts +1 -0
- package/src/features/triggers/utils.ts +89 -37
- package/src/index.ts +49 -13
- package/src/services/mongodb-atlas/__tests__/findOneAndUpdate.test.ts +95 -0
- package/src/services/mongodb-atlas/index.ts +665 -567
- package/src/services/mongodb-atlas/model.ts +16 -3
- package/src/services/mongodb-atlas/utils.ts +3 -0
- package/src/shared/handleUserRegistration.ts +83 -2
- package/src/shared/models/handleUserRegistration.model.ts +2 -1
- package/src/utils/__tests__/registerPlugins.test.ts +5 -1
- package/src/utils/context/index.ts +238 -18
- package/src/utils/context/interface.ts +1 -1
- package/src/utils/crypto/index.ts +5 -1
- package/src/utils/initializer/exposeRoutes.ts +1 -1
- package/src/utils/initializer/registerPlugins.ts +8 -0
- package/src/utils/roles/helpers.ts +3 -2
- package/src/utils/rules-matcher/utils.ts +3 -0
|
@@ -14,10 +14,100 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.GenerateContext = GenerateContext;
|
|
16
16
|
const node_module_1 = require("node:module");
|
|
17
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
18
|
+
const node_url_1 = require("node:url");
|
|
17
19
|
const vm_1 = __importDefault(require("vm"));
|
|
18
20
|
const bson_1 = require("bson");
|
|
19
21
|
const state_1 = require("../../state");
|
|
20
22
|
const helpers_1 = require("./helpers");
|
|
23
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
24
|
+
const transformImportsToRequire = (code) => {
|
|
25
|
+
let importIndex = 0;
|
|
26
|
+
const lines = code.split(/\r?\n/);
|
|
27
|
+
return lines
|
|
28
|
+
.map((line) => {
|
|
29
|
+
const trimmed = line.trim();
|
|
30
|
+
if (/^import\s+type\s+/.test(trimmed)) {
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
const sideEffectMatch = trimmed.match(/^import\s+['"]([^'"]+)['"]\s*;?$/);
|
|
34
|
+
if (sideEffectMatch) {
|
|
35
|
+
return `require('${sideEffectMatch[1]}')`;
|
|
36
|
+
}
|
|
37
|
+
const match = trimmed.match(/^import\s+(.+?)\s+from\s+['"]([^'"]+)['"]\s*;?$/);
|
|
38
|
+
if (!match)
|
|
39
|
+
return line;
|
|
40
|
+
const [, importClause, source] = match;
|
|
41
|
+
const clause = importClause.trim();
|
|
42
|
+
if (clause.startsWith('{') && clause.endsWith('}')) {
|
|
43
|
+
const named = clause.slice(1, -1).trim();
|
|
44
|
+
return `const { ${named} } = require('${source}')`;
|
|
45
|
+
}
|
|
46
|
+
const namespaceMatch = clause.match(/^\*\s+as\s+(\w+)$/);
|
|
47
|
+
if (namespaceMatch) {
|
|
48
|
+
return `const ${namespaceMatch[1]} = require('${source}')`;
|
|
49
|
+
}
|
|
50
|
+
if (clause.includes(',')) {
|
|
51
|
+
const [defaultPart, restRaw] = clause.split(',', 2);
|
|
52
|
+
const defaultName = defaultPart.trim();
|
|
53
|
+
const rest = restRaw.trim();
|
|
54
|
+
const tmpName = `__fb_import_${importIndex++}`;
|
|
55
|
+
const linesOut = [`const ${tmpName} = require('${source}')`];
|
|
56
|
+
if (defaultName) {
|
|
57
|
+
linesOut.push(`const ${defaultName} = ${tmpName}`);
|
|
58
|
+
}
|
|
59
|
+
if (rest.startsWith('{') && rest.endsWith('}')) {
|
|
60
|
+
const named = rest.slice(1, -1).trim();
|
|
61
|
+
linesOut.push(`const { ${named} } = ${tmpName}`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
const nsMatch = rest.match(/^\*\s+as\s+(\w+)$/);
|
|
65
|
+
if (nsMatch) {
|
|
66
|
+
linesOut.push(`const ${nsMatch[1]} = ${tmpName}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return linesOut.join('\n');
|
|
70
|
+
}
|
|
71
|
+
return `const ${clause} = require('${source}')`;
|
|
72
|
+
})
|
|
73
|
+
.join('\n');
|
|
74
|
+
};
|
|
75
|
+
const wrapEsmModule = (code) => {
|
|
76
|
+
const prelude = [
|
|
77
|
+
'const __fb_module = { exports: {} };',
|
|
78
|
+
'let exports = __fb_module.exports;',
|
|
79
|
+
'let module = __fb_module;',
|
|
80
|
+
'const __fb_require = globalThis.__fb_require;',
|
|
81
|
+
'const require = __fb_require;',
|
|
82
|
+
'const __filename = globalThis.__fb_filename;',
|
|
83
|
+
'const __dirname = globalThis.__fb_dirname;'
|
|
84
|
+
].join('\n');
|
|
85
|
+
const trailer = [
|
|
86
|
+
'globalThis.__fb_module = __fb_module;',
|
|
87
|
+
'globalThis.__fb_exports = exports;'
|
|
88
|
+
].join('\n');
|
|
89
|
+
return `${prelude}\n${code}\n${trailer}`;
|
|
90
|
+
};
|
|
91
|
+
const resolveImportTarget = (specifier, customRequire) => {
|
|
92
|
+
try {
|
|
93
|
+
const resolved = customRequire.resolve(specifier);
|
|
94
|
+
if (resolved.startsWith('node:'))
|
|
95
|
+
return resolved;
|
|
96
|
+
if (node_path_1.default.isAbsolute(resolved)) {
|
|
97
|
+
return (0, node_url_1.pathToFileURL)(resolved).href;
|
|
98
|
+
}
|
|
99
|
+
return resolved;
|
|
100
|
+
}
|
|
101
|
+
catch (_a) {
|
|
102
|
+
return specifier;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const shouldFallbackFromVmModules = (error) => {
|
|
106
|
+
if (!error || typeof error !== 'object')
|
|
107
|
+
return false;
|
|
108
|
+
const code = error.code;
|
|
109
|
+
return code === 'ERR_VM_MODULES_DISABLED' || code === 'ERR_VM_MODULES_NOT_SUPPORTED';
|
|
110
|
+
};
|
|
21
111
|
/**
|
|
22
112
|
* > Used to generate the current context
|
|
23
113
|
* @testable
|
|
@@ -36,7 +126,7 @@ function GenerateContext(_a) {
|
|
|
36
126
|
const functionsQueue = state_1.StateManager.select("functionsQueue");
|
|
37
127
|
const functionToRun = Object.assign({ run_as_system: runAsSystem }, currentFunction);
|
|
38
128
|
const run = () => __awaiter(this, void 0, void 0, function* () {
|
|
39
|
-
var _a, _b;
|
|
129
|
+
var _a, _b, _c;
|
|
40
130
|
const contextData = (0, helpers_1.generateContextData)({
|
|
41
131
|
user,
|
|
42
132
|
services,
|
|
@@ -47,21 +137,98 @@ function GenerateContext(_a) {
|
|
|
47
137
|
GenerateContext,
|
|
48
138
|
request
|
|
49
139
|
});
|
|
140
|
+
const isExportedFunction = (value) => typeof value === 'function';
|
|
141
|
+
const getDefaultExport = (value) => {
|
|
142
|
+
if (!value || typeof value !== 'object')
|
|
143
|
+
return undefined;
|
|
144
|
+
if (!('default' in value))
|
|
145
|
+
return undefined;
|
|
146
|
+
const maybeDefault = value.default;
|
|
147
|
+
return isExportedFunction(maybeDefault) ? maybeDefault : undefined;
|
|
148
|
+
};
|
|
149
|
+
const resolveExport = (ctx) => {
|
|
150
|
+
var _a, _b, _c, _d, _e;
|
|
151
|
+
const moduleExports = (_b = (_a = ctx.module) === null || _a === void 0 ? void 0 : _a.exports) !== null && _b !== void 0 ? _b : (_c = ctx.__fb_module) === null || _c === void 0 ? void 0 : _c.exports;
|
|
152
|
+
if (isExportedFunction(moduleExports))
|
|
153
|
+
return moduleExports;
|
|
154
|
+
const contextExports = (_d = ctx.exports) !== null && _d !== void 0 ? _d : ctx.__fb_exports;
|
|
155
|
+
if (isExportedFunction(contextExports))
|
|
156
|
+
return contextExports;
|
|
157
|
+
return (_e = getDefaultExport(moduleExports)) !== null && _e !== void 0 ? _e : getDefaultExport(contextExports);
|
|
158
|
+
};
|
|
159
|
+
const sandboxModule = { exports: {} };
|
|
50
160
|
try {
|
|
51
161
|
const entryFile = (_b = (_a = require.main) === null || _a === void 0 ? void 0 : _a.filename) !== null && _b !== void 0 ? _b : process.cwd();
|
|
52
162
|
const customRequire = (0, node_module_1.createRequire)(entryFile);
|
|
53
|
-
|
|
54
|
-
|
|
163
|
+
const vmContext = vm_1.default.createContext(Object.assign(Object.assign({}, contextData), { require: customRequire, exports: sandboxModule.exports, module: sandboxModule, __filename,
|
|
164
|
+
__dirname, __fb_require: customRequire, __fb_filename: __filename, __fb_dirname: __dirname }));
|
|
165
|
+
const vmModules = vm_1.default;
|
|
166
|
+
const hasStaticImport = /\bimport\s+/.test(functionToRun.code);
|
|
167
|
+
let usedVmModules = false;
|
|
168
|
+
if (hasStaticImport && vmModules.SourceTextModule && vmModules.SyntheticModule) {
|
|
169
|
+
try {
|
|
170
|
+
const moduleCache = new Map();
|
|
171
|
+
const loadModule = (specifier) => __awaiter(this, void 0, void 0, function* () {
|
|
172
|
+
const importTarget = resolveImportTarget(specifier, customRequire);
|
|
173
|
+
const cached = moduleCache.get(importTarget);
|
|
174
|
+
if (cached)
|
|
175
|
+
return cached;
|
|
176
|
+
const namespace = yield dynamicImport(importTarget);
|
|
177
|
+
const exportNames = Object.keys(namespace);
|
|
178
|
+
if ('default' in namespace && !exportNames.includes('default')) {
|
|
179
|
+
exportNames.push('default');
|
|
180
|
+
}
|
|
181
|
+
const syntheticModule = new vmModules.SyntheticModule(exportNames, function () {
|
|
182
|
+
for (const name of exportNames) {
|
|
183
|
+
this.setExport(name, namespace[name]);
|
|
184
|
+
}
|
|
185
|
+
}, { context: vmContext, identifier: importTarget });
|
|
186
|
+
moduleCache.set(importTarget, syntheticModule);
|
|
187
|
+
return syntheticModule;
|
|
188
|
+
});
|
|
189
|
+
const importModuleDynamically = ((specifier) => loadModule(specifier));
|
|
190
|
+
const sourceModule = new vmModules.SourceTextModule(wrapEsmModule(functionToRun.code), {
|
|
191
|
+
context: vmContext,
|
|
192
|
+
identifier: entryFile,
|
|
193
|
+
initializeImportMeta: (meta) => {
|
|
194
|
+
meta.url = (0, node_url_1.pathToFileURL)(entryFile).href;
|
|
195
|
+
},
|
|
196
|
+
importModuleDynamically
|
|
197
|
+
});
|
|
198
|
+
yield sourceModule.link(loadModule);
|
|
199
|
+
yield sourceModule.evaluate();
|
|
200
|
+
usedVmModules = true;
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
if (!shouldFallbackFromVmModules(error)) {
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (!usedVmModules) {
|
|
209
|
+
const codeToRun = functionToRun.code.includes('import ')
|
|
210
|
+
? transformImportsToRequire(functionToRun.code)
|
|
211
|
+
: functionToRun.code;
|
|
212
|
+
vm_1.default.runInContext(codeToRun, vmContext);
|
|
213
|
+
}
|
|
214
|
+
sandboxModule.exports = (_c = resolveExport(vmContext)) !== null && _c !== void 0 ? _c : sandboxModule.exports;
|
|
55
215
|
}
|
|
56
|
-
catch (
|
|
57
|
-
console.
|
|
216
|
+
catch (error) {
|
|
217
|
+
console.error(error);
|
|
218
|
+
throw error;
|
|
58
219
|
}
|
|
59
220
|
if (deserializeArgs) {
|
|
60
|
-
return yield
|
|
221
|
+
return yield sandboxModule.exports(...bson_1.EJSON.deserialize(args));
|
|
61
222
|
}
|
|
62
|
-
return yield
|
|
223
|
+
return yield sandboxModule.exports(...args);
|
|
63
224
|
});
|
|
64
|
-
|
|
65
|
-
|
|
225
|
+
try {
|
|
226
|
+
const res = yield functionsQueue.add(run, enqueue);
|
|
227
|
+
return res;
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
console.error(error);
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
66
233
|
});
|
|
67
234
|
}
|
|
@@ -18,7 +18,7 @@ export interface GenerateContextParams {
|
|
|
18
18
|
}
|
|
19
19
|
type ContextRequest = Pick<FastifyRequest, "ips" | "host" | "hostname" | "url" | "method" | "ip" | "id">;
|
|
20
20
|
export interface GenerateContextDataParams extends Omit<GenerateContextParams, 'args'> {
|
|
21
|
-
GenerateContext: (params: GenerateContextParams) => Promise<
|
|
21
|
+
GenerateContext: (params: GenerateContextParams) => Promise<unknown>;
|
|
22
22
|
}
|
|
23
23
|
export {};
|
|
24
24
|
//# sourceMappingURL=interface.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../../src/utils/context/interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACzD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAA;AACxE,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAEnD,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,eAAe,CAAA;IACpB,eAAe,EAAE,QAAQ,CAAA;IACzB,aAAa,EAAE,SAAS,CAAA;IACxB,KAAK,EAAE,KAAK,CAAA;IACZ,IAAI,EAAE,IAAI,CAAA;IACV,QAAQ,EAAE,QAAQ,CAAA;IAClB,IAAI,EAAE,SAAS,CAAA;IACf,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,cAAc,CAAA;CACzB;AAED,KAAK,cAAc,GAAG,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,MAAM,GAAG,UAAU,GAAG,KAAK,GAAG,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC,CAAA;AACxG,MAAM,WAAW,yBAA0B,SAAQ,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../../src/utils/context/interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACzD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAA;AACxE,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAEnD,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,eAAe,CAAA;IACpB,eAAe,EAAE,QAAQ,CAAA;IACzB,aAAa,EAAE,SAAS,CAAA;IACxB,KAAK,EAAE,KAAK,CAAA;IACZ,IAAI,EAAE,IAAI,CAAA;IACV,QAAQ,EAAE,QAAQ,CAAA;IAClB,IAAI,EAAE,SAAS,CAAA;IACf,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,cAAc,CAAA;CACzB;AAED,KAAK,cAAc,GAAG,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,MAAM,GAAG,UAAU,GAAG,KAAK,GAAG,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC,CAAA;AACxG,MAAM,WAAW,yBAA0B,SAAQ,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CACrE"}
|
|
@@ -16,4 +16,5 @@ export declare const comparePassword: (plaintext: string, storedPassword: string
|
|
|
16
16
|
* @param length -> the token length
|
|
17
17
|
*/
|
|
18
18
|
export declare const generateToken: (length?: number) => string;
|
|
19
|
+
export declare const hashToken: (token: string) => string;
|
|
19
20
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/crypto/index.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAU,WAAW,MAAM,oBAInD,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAU,WAAW,MAAM,EAAE,gBAAgB,MAAM,qBAU9E,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,eAAW,WAExC,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/crypto/index.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAU,WAAW,MAAM,oBAInD,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAU,WAAW,MAAM,EAAE,gBAAgB,MAAM,qBAU9E,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,eAAW,WAExC,CAAA;AAED,eAAO,MAAM,SAAS,GAAI,OAAO,MAAM,WAEtC,CAAA"}
|
|
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.generateToken = exports.comparePassword = exports.hashPassword = void 0;
|
|
15
|
+
exports.hashToken = exports.generateToken = exports.comparePassword = exports.hashPassword = void 0;
|
|
16
16
|
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
17
17
|
const node_util_1 = require("node:util");
|
|
18
18
|
const scrypt = (0, node_util_1.promisify)(node_crypto_1.default.scrypt);
|
|
@@ -47,7 +47,11 @@ exports.comparePassword = comparePassword;
|
|
|
47
47
|
* > Generate a random token
|
|
48
48
|
* @param length -> the token length
|
|
49
49
|
*/
|
|
50
|
-
const generateToken = (length =
|
|
50
|
+
const generateToken = (length = 64) => {
|
|
51
51
|
return node_crypto_1.default.randomBytes(length).toString('hex');
|
|
52
52
|
};
|
|
53
53
|
exports.generateToken = generateToken;
|
|
54
|
+
const hashToken = (token) => {
|
|
55
|
+
return node_crypto_1.default.createHash('sha256').update(token).digest('hex');
|
|
56
|
+
};
|
|
57
|
+
exports.hashToken = hashToken;
|
|
@@ -28,7 +28,7 @@ const exposeRoutes = (fastify) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
28
28
|
const headerHost = (_b = req.headers.host) !== null && _b !== void 0 ? _b : 'localhost:3000';
|
|
29
29
|
const hostname = headerHost.split(':')[0];
|
|
30
30
|
const port = (_c = constants_1.DEFAULT_CONFIG === null || constants_1.DEFAULT_CONFIG === void 0 ? void 0 : constants_1.DEFAULT_CONFIG.PORT) !== null && _c !== void 0 ? _c : 3000;
|
|
31
|
-
const host = `${hostname}:${port}`;
|
|
31
|
+
const host = port === 8080 ? hostname : `${hostname}:${port}`;
|
|
32
32
|
const wsSchema = 'wss';
|
|
33
33
|
return {
|
|
34
34
|
deployment_model: 'LOCAL',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registerPlugins.d.ts","sourceRoot":"","sources":["../../../src/utils/initializer/registerPlugins.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAEzC,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"registerPlugins.d.ts","sourceRoot":"","sources":["../../../src/utils/initializer/registerPlugins.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAEzC,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAOnC,OAAO,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAA;AAE9D,KAAK,gBAAgB,GAAG,eAAe,CAAC,UAAU,CAAC,CAAA;AAGnD,KAAK,qBAAqB,GAAG;IAC3B,QAAQ,EAAE,gBAAgB,CAAA;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,SAAS,CAAA;IACxB,UAAU,CAAC,EAAE,UAAU,CAAA;CACxB,CAAA;AAQD;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAU,gEAMnC,qBAAqB,kBAsBvB,CAAA"}
|
|
@@ -18,8 +18,9 @@ const mongodb_1 = __importDefault(require("@fastify/mongodb"));
|
|
|
18
18
|
const fastify_raw_body_1 = __importDefault(require("fastify-raw-body"));
|
|
19
19
|
const controller_1 = require("../../auth/controller");
|
|
20
20
|
const jwt_1 = __importDefault(require("../../auth/plugins/jwt"));
|
|
21
|
-
const controller_2 = require("../../auth/providers/
|
|
22
|
-
const controller_3 = require("../../auth/providers/
|
|
21
|
+
const controller_2 = require("../../auth/providers/anon-user/controller");
|
|
22
|
+
const controller_3 = require("../../auth/providers/custom-function/controller");
|
|
23
|
+
const controller_4 = require("../../auth/providers/local-userpass/controller");
|
|
23
24
|
const constants_1 = require("../../constants");
|
|
24
25
|
/**
|
|
25
26
|
* > Used to register all plugins
|
|
@@ -104,17 +105,24 @@ const getRegisterConfig = (_a) => __awaiter(void 0, [_a], void 0, function* ({ m
|
|
|
104
105
|
},
|
|
105
106
|
{
|
|
106
107
|
pluginName: 'localUserPassController',
|
|
107
|
-
plugin:
|
|
108
|
+
plugin: controller_4.localUserPassController,
|
|
108
109
|
options: {
|
|
109
110
|
prefix: `${constants_1.API_VERSION}/app/:appId/auth/providers/local-userpass`
|
|
110
111
|
}
|
|
111
112
|
},
|
|
112
113
|
{
|
|
113
114
|
pluginName: 'customFunctionController',
|
|
114
|
-
plugin:
|
|
115
|
+
plugin: controller_3.customFunctionController,
|
|
115
116
|
options: {
|
|
116
117
|
prefix: `${constants_1.API_VERSION}/app/:appId/auth/providers/custom-function`
|
|
117
118
|
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
pluginName: 'anonUserController',
|
|
122
|
+
plugin: controller_2.anonUserController,
|
|
123
|
+
options: {
|
|
124
|
+
prefix: `${constants_1.API_VERSION}/app/:appId/auth/providers/anon-user`
|
|
125
|
+
}
|
|
118
126
|
}
|
|
119
127
|
];
|
|
120
128
|
});
|
|
@@ -52,5 +52,6 @@ const evaluateComplexExpression = (condition, params, user) => __awaiter(void 0,
|
|
|
52
52
|
functionsList,
|
|
53
53
|
services: services_1.services
|
|
54
54
|
});
|
|
55
|
-
|
|
55
|
+
const isTruthy = Boolean(response);
|
|
56
|
+
return key === '%%true' ? isTruthy : !isTruthy;
|
|
56
57
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/utils/rules-matcher/utils.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAe,MAAM,aAAa,CAAA;AAIvE;;GAEG;AACH,QAAA,MAAM,iBAAiB,EAAE,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/utils/rules-matcher/utils.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAe,MAAM,aAAa,CAAA;AAIvE;;GAEG;AACH,QAAA,MAAM,iBAAiB,EAAE,iBAsNxB,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,SAsDvB,CAAA;AAID,eAAe,iBAAiB,CAAA"}
|
|
@@ -128,6 +128,9 @@ const rulesMatcherUtils = {
|
|
|
128
128
|
},
|
|
129
129
|
forceArray: (a) => (Array.isArray(a) ? a : [a]),
|
|
130
130
|
getPath: (path, prefix) => {
|
|
131
|
+
if (typeof path !== 'string' || path.length === 0) {
|
|
132
|
+
return '';
|
|
133
|
+
}
|
|
131
134
|
if (path.indexOf('^') === 0) {
|
|
132
135
|
return (0, trimStart_1.default)(path, '^');
|
|
133
136
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowerforce/flowerbase",
|
|
3
|
-
"version": "1.2.1-beta.
|
|
3
|
+
"version": "1.2.1-beta.21",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
"@fastify/mongodb": "^9.0.1",
|
|
26
26
|
"@fastify/swagger": "^9.5.1",
|
|
27
27
|
"@fastify/swagger-ui": "^5.2.3",
|
|
28
|
-
"@sendgrid/mail": "^8.1.4",
|
|
29
28
|
"aws-sdk": "^2.1692.0",
|
|
30
29
|
"bson": "^6.8.0",
|
|
31
30
|
"dotenv": "^16.4.7",
|
package/src/auth/controller.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ObjectId } from 'bson'
|
|
2
2
|
import { FastifyInstance } from 'fastify'
|
|
3
|
-
import { AUTH_CONFIG, DB_NAME } from '../constants'
|
|
3
|
+
import { AUTH_CONFIG, DB_NAME, DEFAULT_CONFIG } from '../constants'
|
|
4
|
+
import { hashToken } from '../utils/crypto'
|
|
4
5
|
import { SessionCreatedDto } from './dtos'
|
|
5
6
|
import { AUTH_ENDPOINTS, AUTH_ERRORS } from './utils'
|
|
6
7
|
|
|
@@ -12,9 +13,19 @@ const HANDLER_TYPE = 'preHandler'
|
|
|
12
13
|
* @param {FastifyInstance} app - The Fastify instance.
|
|
13
14
|
*/
|
|
14
15
|
export async function authController(app: FastifyInstance) {
|
|
15
|
-
const { authCollection, userCollection } = AUTH_CONFIG
|
|
16
|
+
const { authCollection, userCollection, refreshTokensCollection } = AUTH_CONFIG
|
|
16
17
|
|
|
17
18
|
const db = app.mongo.client.db(DB_NAME)
|
|
19
|
+
const refreshTokenTtlMs = DEFAULT_CONFIG.REFRESH_TOKEN_TTL_DAYS * 24 * 60 * 60 * 1000
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await db.collection(refreshTokensCollection).createIndex(
|
|
23
|
+
{ expiresAt: 1 },
|
|
24
|
+
{ expireAfterSeconds: 0 }
|
|
25
|
+
)
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('Failed to ensure refresh token TTL index', error)
|
|
28
|
+
}
|
|
18
29
|
|
|
19
30
|
app.addHook(HANDLER_TYPE, app.jwtAuthentication)
|
|
20
31
|
|
|
@@ -59,6 +70,21 @@ export async function authController(app: FastifyInstance) {
|
|
|
59
70
|
throw new Error(AUTH_ERRORS.INVALID_TOKEN)
|
|
60
71
|
}
|
|
61
72
|
|
|
73
|
+
const authHeader = req.headers.authorization
|
|
74
|
+
if (!authHeader?.startsWith('Bearer ')) {
|
|
75
|
+
throw new Error(AUTH_ERRORS.INVALID_TOKEN)
|
|
76
|
+
}
|
|
77
|
+
const refreshToken = authHeader.slice('Bearer '.length).trim()
|
|
78
|
+
const refreshTokenHash = hashToken(refreshToken)
|
|
79
|
+
const storedToken = await db.collection(refreshTokensCollection).findOne({
|
|
80
|
+
tokenHash: refreshTokenHash,
|
|
81
|
+
revokedAt: null,
|
|
82
|
+
expiresAt: { $gt: new Date() }
|
|
83
|
+
})
|
|
84
|
+
if (!storedToken) {
|
|
85
|
+
throw new Error(AUTH_ERRORS.INVALID_TOKEN)
|
|
86
|
+
}
|
|
87
|
+
|
|
62
88
|
const auth_user = await db
|
|
63
89
|
?.collection(authCollection)
|
|
64
90
|
.findOne({ _id: new this.mongo.ObjectId(req.user.sub) })
|
|
@@ -75,7 +101,10 @@ export async function authController(app: FastifyInstance) {
|
|
|
75
101
|
return {
|
|
76
102
|
access_token: this.createAccessToken({
|
|
77
103
|
...auth_user,
|
|
78
|
-
user_data:
|
|
104
|
+
user_data: {
|
|
105
|
+
...user,
|
|
106
|
+
id: req.user.sub
|
|
107
|
+
}
|
|
79
108
|
})
|
|
80
109
|
}
|
|
81
110
|
}
|
|
@@ -85,8 +114,45 @@ export async function authController(app: FastifyInstance) {
|
|
|
85
114
|
*/
|
|
86
115
|
app.delete(
|
|
87
116
|
AUTH_ENDPOINTS.SESSION,
|
|
88
|
-
async function () {
|
|
89
|
-
|
|
117
|
+
async function (req, res) {
|
|
118
|
+
const authHeader = req.headers.authorization
|
|
119
|
+
if (!authHeader?.startsWith('Bearer ')) {
|
|
120
|
+
res.status(204)
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
const refreshToken = authHeader.slice('Bearer '.length).trim()
|
|
124
|
+
const refreshTokenHash = hashToken(refreshToken)
|
|
125
|
+
const now = new Date()
|
|
126
|
+
const expiresAt = new Date(Date.now() + refreshTokenTtlMs)
|
|
127
|
+
const updateResult = await db.collection(refreshTokensCollection).findOneAndUpdate(
|
|
128
|
+
{ tokenHash: refreshTokenHash },
|
|
129
|
+
{
|
|
130
|
+
$set: {
|
|
131
|
+
revokedAt: now,
|
|
132
|
+
expiresAt
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
{ returnDocument: 'after' }
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
const fromToken = req.user?.sub
|
|
139
|
+
let userId = updateResult?.value?.userId
|
|
140
|
+
if (!userId && fromToken) {
|
|
141
|
+
try {
|
|
142
|
+
userId = new ObjectId(fromToken)
|
|
143
|
+
} catch {
|
|
144
|
+
userId = fromToken
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (userId && authCollection) {
|
|
149
|
+
await db.collection(authCollection).updateOne(
|
|
150
|
+
{ _id: userId },
|
|
151
|
+
{ $set: { lastLogoutAt: now } }
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { status: 'ok' }
|
|
90
156
|
}
|
|
91
157
|
)
|
|
92
158
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
jest.mock('node:diagnostics_channel', () => {
|
|
2
|
+
const createChannel = () => ({
|
|
3
|
+
publish: jest.fn(),
|
|
4
|
+
subscribe: jest.fn()
|
|
5
|
+
})
|
|
6
|
+
return {
|
|
7
|
+
channel: jest.fn(createChannel),
|
|
8
|
+
tracingChannel: () => ({
|
|
9
|
+
asyncStart: createChannel(),
|
|
10
|
+
asyncEnd: createChannel(),
|
|
11
|
+
error: createChannel()
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
import fastify, { FastifyInstance, FastifyReply } from 'fastify'
|
|
17
|
+
import jwtAuthPlugin from './jwt'
|
|
18
|
+
import { ObjectId } from 'bson'
|
|
19
|
+
|
|
20
|
+
const SECRET = 'test-secret'
|
|
21
|
+
|
|
22
|
+
const createAccessRequest = (payload: { typ: 'access'; sub: string; iat: number }) => {
|
|
23
|
+
const request: Record<string, unknown> = {}
|
|
24
|
+
request.jwtVerify = jest.fn(async () => {
|
|
25
|
+
request.user = payload
|
|
26
|
+
})
|
|
27
|
+
return request
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('jwtAuthentication', () => {
|
|
31
|
+
let app: FastifyInstance
|
|
32
|
+
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
app = fastify()
|
|
35
|
+
await app.register(jwtAuthPlugin, { secret: SECRET })
|
|
36
|
+
await app.ready()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
afterEach(async () => {
|
|
40
|
+
await app.close()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const setupMongo = (userPayload: { _id: ObjectId; lastLogoutAt?: Date }) => {
|
|
44
|
+
const findOneMock = jest.fn().mockResolvedValue(userPayload)
|
|
45
|
+
const collectionMock = { findOne: findOneMock }
|
|
46
|
+
const dbMock = { collection: jest.fn().mockReturnValue(collectionMock) }
|
|
47
|
+
const mongoMock = { client: { db: jest.fn().mockReturnValue(dbMock) } }
|
|
48
|
+
;(app as any).mongo = mongoMock
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const createReply = () => {
|
|
52
|
+
return {
|
|
53
|
+
code: jest.fn().mockReturnThis(),
|
|
54
|
+
send: jest.fn()
|
|
55
|
+
} as unknown as FastifyReply
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
it('allows access tokens issued after the last logout', async () => {
|
|
59
|
+
const userId = new ObjectId()
|
|
60
|
+
const nowSeconds = Math.floor(Date.now() / 1000)
|
|
61
|
+
setupMongo({ _id: userId, lastLogoutAt: new Date((nowSeconds - 30) * 1000) })
|
|
62
|
+
|
|
63
|
+
const request = createAccessRequest({
|
|
64
|
+
typ: 'access',
|
|
65
|
+
sub: userId.toHexString(),
|
|
66
|
+
iat: nowSeconds
|
|
67
|
+
})
|
|
68
|
+
const reply = createReply()
|
|
69
|
+
|
|
70
|
+
await app.jwtAuthentication(request as any, reply)
|
|
71
|
+
|
|
72
|
+
expect(reply.code).not.toHaveBeenCalled()
|
|
73
|
+
expect(reply.send).not.toHaveBeenCalled()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('rejects access tokens issued before the last logout', async () => {
|
|
77
|
+
const userId = new ObjectId()
|
|
78
|
+
const nowSeconds = Math.floor(Date.now() / 1000)
|
|
79
|
+
setupMongo({ _id: userId, lastLogoutAt: new Date((nowSeconds + 30) * 1000) })
|
|
80
|
+
|
|
81
|
+
const request = createAccessRequest({
|
|
82
|
+
typ: 'access',
|
|
83
|
+
sub: userId.toHexString(),
|
|
84
|
+
iat: nowSeconds
|
|
85
|
+
})
|
|
86
|
+
const reply = createReply()
|
|
87
|
+
|
|
88
|
+
await app.jwtAuthentication(request as any, reply)
|
|
89
|
+
|
|
90
|
+
expect(reply.code).toHaveBeenCalledWith(401)
|
|
91
|
+
expect(reply.send).toHaveBeenCalledWith({ message: 'Unauthorized' })
|
|
92
|
+
})
|
|
93
|
+
})
|