@flowerforce/flowerbase 1.6.1 → 1.6.2-beta.1

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 CHANGED
@@ -92,8 +92,8 @@ Ensure the following environment variables are set in your .env file or deployme
92
92
  | ---------------------- | --------------------------------------------------------------------------- | -------------------------------------------------- |
93
93
  | `PROJECT_ID` | A unique ID to identify your project. This value can be freely invented — it's preserved mainly for compatibility with the old Realm-style project structure. | `my-flowerbase-app` |
94
94
  | `PORT` | The port on which the server will run. | `3000` |
95
- | `DB_CONNECTION_STRING` | MongoDB connection URI, including username, password, and database name. | `mongodb+srv://user:pass@cluster.mongodb.net/mydb` |
96
- | `APP_SECRET` | Secret used to sign and verify JWT tokens (choose a strong secret). | `supersecretkey123!` |
95
+ | `MONGODB_URL` | MongoDB connection URI, including username, password, and database name. | `mongodb+srv://user:pass@cluster.mongodb.net/mydb` |
96
+ | `JWT_SECRET` | Secret used to sign and verify JWT tokens (choose a strong secret). | `supersecretkey123!` |
97
97
  | `HOST` | The host address the server binds to (usually `0.0.0.0` for public access). | `0.0.0.0` |
98
98
  | `HTTPS_SCHEMA` | The schema for your server requests (usually `https` or `http`). | `http` |
99
99
  | `RESET_PASSWORD_TTL_SECONDS` | Time-to-live for password reset tokens (in seconds). | `3600` |
@@ -101,6 +101,7 @@ Ensure the following environment variables are set in your .env file or deployme
101
101
  | `AUTH_LOGIN_MAX_ATTEMPTS` | Max login attempts per window. | `10` |
102
102
  | `AUTH_RESET_MAX_ATTEMPTS` | Max reset requests per window. | `5` |
103
103
  | `REFRESH_TOKEN_TTL_DAYS` | Refresh token time-to-live (in days). | `60` |
104
+ | `SWAGGER_ENABLED` | Enable Swagger UI and spec routes (disabled by default). | `true` |
104
105
  | `SWAGGER_UI_USER` | Basic Auth username for Swagger UI (optional). | `admin` |
105
106
  | `SWAGGER_UI_PASSWORD` | Basic Auth password for Swagger UI (optional). | `change-me` |
106
107
 
@@ -109,8 +110,8 @@ Example:
109
110
  ```env
110
111
  PROJECT_ID=your-project-id
111
112
  PORT=3000
112
- DB_CONNECTION_STRING=mongodb+srv://username:password@cluster.mongodb.net/dbname
113
- APP_SECRET=your-jwt-secret
113
+ MONGODB_URL=mongodb+srv://username:password@cluster.mongodb.net/dbname
114
+ JWT_SECRET=your-jwt-secret
114
115
  HOST=0.0.0.0
115
116
  HTTPS_SCHEMA=http
116
117
  RESET_PASSWORD_TTL_SECONDS=3600
@@ -118,6 +119,7 @@ AUTH_RATE_LIMIT_WINDOW_MS=900000
118
119
  AUTH_LOGIN_MAX_ATTEMPTS=10
119
120
  AUTH_RESET_MAX_ATTEMPTS=5
120
121
  REFRESH_TOKEN_TTL_DAYS=60
122
+ SWAGGER_ENABLED=true
121
123
  SWAGGER_UI_USER=admin
122
124
  SWAGGER_UI_PASSWORD=change-me
123
125
  ```
@@ -134,8 +136,8 @@ import { initialize } from '@flowerforce/flowerbase';
134
136
 
135
137
  const projectId = process.env.PROJECT_ID ?? "my-project-id"
136
138
  const port = process.env.PORT ? Number(process.env.PORT) : undefined
137
- const mongodbUrl = process.env.DB_CONNECTION_STRING
138
- const jwtSecret = process.env.APP_SECRET
139
+ const mongodbUrl = process.env.MONGODB_URL
140
+ const jwtSecret = process.env.JWT_SECRET
139
141
  const host = process.env.HOST
140
142
 
