@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.
Files changed (103) hide show
  1. package/README.md +37 -6
  2. package/dist/auth/controller.d.ts.map +1 -1
  3. package/dist/auth/controller.js +55 -4
  4. package/dist/auth/plugins/jwt.d.ts.map +1 -1
  5. package/dist/auth/plugins/jwt.js +52 -6
  6. package/dist/auth/providers/anon-user/controller.d.ts +8 -0
  7. package/dist/auth/providers/anon-user/controller.d.ts.map +1 -0
  8. package/dist/auth/providers/anon-user/controller.js +90 -0
  9. package/dist/auth/providers/anon-user/dtos.d.ts +10 -0
  10. package/dist/auth/providers/anon-user/dtos.d.ts.map +1 -0
  11. package/dist/auth/providers/anon-user/dtos.js +2 -0
  12. package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
  13. package/dist/auth/providers/custom-function/controller.js +35 -25
  14. package/dist/auth/providers/custom-function/dtos.d.ts +4 -1
  15. package/dist/auth/providers/custom-function/dtos.d.ts.map +1 -1
  16. package/dist/auth/providers/local-userpass/controller.d.ts.map +1 -1
  17. package/dist/auth/providers/local-userpass/controller.js +159 -73
  18. package/dist/auth/providers/local-userpass/dtos.d.ts +17 -2
  19. package/dist/auth/providers/local-userpass/dtos.d.ts.map +1 -1
  20. package/dist/auth/utils.d.ts +76 -14
  21. package/dist/auth/utils.d.ts.map +1 -1
  22. package/dist/auth/utils.js +55 -61
  23. package/dist/constants.d.ts +12 -0
  24. package/dist/constants.d.ts.map +1 -1
  25. package/dist/constants.js +16 -4
  26. package/dist/features/functions/controller.d.ts.map +1 -1
  27. package/dist/features/functions/controller.js +31 -12
  28. package/dist/features/functions/dtos.d.ts +3 -0
  29. package/dist/features/functions/dtos.d.ts.map +1 -1
  30. package/dist/features/functions/interface.d.ts +3 -0
  31. package/dist/features/functions/interface.d.ts.map +1 -1
  32. package/dist/features/functions/utils.d.ts +3 -2
  33. package/dist/features/functions/utils.d.ts.map +1 -1
  34. package/dist/features/functions/utils.js +19 -7
  35. package/dist/features/triggers/index.d.ts.map +1 -1
  36. package/dist/features/triggers/index.js +49 -7
  37. package/dist/features/triggers/interface.d.ts +1 -0
  38. package/dist/features/triggers/interface.d.ts.map +1 -1
  39. package/dist/features/triggers/utils.d.ts.map +1 -1
  40. package/dist/features/triggers/utils.js +67 -26
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +48 -13
  43. package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
  44. package/dist/services/mongodb-atlas/index.js +72 -2
  45. package/dist/services/mongodb-atlas/model.d.ts +3 -2
  46. package/dist/services/mongodb-atlas/model.d.ts.map +1 -1
  47. package/dist/services/mongodb-atlas/utils.d.ts.map +1 -1
  48. package/dist/services/mongodb-atlas/utils.js +3 -1
  49. package/dist/shared/handleUserRegistration.d.ts.map +1 -1
  50. package/dist/shared/handleUserRegistration.js +66 -1
  51. package/dist/shared/models/handleUserRegistration.model.d.ts +2 -1
  52. package/dist/shared/models/handleUserRegistration.model.d.ts.map +1 -1
  53. package/dist/shared/models/handleUserRegistration.model.js +1 -0
  54. package/dist/utils/context/helpers.d.ts +6 -6
  55. package/dist/utils/context/helpers.d.ts.map +1 -1
  56. package/dist/utils/context/index.d.ts +1 -1
  57. package/dist/utils/context/index.d.ts.map +1 -1
  58. package/dist/utils/context/index.js +176 -9
  59. package/dist/utils/context/interface.d.ts +1 -1
  60. package/dist/utils/context/interface.d.ts.map +1 -1
  61. package/dist/utils/crypto/index.d.ts +1 -0
  62. package/dist/utils/crypto/index.d.ts.map +1 -1
  63. package/dist/utils/crypto/index.js +6 -2
  64. package/dist/utils/initializer/exposeRoutes.js +1 -1
  65. package/dist/utils/initializer/registerPlugins.d.ts.map +1 -1
  66. package/dist/utils/initializer/registerPlugins.js +12 -4
  67. package/dist/utils/roles/helpers.js +2 -1
  68. package/dist/utils/rules-matcher/utils.d.ts.map +1 -1
  69. package/dist/utils/rules-matcher/utils.js +3 -0
  70. package/package.json +1 -2
  71. package/src/auth/controller.ts +71 -5
  72. package/src/auth/plugins/jwt.test.ts +93 -0
  73. package/src/auth/plugins/jwt.ts +67 -8
  74. package/src/auth/providers/anon-user/controller.ts +91 -0
  75. package/src/auth/providers/anon-user/dtos.ts +10 -0
  76. package/src/auth/providers/custom-function/controller.ts +40 -31
  77. package/src/auth/providers/custom-function/dtos.ts +5 -1
  78. package/src/auth/providers/local-userpass/controller.ts +211 -101
  79. package/src/auth/providers/local-userpass/dtos.ts +20 -2
  80. package/src/auth/utils.ts +66 -83
  81. package/src/constants.ts +14 -2
  82. package/src/features/functions/controller.ts +42 -12
  83. package/src/features/functions/dtos.ts +3 -0
  84. package/src/features/functions/interface.ts +3 -0
  85. package/src/features/functions/utils.ts +29 -8
  86. package/src/features/triggers/index.ts +44 -1
  87. package/src/features/triggers/interface.ts +1 -0
  88. package/src/features/triggers/utils.ts +89 -37
  89. package/src/index.ts +49 -13
  90. package/src/services/mongodb-atlas/__tests__/findOneAndUpdate.test.ts +95 -0
  91. package/src/services/mongodb-atlas/index.ts +665 -567
  92. package/src/services/mongodb-atlas/model.ts +16 -3
  93. package/src/services/mongodb-atlas/utils.ts +3 -0
  94. package/src/shared/handleUserRegistration.ts +83 -2
  95. package/src/shared/models/handleUserRegistration.model.ts +2 -1
  96. package/src/utils/__tests__/registerPlugins.test.ts +5 -1
  97. package/src/utils/context/index.ts +238 -18
  98. package/src/utils/context/interface.ts +1 -1
  99. package/src/utils/crypto/index.ts +5 -1
  100. package/src/utils/initializer/exposeRoutes.ts +1 -1
  101. package/src/utils/initializer/registerPlugins.ts +8 -0
  102. package/src/utils/roles/helpers.ts +3 -2
  103. 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