141
143
  initialize({
@@ -401,8 +403,8 @@ import { initialize } from '@flowerforce/flowerbase';
401
403
 
402
404
  const projectId = process.env.PROJECT_ID ?? "my-project-id"
403
405
  const port = process.env.PORT ? Number(process.env.PORT) : undefined
404
- const mongodbUrl = process.env.DB_CONNECTION_STRING
405
- const jwtSecret = process.env.APP_SECRET
406
+ const mongodbUrl = process.env.MONGODB_URL
407
+ const jwtSecret = process.env.JWT_SECRET
406
408
  const host = process.env.HOST
407
409
 
408
410
  initialize({
@@ -422,8 +424,8 @@ Ensure the following environment variables are set in your .env file or deployme
422
424
  | ---------------------- | --------------------------------------------------------------------------- | -------------------------------------------------- |
423
425
  | `PROJECT_ID` | A unique ID to identify your project. This value can be freely invented — it's preserved mainly for compatibility with the old Realm-style project structure. | `my-flowerbase-app` |
424
426
  | `PORT` | The port on which the server will run. | `3000` |
425
- | `DB_CONNECTION_STRING` | MongoDB connection URI, including username, password, and database name. | `mongodb+srv://user:pass@cluster.mongodb.net/mydb` |
426
- | `APP_SECRET` | Secret used to sign and verify JWT tokens (choose a strong secret). | `supersecretkey123!` |
427
+ | `MONGODB_URL` | MongoDB connection URI, including username, password, and database name. | `mongodb+srv://user:pass@cluster.mongodb.net/mydb` |
428
+ | `JWT_SECRET` | Secret used to sign and verify JWT tokens (choose a strong secret). | `supersecretkey123!` |
427
429
  | `HOST` | The host address the server binds to (usually `0.0.0.0` for public access). | `0.0.0.0` |
428
430
  | `HTTPS_SCHEMA` | The schema for your server requests (usually `https` or `http`). | `http` |
429
431
  | `RESET_PASSWORD_TTL_SECONDS` | Time-to-live for password reset tokens (in seconds). | `3600` |
@@ -431,6 +433,7 @@ Ensure the following environment variables are set in your .env file or deployme
431
433
  | `AUTH_LOGIN_MAX_ATTEMPTS` | Max login attempts per window. | `10` |
432
434
  | `AUTH_RESET_MAX_ATTEMPTS` | Max reset requests per window. | `5` |
433
435
  | `REFRESH_TOKEN_TTL_DAYS` | Refresh token time-to-live (in days). | `60` |
436
+ | `SWAGGER_ENABLED` | Enable Swagger UI and spec routes (disabled by default). | `true` |
434
437
  | `SWAGGER_UI_USER` | Basic Auth username for Swagger UI (optional). | `admin` |
435
438
  | `SWAGGER_UI_PASSWORD` | Basic Auth password for Swagger UI (optional). | `change-me` |
436
439
 
@@ -439,8 +442,8 @@ Example:
439
442
  ```env
440
443
  PROJECT_ID=your-project-id
441
444
  PORT=3000
442
- DB_CONNECTION_STRING=mongodb+srv://username:password@cluster.mongodb.net/dbname
443
- APP_SECRET=your-jwt-secret
445
+ MONGODB_URL=mongodb+srv://username:password@cluster.mongodb.net/dbname
446
+ JWT_SECRET=your-jwt-secret
444
447
  HOST=0.0.0.0
445
448
  HTTPS_SCHEMA=http
446
449
  RESET_PASSWORD_TTL_SECONDS=3600
@@ -448,6 +451,7 @@ AUTH_RATE_LIMIT_WINDOW_MS=900000
448
451
  AUTH_LOGIN_MAX_ATTEMPTS=10
449
452
  AUTH_RESET_MAX_ATTEMPTS=5
450
453
  REFRESH_TOKEN_TTL_DAYS=60
454
+ SWAGGER_ENABLED=true
451
455
  SWAGGER_UI_USER=admin
452
456
  SWAGGER_UI_PASSWORD=change-me
453
457
  ```
@@ -23,7 +23,7 @@ const utils_1 = require("../../utils");
23
23
  function anonUserController(app) {
24
24
  return __awaiter(this, void 0, void 0, function* () {
25
25
  const db = app.mongo.client.db(constants_1.DB_NAME);
26
- const { authCollection, refreshTokensCollection, providers } = constants_1.AUTH_CONFIG;
26
+ const { authCollection, refreshTokensCollection } = constants_1.AUTH_CONFIG;
27
27
  const refreshTokenTtlMs = constants_1.DEFAULT_CONFIG.REFRESH_TOKEN_TTL_DAYS * 24 * 60 * 60 * 1000;
28
28
  const anonUserTtlSeconds = constants_1.DEFAULT_CONFIG.ANON_USER_TTL_SECONDS;
29
29
  try {
@@ -37,8 +37,9 @@ function anonUserController(app) {
37
37
  }
38
38
  app.post(utils_1.AUTH_ENDPOINTS.LOGIN, function () {
39
39
  return __awaiter(this, void 0, void 0, function* () {
40
- const anonProvider = providers === null || providers === void 0 ? void 0 : providers['anon-user'];
41
- if (anonProvider === null || anonProvider === void 0 ? void 0 : anonProvider.disabled) {
40
+ var _a;
41
+ const anonProvider = (_a = constants_1.AUTH_CONFIG.authProviders) === null || _a === void 0 ? void 0 : _a['anon-user'];
42
+ if (!anonProvider || anonProvider.disabled) {
42
43
  throw new Error('Anonymous authentication disabled');
43
44
  }
44
45
  const now = new Date();
@@ -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;AASzC;;;;GAIG;AACH,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,eAAe,iBAgGlE"}
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;AASzC;;;;GAIG;AACH,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,eAAe,iBAoGlE"}
@@ -39,8 +39,13 @@ function customFunctionController(app) {
39
39
  schema: schema_1.LOGIN_SCHEMA
40
40
  }, function (req, reply) {
41
41
  return __awaiter(this, void 0, void 0, function* () {
42
- const { providers } = constants_1.AUTH_CONFIG;
43
- const authFunctionName = providers["custom-function"].authFunctionName;
42
+ var _a, _b;
43
+ const customFunctionProvider = (_a = constants_1.AUTH_CONFIG.authProviders) === null || _a === void 0 ? void 0 : _a['custom-function'];
44
+ if (!customFunctionProvider || customFunctionProvider.disabled) {
45
+ throw new Error('Custom function authentication disabled');
46
+ }
47
+ const authFunctionName = (_b = customFunctionProvider
48
+ .config) === null || _b === void 0 ? void 0 : _b.authFunctionName;
44
49
  if (!authFunctionName || !functionsList[authFunctionName]) {
45
50
  throw new Error("Missing Auth Function");
46
51
  }
@@ -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;AAqCzC;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,eAAe,iBAqVjE"}
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;AAqCzC;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,eAAe,iBA8WjE"}
@@ -47,6 +47,7 @@ function localUserPassController(app) {
47
47
  const registerMaxAttempts = constants_1.DEFAULT_CONFIG.AUTH_REGISTER_MAX_ATTEMPTS;
48
48
  const resetMaxAttempts = constants_1.DEFAULT_CONFIG.AUTH_RESET_MAX_ATTEMPTS;
49
49
  const refreshTokenTtlMs = constants_1.DEFAULT_CONFIG.REFRESH_TOKEN_TTL_DAYS * 24 * 60 * 60 * 1000;
50
+ const resolveLocalUserpassProvider = () => { var _a; return (_a = constants_1.AUTH_CONFIG.authProviders) === null || _a === void 0 ? void 0 : _a['local-userpass']; };
50
51
  try {
51
52
  yield db.collection(resetPasswordCollection).createIndex({ createdAt: 1 }, { expireAfterSeconds: resetPasswordTtlSeconds });
52
53
  }
@@ -102,6 +103,10 @@ function localUserPassController(app) {
102
103
  app.post(utils_1.AUTH_ENDPOINTS.REGISTRATION, {
103
104
  schema: utils_1.REGISTRATION_SCHEMA
104
105
  }, (req, res) => __awaiter(this, void 0, void 0, function* () {
106
+ const localUserpassProvider = resolveLocalUserpassProvider();
107
+ if (!localUserpassProvider || localUserpassProvider.disabled) {
108
+ throw new Error('Local userpass authentication disabled');
109
+ }
105
110
  const key = `register:${req.ip}`;
106
111
  if (isRateLimited(key, registerMaxAttempts, rateLimitWindowMs)) {
107
112
  res.status(429).send({ message: 'Too many requests' });
@@ -141,6 +146,10 @@ function localUserPassController(app) {
141
146
  app.post(utils_1.AUTH_ENDPOINTS.CONFIRM, {
142
147
  schema: utils_1.CONFIRM_USER_SCHEMA
143
148
  }, (req, res) => __awaiter(this, void 0, void 0, function* () {
149
+ const localUserpassProvider = resolveLocalUserpassProvider();
150
+ if (!localUserpassProvider || localUserpassProvider.disabled) {
151
+ throw new Error('Local userpass authentication disabled');
152
+ }
144
153
  const key = `confirm:${req.ip}`;
145
154
  if (isRateLimited(key, resetMaxAttempts, rateLimitWindowMs)) {
146
155
  res.status(429).send({ message: 'Too many requests' });
@@ -174,6 +183,10 @@ function localUserPassController(app) {
174
183
  schema: utils_1.LOGIN_SCHEMA
175
184
  }, function (req, res) {
176
185
  return __awaiter(this, void 0, void 0, function* () {
186
+ const localUserpassProvider = resolveLocalUserpassProvider();
187
+ if (!localUserpassProvider || localUserpassProvider.disabled) {
188
+ throw new Error('Local userpass authentication disabled');
189
+ }
177
190
  const key = `login:${req.ip}`;
178
191
  if (isRateLimited(key, loginMaxAttempts, rateLimitWindowMs)) {
179
192
  res.status(429).send({ message: 'Too many requests' });
@@ -227,6 +240,10 @@ function localUserPassController(app) {
227
240
  schema: utils_1.RESET_SEND_SCHEMA
228
241
  }, function (req, res) {
229
242
  return __awaiter(this, void 0, void 0, function* () {
243
+ const localUserpassProvider = resolveLocalUserpassProvider();
244
+ if (!localUserpassProvider || localUserpassProvider.disabled) {
245
+ throw new Error('Local userpass authentication disabled');
246
+ }
230
247
  const key = `reset:${req.ip}`;
231
248
  if (isRateLimited(key, resetMaxAttempts, rateLimitWindowMs)) {
232
249
  res.status(429);
@@ -243,6 +260,10 @@ function localUserPassController(app) {
243
260
  schema: utils_1.RESET_CALL_SCHEMA
244
261
  }, function (req, res) {
245
262
  return __awaiter(this, void 0, void 0, function* () {
263
+ const localUserpassProvider = resolveLocalUserpassProvider();
264
+ if (!localUserpassProvider || localUserpassProvider.disabled) {
265
+ throw new Error('Local userpass authentication disabled');
266
+ }
246
267
  const key = `reset:${req.ip}`;
247
268
  if (isRateLimited(key, resetMaxAttempts, rateLimitWindowMs)) {
248
269
  res.status(429);
@@ -266,6 +287,10 @@ function localUserPassController(app) {
266
287
  schema: utils_1.CONFIRM_RESET_SCHEMA
267
288
  }, function (req, res) {
268
289
  return __awaiter(this, void 0, void 0, function* () {
290
+ const localUserpassProvider = resolveLocalUserpassProvider();
291
+ if (!localUserpassProvider || localUserpassProvider.disabled) {
292
+ throw new Error('Local userpass authentication disabled');
293
+ }
269
294
  const key = `reset-confirm:${req.ip}`;
270
295
  if (isRateLimited(key, resetMaxAttempts, rateLimitWindowMs)) {
271
296
  res.status(429);
@@ -149,7 +149,7 @@ const loadAuthConfig = () => {
149
149
  'anon-user': {
150
150
  name: 'anon-user',
151
151
  type: 'anon-user',
152
- disabled: false
152
+ disabled: true
153
153
  }
154
154
  };
155
155
  }
@@ -14,6 +14,7 @@ export declare const DEFAULT_CONFIG: {
14
14
  AUTH_RESET_MAX_ATTEMPTS: number;
15
15
  REFRESH_TOKEN_TTL_DAYS: number;
16
16
  ANON_USER_TTL_SECONDS: number;
17
+ SWAGGER_ENABLED: boolean;
17
18
  SWAGGER_UI_USER: string;
18
19
  SWAGGER_UI_PASSWORD: string;
19
20
  CORS_OPTIONS: {
@@ -24,6 +25,10 @@ export declare const DEFAULT_CONFIG: {
24
25
  export declare const API_VERSION: string;
25
26
  export declare const HTTPS_SCHEMA: string;
26
27
  export declare const DB_NAME: string;
28
+ type AuthProviders = Record<string, {
29
+ disabled?: boolean;
30
+ config?: unknown;
31
+ }>;
27
32
  export declare const AUTH_CONFIG: {
28
33
  authCollection: string;
29
34
  userCollection: string;
@@ -31,6 +36,7 @@ export declare const AUTH_CONFIG: {
31
36
  refreshTokensCollection: string;
32
37
  resetPasswordConfig: import("./auth/utils").Config;
33
38
  localUserpassConfig: import("./auth/utils").Config;
39
+ authProviders: AuthProviders;
34
40
  user_id_field: string;
35
41
  on_user_creation_function_name: string;
36
42
  providers: {
@@ -44,4 +50,5 @@ export declare const S3_CONFIG: {
44
50
  ACCESS_KEY_ID: string | undefined;
45
51
  SECRET_ACCESS_KEY: string | undefined;
46
52
  };
53
+ export {};
47
54
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAA;AAUpC,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;iBAmBsB,eAAe,EAAE;;CAEjE,CAAA;AACD,eAAO,MAAM,WAAW,QAA8C,CAAA;AACtE,eAAO,MAAM,YAAY,QAA8B,CAAA;AACvD,eAAO,MAAM,OAAO,QAAgB,CAAA;AAGpC,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;CAavB,CAAA;AAID,eAAO,MAAM,SAAS;;;CAGrB,CAAA"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAA;AAepC,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;iBAoBsB,eAAe,EAAE;;CAEjE,CAAA;AACD,eAAO,MAAM,WAAW,QAA8C,CAAA;AACtE,eAAO,MAAM,YAAY,QAA8B,CAAA;AACvD,eAAO,MAAM,OAAO,QAAgB,CAAA;AAEpC,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAA;AAE7E,eAAO,MAAM,WAAW;;;;;;;mBAOqB,aAAa;;;;;;;;;CAOzD,CAAA;AAID,eAAO,MAAM,SAAS;;;CAGrB,CAAA"}
package/dist/constants.js CHANGED
@@ -14,6 +14,11 @@ var _a, _b, _c;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.S3_CONFIG = exports.AUTH_CONFIG = exports.DB_NAME = exports.HTTPS_SCHEMA = exports.API_VERSION = exports.DEFAULT_CONFIG = void 0;
16
16
  const utils_1 = require("./auth/utils");
17
+ const parseBoolean = (value) => {
18
+ if (!value)
19
+ return false;
20
+ return ['1', 'true', 'yes', 'on'].includes(value.toLowerCase());
21
+ };
17
22
  const { database_name, collection_name = 'users', user_id_field = 'id', on_user_creation_function_name } = (0, utils_1.loadCustomUserData)();
18
23
  const _d = (0, utils_1.loadAuthConfig)(), { auth_collection = 'auth_users' } = _d, configuration = __rest(_d, ["auth_collection"]);
19
24
  exports.DEFAULT_CONFIG = {
@@ -31,6 +36,7 @@ exports.DEFAULT_CONFIG = {
31
36
  AUTH_RESET_MAX_ATTEMPTS: Number(process.env.AUTH_RESET_MAX_ATTEMPTS) || 5,
32
37
  REFRESH_TOKEN_TTL_DAYS: Number(process.env.REFRESH_TOKEN_TTL_DAYS) || 60,
33
38
  ANON_USER_TTL_SECONDS: Number(process.env.ANON_USER_TTL_SECONDS) || 3 * 60 * 60,
39
+ SWAGGER_ENABLED: parseBoolean(process.env.SWAGGER_ENABLED),
34
40
  SWAGGER_UI_USER: process.env.SWAGGER_UI_USER || '',
35
41
  SWAGGER_UI_PASSWORD: process.env.SWAGGER_UI_PASSWORD || '',
36
42
  CORS_OPTIONS: {
@@ -49,6 +55,7 @@ exports.AUTH_CONFIG = {
49
55
  refreshTokensCollection: 'auth_refresh_tokens',
50
56
  resetPasswordConfig: (_a = configuration['local-userpass']) === null || _a === void 0 ? void 0 : _a.config,
51
57
  localUserpassConfig: (_b = configuration['local-userpass']) === null || _b === void 0 ? void 0 : _b.config,
58
+ authProviders: configuration,
52
59
  user_id_field,
53
60
  on_user_creation_function_name,
54
61
  providers: {
@@ -23,6 +23,7 @@ type Config = {
23
23
  match: Record<string, unknown>;
24
24
  operation_types: string[];
25
25
  operation_type?: 'CREATE' | 'DELETE' | 'LOGOUT';
26
+ providers?: string[];
26
27
  project: Record<string, unknown>;
27
28
  service_name: string;
28
29
  skip_catchup_events: boolean;
@@ -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;IAC/C,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;CACzB,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,QAAQ,CAAA;IAC/C,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;CACzB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/features/triggers/utils.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,aAAa,EAAW,QAAQ,EAAE,MAAM,aAAa,CAAA;AAqC9D;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GAAU,gBAAuB,KAAG,OAAO,CAAC,QAAQ,CAkB5E,CAAA;AAuaD,eAAO,MAAM,gBAAgB;0EAnZ1B,aAAa;yEAwVb,aAAa;+EAtSb,aAAa;CAqWf,CAAA"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/features/triggers/utils.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,aAAa,EAAW,QAAQ,EAAE,MAAM,aAAa,CAAA;AAqC9D;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GAAU,gBAAuB,KAAG,OAAO,CAAC,QAAQ,CAkB5E,CAAA;AAyeD,eAAO,MAAM,gBAAgB;0EArd1B,aAAa;yEA0Zb,aAAa;+EArUb,aAAa;CAoYf,CAAA"}
@@ -123,6 +123,38 @@ const mapOpInverse = {
123
123
  LOGOUT: ['update'],
124
124
  };
125
125
  const normalizeOperationTypes = (operationTypes = []) => operationTypes.map((op) => op.toLowerCase());
126
+ const normalizeProviders = (providers = []) => providers
127
+ .map((provider) => (typeof provider === 'string' ? provider.trim().toLowerCase() : ''))
128
+ .filter((provider) => provider.length);
129
+ const extractProvidersFromDocument = (document) => {
130
+ if (!document)
131
+ return [];
132
+ const providers = [];
133
+ const identities = document.identities;
134
+ if (Array.isArray(identities)) {
135
+ for (const identity of identities) {
136
+ if (!identity || typeof identity !== 'object')
137
+ continue;
138
+ const providerType = identity.provider_type;
139
+ if (typeof providerType === 'string') {
140
+ providers.push(providerType.toLowerCase());
141
+ }
142
+ }
143
+ }
144
+ const rootProviderType = document.provider_type;
145
+ if (typeof rootProviderType === 'string') {
146
+ providers.push(rootProviderType.toLowerCase());
147
+ }
148
+ return providers;
149
+ };
150
+ const matchesProviderFilter = (document, providerFilter) => {
151
+ if (!providerFilter.length)
152
+ return true;
153
+ const documentProviders = extractProvidersFromDocument(document);
154
+ if (!documentProviders.length)
155
+ return false;
156
+ return documentProviders.some((provider) => providerFilter.includes(provider));
157
+ };
126
158
  const resolveDocumentOptions = ({ requestFullDocument, requestFullDocumentBeforeChange, normalizedOperations }) => {
127
159
  const includesUpdateOrReplace = normalizedOperations.some((op) => op === 'update' || op === 'replace');
128
160
  const fullDocument = (() => {
@@ -134,9 +166,10 @@ const resolveDocumentOptions = ({ requestFullDocument, requestFullDocumentBefore
134
166
  return { fullDocument, fullDocumentBeforeChange };
135
167
  };
136
168
  const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, function* ({ config, triggerHandler, functionsList, services, app }) {
137
- var _b;
169
+ var _b, _c;
138
170
  const { database, isAutoTrigger, operation_types = [], operation_type } = config;
139
- const authCollection = (_b = constants_1.AUTH_CONFIG.authCollection) !== null && _b !== void 0 ? _b : 'auth_users';
171
+ const providerFilter = normalizeProviders((_b = config.providers) !== null && _b !== void 0 ? _b : []);
172
+ const authCollection = (_c = constants_1.AUTH_CONFIG.authCollection) !== null && _c !== void 0 ? _c : 'auth_users';
140
173
  const collection = app.mongo.client.db(database || constants_1.DB_NAME).collection(authCollection);
141
174
  const operationCandidates = operation_type ? mapOpInverse[operation_type] : operation_types;
142
175
  const normalizedOps = normalizeOperationTypes(operationCandidates);
@@ -216,6 +249,9 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
216
249
  _id: documentKey._id
217
250
  }));
218
251
  }
252
+ if (!matchesProviderFilter(logoutDocument, providerFilter)) {
253
+ return;
254
+ }
219
255
  const userData = buildUserData(logoutDocument);
220
256
  if (!userData) {
221
257
  return;
@@ -248,7 +284,11 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
248
284
  if (isAutoTrigger || operation_type !== 'DELETE') {
249
285
  return;
250
286
  }
251
- const userData = buildUserData(fullDocumentBeforeChange !== null && fullDocumentBeforeChange !== void 0 ? fullDocumentBeforeChange : confirmedDocument);
287
+ const deleteDocument = fullDocumentBeforeChange !== null && fullDocumentBeforeChange !== void 0 ? fullDocumentBeforeChange : confirmedDocument;
288
+ if (!matchesProviderFilter(deleteDocument, providerFilter)) {
289
+ return;
290
+ }
291
+ const userData = buildUserData(deleteDocument);
252
292
  if (!userData) {
253
293
  return;
254
294
  }
@@ -277,7 +317,16 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
277
317
  return;
278
318
  }
279
319
  if (isReplace) {
280
- const userData = buildUserData(confirmedDocument);
320
+ let replaceDocument = confirmedDocument;
321
+ if (!replaceDocument && providerFilter.length && (documentKey === null || documentKey === void 0 ? void 0 : documentKey._id)) {
322
+ replaceDocument = (yield collection.findOne({
323
+ _id: documentKey._id
324
+ }));
325
+ }
326
+ if (!matchesProviderFilter(replaceDocument, providerFilter)) {
327
+ return;
328
+ }
329
+ const userData = buildUserData(replaceDocument);
281
330
  if (!userData) {
282
331
  return;
283
332
  }
@@ -323,6 +372,16 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
323
372
  if (!confirmedCandidate) {
324
373
  return;
325
374
  }
375
+ let candidateDocument = confirmedDocument;
376
+ if (!candidateDocument && providerFilter.length && (documentKey === null || documentKey === void 0 ? void 0 : documentKey._id)) {
377
+ candidateDocument = (yield collection.findOne({
378
+ _id: documentKey._id
379
+ }));
380
+ confirmedDocument = candidateDocument !== null && candidateDocument !== void 0 ? candidateDocument : confirmedDocument;
381
+ }
382
+ if (!matchesProviderFilter(candidateDocument, providerFilter)) {
383
+ return;
384
+ }
326
385
  const updateResult = yield collection.findOneAndUpdate({
327
386
  _id: documentKey._id,
328
387
  status: 'confirmed',
@@ -339,6 +398,9 @@ const handleAuthenticationTrigger = (_a) => __awaiter(void 0, [_a], void 0, func
339
398
  return;
340
399
  }
341
400
  delete document.password;
401
+ if (!matchesProviderFilter(document, providerFilter)) {
402
+ return;
403
+ }
342
404
  const userData = buildUserData(document);
343
405
  if (!userData) {
344
406
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAA;AActB,cAAc,SAAS,CAAA;AAGvB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAA;AAE/D,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,eAAe,EAAE,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,EAC/B,SAAS,EACT,IAA0B,EAC1B,SAAqC,EACrC,IAA0B,EAC1B,UAAuC,EACvC,UAAwC,EACxC,QAAQ,EACT,EAAE,gBAAgB,iBA6GlB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAA;AActB,cAAc,SAAS,CAAA;AAGvB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAA;AAE/D,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,eAAe,EAAE,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,EAC/B,SAAS,EACT,IAA0B,EAC1B,SAAqC,EACrC,IAA0B,EAC1B,UAAuC,EACvC,UAAwC,EACxC,QAAQ,EACT,EAAE,gBAAgB,iBA+GlB"}
package/dist/index.js CHANGED
@@ -108,48 +108,50 @@ function initialize(_a) {
108
108
  services: services_1.services
109
109
  };
110
110
  Object.entries(stateConfig).forEach(([key, value]) => state_1.StateManager.setData(key, value));
111
- yield fastify.register(Promise.resolve().then(() => __importStar(require('@fastify/swagger'))));
112
- yield fastify.register(Promise.resolve().then(() => __importStar(require('@fastify/swagger-ui'))), {
113
- routePrefix: '/documentation',
114
- uiConfig: {
115
- docExpansion: 'full',
116
- deepLinking: false
117
- },
118
- uiHooks: {
119
- onRequest: function (request, reply, next) {
120
- const swaggerUser = constants_1.DEFAULT_CONFIG.SWAGGER_UI_USER;
121
- const swaggerPassword = constants_1.DEFAULT_CONFIG.SWAGGER_UI_PASSWORD;
122
- if (!swaggerUser && !swaggerPassword) {
111
+ if (constants_1.DEFAULT_CONFIG.SWAGGER_ENABLED) {
112
+ yield fastify.register(Promise.resolve().then(() => __importStar(require('@fastify/swagger'))));
113
+ yield fastify.register(Promise.resolve().then(() => __importStar(require('@fastify/swagger-ui'))), {
114
+ routePrefix: '/documentation',
115
+ uiConfig: {
116
+ docExpansion: 'full',
117
+ deepLinking: false
118
+ },
119
+ uiHooks: {
120
+ onRequest: function (request, reply, next) {
121
+ const swaggerUser = constants_1.DEFAULT_CONFIG.SWAGGER_UI_USER;
122
+ const swaggerPassword = constants_1.DEFAULT_CONFIG.SWAGGER_UI_PASSWORD;
123
+ if (!swaggerUser && !swaggerPassword) {
124
+ next();
125
+ return;
126
+ }
127
+ const authHeader = request.headers.authorization;
128
+ if (!authHeader || !authHeader.startsWith('Basic ')) {
129
+ reply
130
+ .code(401)
131
+ .header('WWW-Authenticate', 'Basic realm="Swagger UI"')
132
+ .send({ message: 'Unauthorized' });
133
+ return;
134
+ }
135
+ const encoded = authHeader.slice('Basic '.length);
136
+ const decoded = Buffer.from(encoded, 'base64').toString('utf8');
137
+ const [user, pass] = decoded.split(':');
138
+ if (user !== swaggerUser || pass !== swaggerPassword) {
139
+ reply
140
+ .code(401)
141
+ .header('WWW-Authenticate', 'Basic realm="Swagger UI"')
142
+ .send({ message: 'Unauthorized' });
143
+ return;
144
+ }
123
145
  next();
124
- return;
125
- }
126
- const authHeader = request.headers.authorization;
127
- if (!authHeader || !authHeader.startsWith('Basic ')) {
128
- reply
129
- .code(401)
130
- .header('WWW-Authenticate', 'Basic realm="Swagger UI"')
131
- .send({ message: 'Unauthorized' });
132
- return;
133
- }
134
- const encoded = authHeader.slice('Basic '.length);
135
- const decoded = Buffer.from(encoded, 'base64').toString('utf8');
136
- const [user, pass] = decoded.split(':');
137
- if (user !== swaggerUser || pass !== swaggerPassword) {
138
- reply
139
- .code(401)
140
- .header('WWW-Authenticate', 'Basic realm="Swagger UI"')
141
- .send({ message: 'Unauthorized' });
142
- return;
143
- }
144
- next();
146
+ },
147
+ preHandler: function (request, reply, next) { next(); }
145
148
  },
146
- preHandler: function (request, reply, next) { next(); }
147
- },
148
- staticCSP: true,
149
- transformStaticCSP: (header) => header,
150
- transformSpecification: (swaggerObject) => { return swaggerObject; },
151
- transformSpecificationClone: true
152
- });
149
+ staticCSP: true,
150
+ transformStaticCSP: (header) => header,
151
+ transformSpecification: (swaggerObject) => { return swaggerObject; },
152
+ transformSpecificationClone: true
153
+ });
154
+ }
153
155
  yield (0, registerPlugins_1.registerPlugins)({
154
156
  register: fastify.register,
155
157
  mongodbUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowerforce/flowerbase",
3
- "version": "1.6.1",
3
+ "version": "1.6.2-beta.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -13,7 +13,7 @@ import { LoginDto } from './dtos'
13
13
  */
14
14
  export async function anonUserController(app: FastifyInstance) {
15
15
  const db = app.mongo.client.db(DB_NAME)
16
- const { authCollection, refreshTokensCollection, providers } = AUTH_CONFIG
16
+ const { authCollection, refreshTokensCollection } = AUTH_CONFIG
17
17
  const refreshTokenTtlMs = DEFAULT_CONFIG.REFRESH_TOKEN_TTL_DAYS * 24 * 60 * 60 * 1000
18
18
  const anonUserTtlSeconds = DEFAULT_CONFIG.ANON_USER_TTL_SECONDS
19
19
 
@@ -32,8 +32,8 @@ export async function anonUserController(app: FastifyInstance) {
32
32
  app.post<LoginDto>(
33
33
  AUTH_ENDPOINTS.LOGIN,
34
34
  async function () {
35
- const anonProvider = providers?.['anon-user']
36
- if (anonProvider?.disabled) {
35
+ const anonProvider = AUTH_CONFIG.authProviders?.['anon-user']
36
+ if (!anonProvider || anonProvider.disabled) {
37
37
  throw new Error('Anonymous authentication disabled')
38
38
  }
39
39
 
@@ -33,8 +33,12 @@ export async function customFunctionController(app: FastifyInstance) {
33
33
  schema: LOGIN_SCHEMA
34
34
  },
35
35
  async function (req, reply) {
36
- const { providers } = AUTH_CONFIG
37
- const authFunctionName = providers["custom-function"].authFunctionName
36
+ const customFunctionProvider = AUTH_CONFIG.authProviders?.['custom-function']
37
+ if (!customFunctionProvider || customFunctionProvider.disabled) {
38
+ throw new Error('Custom function authentication disabled')
39
+ }
40
+ const authFunctionName = (customFunctionProvider as { config?: { authFunctionName?: string } })
41
+ .config?.authFunctionName
38
42
 
39
43
  if (!authFunctionName || !functionsList[authFunctionName]) {
40
44
  throw new Error("Missing Auth Function")
@@ -51,6 +51,7 @@ export async function localUserPassController(app: FastifyInstance) {
51
51
  const registerMaxAttempts = DEFAULT_CONFIG.AUTH_REGISTER_MAX_ATTEMPTS
52
52
  const resetMaxAttempts = DEFAULT_CONFIG.AUTH_RESET_MAX_ATTEMPTS
53
53
  const refreshTokenTtlMs = DEFAULT_CONFIG.REFRESH_TOKEN_TTL_DAYS * 24 * 60 * 60 * 1000
54
+ const resolveLocalUserpassProvider = () => AUTH_CONFIG.authProviders?.['local-userpass']
54
55
 
55
56
  try {
56
57
  await db.collection(resetPasswordCollection).createIndex(
@@ -132,6 +133,10 @@ export async function localUserPassController(app: FastifyInstance) {
132
133
  schema: REGISTRATION_SCHEMA
133
134
  },
134
135
  async (req, res) => {
136
+ const localUserpassProvider = resolveLocalUserpassProvider()
137
+ if (!localUserpassProvider || localUserpassProvider.disabled) {
138
+ throw new Error('Local userpass authentication disabled')
139
+ }
135
140
  const key = `register:${req.ip}`
136
141
  if (isRateLimited(key, registerMaxAttempts, rateLimitWindowMs)) {
137
142
  res.status(429).send({ message: 'Too many requests' })
@@ -178,6 +183,10 @@ export async function localUserPassController(app: FastifyInstance) {
178
183
  schema: CONFIRM_USER_SCHEMA
179
184
  },
180
185
  async (req, res) => {
186
+ const localUserpassProvider = resolveLocalUserpassProvider()
187
+ if (!localUserpassProvider || localUserpassProvider.disabled) {
188
+ throw new Error('Local userpass authentication disabled')
189
+ }
181
190
  const key = `confirm:${req.ip}`
182
191
  if (isRateLimited(key, resetMaxAttempts, rateLimitWindowMs)) {
183
192
  res.status(429).send({ message: 'Too many requests' })
@@ -222,6 +231,10 @@ export async function localUserPassController(app: FastifyInstance) {
222
231
  schema: LOGIN_SCHEMA
223
232
  },
224
233
  async function (req, res) {
234
+ const localUserpassProvider = resolveLocalUserpassProvider()
235
+ if (!localUserpassProvider || localUserpassProvider.disabled) {
236
+ throw new Error('Local userpass authentication disabled')
237
+ }
225
238
  const key = `login:${req.ip}`
226
239
  if (isRateLimited(key, loginMaxAttempts, rateLimitWindowMs)) {
227
240
  res.status(429).send({ message: 'Too many requests' })
@@ -295,6 +308,10 @@ export async function localUserPassController(app: FastifyInstance) {
295
308
  schema: RESET_SEND_SCHEMA
296
309
  },
297
310
  async function (req, res) {
311
+ const localUserpassProvider = resolveLocalUserpassProvider()
312
+ if (!localUserpassProvider || localUserpassProvider.disabled) {
313
+ throw new Error('Local userpass authentication disabled')
314
+ }
298
315
  const key = `reset:${req.ip}`
299
316
  if (isRateLimited(key, resetMaxAttempts, rateLimitWindowMs)) {
300
317
  res.status(429)
@@ -314,6 +331,10 @@ export async function localUserPassController(app: FastifyInstance) {
314
331
  schema: RESET_CALL_SCHEMA
315
332
  },
316
333
  async function (req, res) {
334
+ const localUserpassProvider = resolveLocalUserpassProvider()
335
+ if (!localUserpassProvider || localUserpassProvider.disabled) {
336
+ throw new Error('Local userpass authentication disabled')
337
+ }
317
338
  const key = `reset:${req.ip}`
318
339
  if (isRateLimited(key, resetMaxAttempts, rateLimitWindowMs)) {
319
340
  res.status(429)
@@ -344,6 +365,10 @@ export async function localUserPassController(app: FastifyInstance) {
344
365
  schema: CONFIRM_RESET_SCHEMA
345
366
  },
346
367
  async function (req, res) {
368
+ const localUserpassProvider = resolveLocalUserpassProvider()
369
+ if (!localUserpassProvider || localUserpassProvider.disabled) {
370
+ throw new Error('Local userpass authentication disabled')
371
+ }
347
372
  const key = `reset-confirm:${req.ip}`
348
373
  if (isRateLimited(key, resetMaxAttempts, rateLimitWindowMs)) {
349
374
  res.status(429)
package/src/auth/utils.ts CHANGED
@@ -208,7 +208,7 @@ export const loadAuthConfig = (): AuthConfig => {
208
208
  'anon-user': {
209
209
  name: 'anon-user',
210
210
  type: 'anon-user',
211
- disabled: false
211
+ disabled: true
212
212
  }
213
213
  }
214
214
  }
package/src/constants.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  import { loadAuthConfig, loadCustomUserData } from './auth/utils'
2
2
  import { ALLOWED_METHODS } from './'
3
3
 
4
+ const parseBoolean = (value?: string) => {
5
+ if (!value) return false
6
+ return ['1', 'true', 'yes', 'on'].includes(value.toLowerCase())
7
+ }
8
+
4
9
  const {
5
10
  database_name,
6
11
  collection_name = 'users',
@@ -24,6 +29,7 @@ export const DEFAULT_CONFIG = {
24
29
  AUTH_RESET_MAX_ATTEMPTS: Number(process.env.AUTH_RESET_MAX_ATTEMPTS) || 5,
25
30
  REFRESH_TOKEN_TTL_DAYS: Number(process.env.REFRESH_TOKEN_TTL_DAYS) || 60,
26
31
  ANON_USER_TTL_SECONDS: Number(process.env.ANON_USER_TTL_SECONDS) || 3 * 60 * 60,
32
+ SWAGGER_ENABLED: parseBoolean(process.env.SWAGGER_ENABLED),
27
33
  SWAGGER_UI_USER: process.env.SWAGGER_UI_USER || '',
28
34
  SWAGGER_UI_PASSWORD: process.env.SWAGGER_UI_PASSWORD || '',
29
35
  CORS_OPTIONS: {
@@ -35,6 +41,7 @@ export const API_VERSION = `/api/client/${DEFAULT_CONFIG.API_VERSION}`
35
41
  export const HTTPS_SCHEMA = DEFAULT_CONFIG.HTTPS_SCHEMA
36
42
  export const DB_NAME = database_name
37
43
 
44
+ type AuthProviders = Record<string, { disabled?: boolean; config?: unknown }>
38
45
  // TODO spostare nell'oggetto providers anche le altre configurazioni
39
46
  export const AUTH_CONFIG = {
40
47
  authCollection: auth_collection,
@@ -43,6 +50,7 @@ export const AUTH_CONFIG = {
43
50
  refreshTokensCollection: 'auth_refresh_tokens',
44
51
  resetPasswordConfig: configuration['local-userpass']?.config,
45
52
  localUserpassConfig: configuration['local-userpass']?.config,
53
+ authProviders: configuration as unknown as AuthProviders,
46
54
  user_id_field,
47
55
  on_user_creation_function_name,
48
56
  providers: {
@@ -25,6 +25,7 @@ type Config = {
25
25
  match: Record<string, unknown>
26
26
  operation_types: string[]
27
27
  operation_type?: 'CREATE' | 'DELETE' | 'LOGOUT'
28
+ providers?: string[]
28
29
  project: Record<string, unknown>
29
30
  service_name: string
30
31
  skip_catchup_events: boolean
@@ -112,6 +112,41 @@ const mapOpInverse = {
112
112
  const normalizeOperationTypes = (operationTypes: string[] = []) =>
113
113
  operationTypes.map((op) => op.toLowerCase())
114
114
 
115
+ const normalizeProviders = (providers: string[] = []) =>
116
+ providers
117
+ .map((provider) => (typeof provider === 'string' ? provider.trim().toLowerCase() : ''))
118
+ .filter((provider) => provider.length)
119
+
120
+ const extractProvidersFromDocument = (document?: Record<string, unknown> | null) => {
121
+ if (!document) return []
122
+ const providers: string[] = []
123
+ const identities = (document as { identities?: unknown }).identities
124
+ if (Array.isArray(identities)) {
125
+ for (const identity of identities) {
126
+ if (!identity || typeof identity !== 'object') continue
127
+ const providerType = (identity as { provider_type?: unknown }).provider_type
128
+ if (typeof providerType === 'string') {
129
+ providers.push(providerType.toLowerCase())
130
+ }
131
+ }
132
+ }
133
+ const rootProviderType = (document as { provider_type?: unknown }).provider_type
134
+ if (typeof rootProviderType === 'string') {
135
+ providers.push(rootProviderType.toLowerCase())
136
+ }
137
+ return providers
138
+ }
139
+
140
+ const matchesProviderFilter = (
141
+ document: Record<string, unknown> | null | undefined,
142
+ providerFilter: string[]
143
+ ) => {
144
+ if (!providerFilter.length) return true
145
+ const documentProviders = extractProvidersFromDocument(document)
146
+ if (!documentProviders.length) return false
147
+ return documentProviders.some((provider) => providerFilter.includes(provider))
148
+ }
149
+
115
150
  const resolveDocumentOptions = ({
116
151
  requestFullDocument,
117
152
  requestFullDocumentBeforeChange,
@@ -140,6 +175,7 @@ const handleAuthenticationTrigger = async ({
140
175
  app
141
176
  }: HandlerParams) => {
142
177
  const { database, isAutoTrigger, operation_types = [], operation_type } = config
178
+ const providerFilter = normalizeProviders(config.providers ?? [])
143
179
  const authCollection = AUTH_CONFIG.authCollection ?? 'auth_users'
144
180
  const collection = app.mongo.client.db(database || DB_NAME).collection(authCollection)
145
181
  const operationCandidates = operation_type ? mapOpInverse[operation_type] : operation_types
@@ -238,6 +274,9 @@ const handleAuthenticationTrigger = async ({
238
274
  _id: documentKey._id
239
275
  }) as Record<string, unknown> | null
240
276
  }
277
+ if (!matchesProviderFilter(logoutDocument, providerFilter)) {
278
+ return
279
+ }
241
280
  const userData = buildUserData(logoutDocument)
242
281
  if (!userData) {
243
282
  return
@@ -270,7 +309,11 @@ const handleAuthenticationTrigger = async ({
270
309
  if (isAutoTrigger || operation_type !== 'DELETE') {
271
310
  return
272
311
  }
273
- const userData = buildUserData(fullDocumentBeforeChange ?? confirmedDocument)
312
+ const deleteDocument = fullDocumentBeforeChange ?? confirmedDocument
313
+ if (!matchesProviderFilter(deleteDocument, providerFilter)) {
314
+ return
315
+ }
316
+ const userData = buildUserData(deleteDocument)
274
317
  if (!userData) {
275
318
  return
276
319
  }
@@ -299,7 +342,16 @@ const handleAuthenticationTrigger = async ({
299
342
  }
300
343
 
301
344
  if (isReplace) {
302
- const userData = buildUserData(confirmedDocument)
345
+ let replaceDocument = confirmedDocument
346
+ if (!replaceDocument && providerFilter.length && documentKey?._id) {
347
+ replaceDocument = await collection.findOne({
348
+ _id: documentKey._id
349
+ }) as Record<string, unknown> | null
350
+ }
351
+ if (!matchesProviderFilter(replaceDocument, providerFilter)) {
352
+ return
353
+ }
354
+ const userData = buildUserData(replaceDocument)
303
355
  if (!userData) {
304
356
  return
305
357
  }
@@ -347,6 +399,17 @@ const handleAuthenticationTrigger = async ({
347
399
  return
348
400
  }
349
401
 
402
+ let candidateDocument = confirmedDocument
403
+ if (!candidateDocument && providerFilter.length && documentKey?._id) {
404
+ candidateDocument = await collection.findOne({
405
+ _id: documentKey._id
406
+ }) as Record<string, unknown> | null
407
+ confirmedDocument = candidateDocument ?? confirmedDocument
408
+ }
409
+ if (!matchesProviderFilter(candidateDocument, providerFilter)) {
410
+ return
411
+ }
412
+
350
413
  const updateResult = await collection.findOneAndUpdate(
351
414
  {
352
415
  _id: documentKey._id,
@@ -371,6 +434,9 @@ const handleAuthenticationTrigger = async ({
371
434
 
372
435
  delete (document as { password?: unknown }).password
373
436
 
437
+ if (!matchesProviderFilter(document, providerFilter)) {
438
+ return
439
+ }
374
440
  const userData = buildUserData(document)
375
441
  if (!userData) {
376
442
  return
package/src/index.ts CHANGED
@@ -91,49 +91,51 @@ export async function initialize({
91
91
  StateManager.setData(key as Parameters<typeof StateManager.setData>[0], value)
92
92
  )
93
93
 
94
- await fastify.register(import('@fastify/swagger'))
95
-
96
- await fastify.register(import('@fastify/swagger-ui'), {
97
- routePrefix: '/documentation',
98
- uiConfig: {
99
- docExpansion: 'full',
100
- deepLinking: false
101
- },
102
- uiHooks: {
103
- onRequest: function (request, reply, next) {
104
- const swaggerUser = DEFAULT_CONFIG.SWAGGER_UI_USER
105
- const swaggerPassword = DEFAULT_CONFIG.SWAGGER_UI_PASSWORD
106
- if (!swaggerUser && !swaggerPassword) {
94
+ if (DEFAULT_CONFIG.SWAGGER_ENABLED) {
95
+ await fastify.register(import('@fastify/swagger'))
96
+
97
+ await fastify.register(import('@fastify/swagger-ui'), {
98
+ routePrefix: '/documentation',
99
+ uiConfig: {
100
+ docExpansion: 'full',
101
+ deepLinking: false
102
+ },
103
+ uiHooks: {
104
+ onRequest: function (request, reply, next) {
105
+ const swaggerUser = DEFAULT_CONFIG.SWAGGER_UI_USER
106
+ const swaggerPassword = DEFAULT_CONFIG.SWAGGER_UI_PASSWORD
107
+ if (!swaggerUser && !swaggerPassword) {
108
+ next()
109
+ return
110
+ }
111
+ const authHeader = request.headers.authorization
112
+ if (!authHeader || !authHeader.startsWith('Basic ')) {
113
+ reply
114
+ .code(401)
115
+ .header('WWW-Authenticate', 'Basic realm="Swagger UI"')
116
+ .send({ message: 'Unauthorized' })
117
+ return
118
+ }
119
+ const encoded = authHeader.slice('Basic '.length)
120
+ const decoded = Buffer.from(encoded, 'base64').toString('utf8')
121
+ const [user, pass] = decoded.split(':')
122
+ if (user !== swaggerUser || pass !== swaggerPassword) {
123
+ reply
124
+ .code(401)
125
+ .header('WWW-Authenticate', 'Basic realm="Swagger UI"')
126
+ .send({ message: 'Unauthorized' })
127
+ return
128
+ }
107
129
  next()
108
- return
109
- }
110
- const authHeader = request.headers.authorization
111
- if (!authHeader || !authHeader.startsWith('Basic ')) {
112
- reply
113
- .code(401)
114
- .header('WWW-Authenticate', 'Basic realm="Swagger UI"')
115
- .send({ message: 'Unauthorized' })
116
- return
117
- }
118
- const encoded = authHeader.slice('Basic '.length)
119
- const decoded = Buffer.from(encoded, 'base64').toString('utf8')
120
- const [user, pass] = decoded.split(':')
121
- if (user !== swaggerUser || pass !== swaggerPassword) {
122
- reply
123
- .code(401)
124
- .header('WWW-Authenticate', 'Basic realm="Swagger UI"')
125
- .send({ message: 'Unauthorized' })
126
- return
127
- }
128
- next()
130
+ },
131
+ preHandler: function (request, reply, next) { next() }
129
132
  },
130
- preHandler: function (request, reply, next) { next() }
131
- },
132
- staticCSP: true,
133
- transformStaticCSP: (header) => header,
134
- transformSpecification: (swaggerObject,) => { return swaggerObject },
135
- transformSpecificationClone: true
136
- })
133
+ staticCSP: true,
134
+ transformStaticCSP: (header) => header,
135
+ transformSpecification: (swaggerObject,) => { return swaggerObject },
136
+ transformSpecificationClone: true
137
+ })
138
+ }
137
139
 
138
140
  await registerPlugins({
139
141
  register: fastify.register,