- vm_1.default.runInContext(functionToRun.code, vm_1.default.createContext(Object.assign(Object.assign({}, contextData), { require: customRequire, exports,
54
- module, __filename: __filename, __dirname: __dirname })));
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 (e) {
57
- console.log(e);
216
+ catch (error) {
217
+ console.error(error);
218
+ throw error;
58
219
  }
59
220
  if (deserializeArgs) {
60
- return yield module.exports(...bson_1.EJSON.deserialize(args));
221
+ return yield sandboxModule.exports(...bson_1.EJSON.deserialize(args));
61
222
  }
62
- return yield module.exports(...args);
223
+ return yield sandboxModule.exports(...args);
63
224
  });
64
- const res = yield functionsQueue.add(run, enqueue);
65
- return res;
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<void>;
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,IAAI,CAAC,CAAA;CAClE"}
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 = 32) => {
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;AAMnC,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"}
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/custom-function/controller");
22
- const controller_3 = require("../../auth/providers/local-userpass/controller");
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: controller_3.localUserPassController,
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: controller_2.customFunctionController,
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
- return key === '%%true' ? response : !response;
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,iBAmNxB,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,SAsDvB,CAAA;AAID,eAAe,iBAAiB,CAAA"}
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.2",
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",
@@ -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: user
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
- return { status: "ok" }
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
+ })