@rebasepro/server-core 0.0.1-canary.09e5ec5
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/LICENSE +6 -0
- package/README.md +40 -0
- package/build-errors.txt +52 -0
- package/coverage/clover.xml +3739 -0
- package/coverage/coverage-final.json +31 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +266 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov-report/src/api/ast-schema-editor.ts.html +952 -0
- package/coverage/lcov-report/src/api/errors.ts.html +472 -0
- package/coverage/lcov-report/src/api/graphql/graphql-schema-generator.ts.html +1069 -0
- package/coverage/lcov-report/src/api/graphql/index.html +116 -0
- package/coverage/lcov-report/src/api/index.html +176 -0
- package/coverage/lcov-report/src/api/openapi-generator.ts.html +565 -0
- package/coverage/lcov-report/src/api/rest/api-generator.ts.html +994 -0
- package/coverage/lcov-report/src/api/rest/index.html +131 -0
- package/coverage/lcov-report/src/api/rest/query-parser.ts.html +550 -0
- package/coverage/lcov-report/src/api/schema-editor-routes.ts.html +202 -0
- package/coverage/lcov-report/src/api/server.ts.html +823 -0
- package/coverage/lcov-report/src/auth/admin-routes.ts.html +973 -0
- package/coverage/lcov-report/src/auth/index.html +176 -0
- package/coverage/lcov-report/src/auth/jwt.ts.html +574 -0
- package/coverage/lcov-report/src/auth/middleware.ts.html +745 -0
- package/coverage/lcov-report/src/auth/password.ts.html +310 -0
- package/coverage/lcov-report/src/auth/services.ts.html +2074 -0
- package/coverage/lcov-report/src/collections/index.html +116 -0
- package/coverage/lcov-report/src/collections/loader.ts.html +232 -0
- package/coverage/lcov-report/src/db/auth-schema.ts.html +523 -0
- package/coverage/lcov-report/src/db/data-transformer.ts.html +1753 -0
- package/coverage/lcov-report/src/db/entityService.ts.html +700 -0
- package/coverage/lcov-report/src/db/index.html +146 -0
- package/coverage/lcov-report/src/db/services/EntityFetchService.ts.html +4048 -0
- package/coverage/lcov-report/src/db/services/EntityPersistService.ts.html +883 -0
- package/coverage/lcov-report/src/db/services/RelationService.ts.html +3121 -0
- package/coverage/lcov-report/src/db/services/entity-helpers.ts.html +442 -0
- package/coverage/lcov-report/src/db/services/index.html +176 -0
- package/coverage/lcov-report/src/db/services/index.ts.html +124 -0
- package/coverage/lcov-report/src/generate-drizzle-schema-logic.ts.html +1960 -0
- package/coverage/lcov-report/src/index.html +116 -0
- package/coverage/lcov-report/src/services/driver-registry.ts.html +631 -0
- package/coverage/lcov-report/src/services/index.html +131 -0
- package/coverage/lcov-report/src/services/postgresDataDriver.ts.html +3025 -0
- package/coverage/lcov-report/src/storage/LocalStorageController.ts.html +1189 -0
- package/coverage/lcov-report/src/storage/S3StorageController.ts.html +970 -0
- package/coverage/lcov-report/src/storage/index.html +161 -0
- package/coverage/lcov-report/src/storage/storage-registry.ts.html +646 -0
- package/coverage/lcov-report/src/storage/types.ts.html +451 -0
- package/coverage/lcov-report/src/utils/drizzle-conditions.ts.html +3082 -0
- package/coverage/lcov-report/src/utils/index.html +116 -0
- package/coverage/lcov.info +7179 -0
- package/dist/common/src/collections/CollectionRegistry.d.ts +56 -0
- package/dist/common/src/collections/index.d.ts +1 -0
- package/dist/common/src/data/buildRebaseData.d.ts +14 -0
- package/dist/common/src/index.d.ts +3 -0
- package/dist/common/src/util/builders.d.ts +57 -0
- package/dist/common/src/util/callbacks.d.ts +6 -0
- package/dist/common/src/util/collections.d.ts +11 -0
- package/dist/common/src/util/common.d.ts +2 -0
- package/dist/common/src/util/conditions.d.ts +26 -0
- package/dist/common/src/util/entities.d.ts +58 -0
- package/dist/common/src/util/enums.d.ts +3 -0
- package/dist/common/src/util/index.d.ts +16 -0
- package/dist/common/src/util/navigation_from_path.d.ts +34 -0
- package/dist/common/src/util/navigation_utils.d.ts +20 -0
- package/dist/common/src/util/parent_references_from_path.d.ts +6 -0
- package/dist/common/src/util/paths.d.ts +14 -0
- package/dist/common/src/util/permissions.d.ts +5 -0
- package/dist/common/src/util/references.d.ts +2 -0
- package/dist/common/src/util/relations.d.ts +22 -0
- package/dist/common/src/util/resolutions.d.ts +72 -0
- package/dist/common/src/util/storage.d.ts +24 -0
- package/dist/index-DXVBFp5V.js +37 -0
- package/dist/index-DXVBFp5V.js.map +1 -0
- package/dist/index.es.js +49934 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +49968 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/server-core/src/api/ast-schema-editor.d.ts +21 -0
- package/dist/server-core/src/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
- package/dist/server-core/src/api/errors.d.ts +35 -0
- package/dist/server-core/src/api/graphql/graphql-schema-generator.d.ts +35 -0
- package/dist/server-core/src/api/graphql/index.d.ts +1 -0
- package/dist/server-core/src/api/index.d.ts +9 -0
- package/dist/server-core/src/api/openapi-generator.d.ts +16 -0
- package/dist/server-core/src/api/rest/api-generator.d.ts +64 -0
- package/dist/server-core/src/api/rest/index.d.ts +1 -0
- package/dist/server-core/src/api/rest/query-parser.d.ts +9 -0
- package/dist/server-core/src/api/schema-editor-routes.d.ts +3 -0
- package/dist/server-core/src/api/server.d.ts +40 -0
- package/dist/server-core/src/api/types.d.ts +90 -0
- package/dist/server-core/src/auth/admin-routes.d.ts +16 -0
- package/dist/server-core/src/auth/apple-oauth.d.ts +30 -0
- package/dist/server-core/src/auth/bitbucket-oauth.d.ts +11 -0
- package/dist/server-core/src/auth/discord-oauth.d.ts +14 -0
- package/dist/server-core/src/auth/facebook-oauth.d.ts +14 -0
- package/dist/server-core/src/auth/github-oauth.d.ts +15 -0
- package/dist/server-core/src/auth/gitlab-oauth.d.ts +13 -0
- package/dist/server-core/src/auth/google-oauth.d.ts +14 -0
- package/dist/server-core/src/auth/index.d.ts +23 -0
- package/dist/server-core/src/auth/interfaces.d.ts +309 -0
- package/dist/server-core/src/auth/jwt.d.ts +43 -0
- package/dist/server-core/src/auth/linkedin-oauth.d.ts +18 -0
- package/dist/server-core/src/auth/microsoft-oauth.d.ts +16 -0
- package/dist/server-core/src/auth/middleware.d.ts +81 -0
- package/dist/server-core/src/auth/password.d.ts +22 -0
- package/dist/server-core/src/auth/rate-limiter.d.ts +31 -0
- package/dist/server-core/src/auth/routes.d.ts +27 -0
- package/dist/server-core/src/auth/slack-oauth.d.ts +12 -0
- package/dist/server-core/src/auth/spotify-oauth.d.ts +12 -0
- package/dist/server-core/src/auth/twitter-oauth.d.ts +18 -0
- package/dist/server-core/src/bootstrappers/index.d.ts +0 -0
- package/dist/server-core/src/collections/BackendCollectionRegistry.d.ts +13 -0
- package/dist/server-core/src/collections/loader.d.ts +5 -0
- package/dist/server-core/src/cron/cron-loader.d.ts +17 -0
- package/dist/server-core/src/cron/cron-routes.d.ts +14 -0
- package/dist/server-core/src/cron/cron-scheduler.d.ts +61 -0
- package/dist/server-core/src/cron/cron-store.d.ts +32 -0
- package/dist/server-core/src/cron/index.d.ts +6 -0
- package/dist/server-core/src/db/interfaces.d.ts +18 -0
- package/dist/server-core/src/email/index.d.ts +6 -0
- package/dist/server-core/src/email/smtp-email-service.d.ts +25 -0
- package/dist/server-core/src/email/templates.d.ts +42 -0
- package/dist/server-core/src/email/types.d.ts +107 -0
- package/dist/server-core/src/functions/function-loader.d.ts +17 -0
- package/dist/server-core/src/functions/function-routes.d.ts +10 -0
- package/dist/server-core/src/functions/index.d.ts +3 -0
- package/dist/server-core/src/history/history-routes.d.ts +23 -0
- package/dist/server-core/src/history/index.d.ts +1 -0
- package/dist/server-core/src/index.d.ts +29 -0
- package/dist/server-core/src/init.d.ts +159 -0
- package/dist/server-core/src/serve-spa.d.ts +30 -0
- package/dist/server-core/src/services/driver-registry.d.ts +78 -0
- package/dist/server-core/src/singleton.d.ts +35 -0
- package/dist/server-core/src/storage/LocalStorageController.d.ts +46 -0
- package/dist/server-core/src/storage/S3StorageController.d.ts +36 -0
- package/dist/server-core/src/storage/index.d.ts +25 -0
- package/dist/server-core/src/storage/routes.d.ts +38 -0
- package/dist/server-core/src/storage/storage-registry.d.ts +78 -0
- package/dist/server-core/src/storage/types.d.ts +103 -0
- package/dist/server-core/src/types/index.d.ts +11 -0
- package/dist/server-core/src/utils/dev-port.d.ts +35 -0
- package/dist/server-core/src/utils/logger.d.ts +31 -0
- package/dist/server-core/src/utils/logging.d.ts +9 -0
- package/dist/server-core/src/utils/request-logger.d.ts +19 -0
- package/dist/server-core/src/utils/sql.d.ts +27 -0
- package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
- package/dist/types/src/controllers/auth.d.ts +119 -0
- package/dist/types/src/controllers/client.d.ts +170 -0
- package/dist/types/src/controllers/collection_registry.d.ts +45 -0
- package/dist/types/src/controllers/customization_controller.d.ts +60 -0
- package/dist/types/src/controllers/data.d.ts +168 -0
- package/dist/types/src/controllers/data_driver.d.ts +160 -0
- package/dist/types/src/controllers/database_admin.d.ts +11 -0
- package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
- package/dist/types/src/controllers/effective_role.d.ts +4 -0
- package/dist/types/src/controllers/email.d.ts +34 -0
- package/dist/types/src/controllers/index.d.ts +18 -0
- package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
- package/dist/types/src/controllers/navigation.d.ts +213 -0
- package/dist/types/src/controllers/registry.d.ts +54 -0
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
- package/dist/types/src/controllers/side_entity_controller.d.ts +90 -0
- package/dist/types/src/controllers/snackbar.d.ts +24 -0
- package/dist/types/src/controllers/storage.d.ts +171 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/rebase_context.d.ts +105 -0
- package/dist/types/src/types/backend.d.ts +536 -0
- package/dist/types/src/types/builders.d.ts +15 -0
- package/dist/types/src/types/chips.d.ts +5 -0
- package/dist/types/src/types/collections.d.ts +856 -0
- package/dist/types/src/types/cron.d.ts +102 -0
- package/dist/types/src/types/data_source.d.ts +64 -0
- package/dist/types/src/types/entities.d.ts +145 -0
- package/dist/types/src/types/entity_actions.d.ts +98 -0
- package/dist/types/src/types/entity_callbacks.d.ts +173 -0
- package/dist/types/src/types/entity_link_builder.d.ts +7 -0
- package/dist/types/src/types/entity_overrides.d.ts +10 -0
- package/dist/types/src/types/entity_views.d.ts +61 -0
- package/dist/types/src/types/export_import.d.ts +21 -0
- package/dist/types/src/types/index.d.ts +23 -0
- package/dist/types/src/types/locales.d.ts +4 -0
- package/dist/types/src/types/modify_collections.d.ts +5 -0
- package/dist/types/src/types/plugins.d.ts +279 -0
- package/dist/types/src/types/properties.d.ts +1176 -0
- package/dist/types/src/types/property_config.d.ts +70 -0
- package/dist/types/src/types/relations.d.ts +336 -0
- package/dist/types/src/types/slots.d.ts +252 -0
- package/dist/types/src/types/translations.d.ts +870 -0
- package/dist/types/src/types/user_management_delegate.d.ts +121 -0
- package/dist/types/src/types/websockets.d.ts +78 -0
- package/dist/types/src/users/index.d.ts +2 -0
- package/dist/types/src/users/roles.d.ts +22 -0
- package/dist/types/src/users/user.d.ts +46 -0
- package/history_diff.log +385 -0
- package/jest.config.cjs +16 -0
- package/package.json +86 -0
- package/scratch.ts +9 -0
- package/src/api/ast-schema-editor.ts +289 -0
- package/src/api/collections_for_test/callbacks_test_collection.ts +60 -0
- package/src/api/errors.ts +179 -0
- package/src/api/graphql/graphql-schema-generator.ts +336 -0
- package/src/api/graphql/index.ts +2 -0
- package/src/api/index.ts +11 -0
- package/src/api/openapi-generator.ts +715 -0
- package/src/api/rest/api-generator.ts +472 -0
- package/src/api/rest/index.ts +2 -0
- package/src/api/rest/query-parser.ts +155 -0
- package/src/api/schema-editor-routes.ts +41 -0
- package/src/api/server.ts +248 -0
- package/src/api/types.ts +90 -0
- package/src/auth/admin-routes.ts +529 -0
- package/src/auth/apple-oauth.ts +130 -0
- package/src/auth/bitbucket-oauth.ts +82 -0
- package/src/auth/discord-oauth.ts +83 -0
- package/src/auth/facebook-oauth.ts +72 -0
- package/src/auth/github-oauth.ts +110 -0
- package/src/auth/gitlab-oauth.ts +70 -0
- package/src/auth/google-oauth.ts +48 -0
- package/src/auth/index.ts +34 -0
- package/src/auth/interfaces.ts +363 -0
- package/src/auth/jwt.ts +181 -0
- package/src/auth/linkedin-oauth.ts +81 -0
- package/src/auth/microsoft-oauth.ts +88 -0
- package/src/auth/middleware.ts +384 -0
- package/src/auth/password.ts +77 -0
- package/src/auth/rate-limiter.ts +129 -0
- package/src/auth/routes.ts +788 -0
- package/src/auth/slack-oauth.ts +71 -0
- package/src/auth/spotify-oauth.ts +67 -0
- package/src/auth/twitter-oauth.ts +120 -0
- package/src/bootstrappers/index.ts +1 -0
- package/src/collections/BackendCollectionRegistry.ts +20 -0
- package/src/collections/loader.ts +49 -0
- package/src/cron/cron-loader.ts +89 -0
- package/src/cron/cron-routes.test.ts +265 -0
- package/src/cron/cron-routes.ts +85 -0
- package/src/cron/cron-scheduler.test.ts +421 -0
- package/src/cron/cron-scheduler.ts +413 -0
- package/src/cron/cron-store.ts +163 -0
- package/src/cron/index.ts +6 -0
- package/src/db/interfaces.ts +60 -0
- package/src/email/index.ts +18 -0
- package/src/email/smtp-email-service.ts +91 -0
- package/src/email/templates.ts +388 -0
- package/src/email/types.ts +105 -0
- package/src/functions/function-loader.ts +119 -0
- package/src/functions/function-routes.ts +31 -0
- package/src/functions/index.ts +3 -0
- package/src/history/history-routes.ts +129 -0
- package/src/history/index.ts +2 -0
- package/src/index.ts +66 -0
- package/src/init.ts +727 -0
- package/src/serve-spa.ts +81 -0
- package/src/services/driver-registry.ts +182 -0
- package/src/singleton.test.ts +28 -0
- package/src/singleton.ts +70 -0
- package/src/storage/LocalStorageController.ts +365 -0
- package/src/storage/S3StorageController.ts +298 -0
- package/src/storage/index.ts +43 -0
- package/src/storage/routes.ts +264 -0
- package/src/storage/storage-registry.ts +187 -0
- package/src/storage/types.ts +134 -0
- package/src/types/index.ts +27 -0
- package/src/utils/dev-port.ts +176 -0
- package/src/utils/logger.ts +143 -0
- package/src/utils/logging.ts +38 -0
- package/src/utils/request-logger.ts +66 -0
- package/src/utils/sql.ts +38 -0
- package/test/admin-routes.test.ts +640 -0
- package/test/api-generator.test.ts +501 -0
- package/test/ast-schema-editor.test.ts +63 -0
- package/test/auth-middleware-hono.test.ts +556 -0
- package/test/auth-routes.test.ts +1047 -0
- package/test/driver-registry.test.ts +282 -0
- package/test/error-propagation.test.ts +226 -0
- package/test/errors-hono.test.ts +133 -0
- package/test/errors.test.ts +155 -0
- package/test/jwt-security.test.ts +182 -0
- package/test/jwt.test.ts +324 -0
- package/test/middleware.test.ts +300 -0
- package/test/password.test.ts +165 -0
- package/test/query-parser.test.ts +263 -0
- package/test/rate-limiter.test.ts +102 -0
- package/test/safe-compare.test.ts +66 -0
- package/test/singleton.test.ts +59 -0
- package/test/storage-local.test.ts +271 -0
- package/test/storage-registry.test.ts +282 -0
- package/test/storage-routes.test.ts +222 -0
- package/test/storage-s3.test.ts +304 -0
- package/test-ast.ts +28 -0
- package/test.ts +6 -0
- package/test_output.txt +1133 -0
- package/tsconfig.json +49 -0
- package/tsconfig.prod.json +20 -0
- package/vite.config.ts +80 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { MiddlewareHandler, Context } from "hono";
|
|
2
|
+
import { DataDriver } from "@rebasepro/types";
|
|
3
|
+
import { verifyAccessToken, AccessTokenPayload } from "./jwt";
|
|
4
|
+
import { HonoEnv } from "../api/types";
|
|
5
|
+
import { timingSafeEqual } from "crypto";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Result from a custom auth validator.
|
|
9
|
+
* - `false`/`null`/`undefined` = not authenticated
|
|
10
|
+
* - `true` = authenticated as default user
|
|
11
|
+
* - object with `userId` or `uid` = authenticated with user info
|
|
12
|
+
*/
|
|
13
|
+
export type AuthResult = boolean | null | undefined | { userId?: string; uid?: string; roles?: string[]; [key: string]: unknown };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Options for creating an auth middleware via createAuthMiddleware()
|
|
17
|
+
*/
|
|
18
|
+
export interface AuthMiddlewareOptions {
|
|
19
|
+
/** DataDriver to scope via withAuth() for RLS */
|
|
20
|
+
driver: DataDriver;
|
|
21
|
+
/**
|
|
22
|
+
* If true, return 401 when no valid token is present.
|
|
23
|
+
*
|
|
24
|
+
* **Defaults to `true` (secure by default).** Set to `false` only for
|
|
25
|
+
* intentionally public endpoints where access control is fully delegated
|
|
26
|
+
* to Postgres Row-Level Security policies.
|
|
27
|
+
*/
|
|
28
|
+
requireAuth?: boolean;
|
|
29
|
+
/** Optional custom validator (for non-JWT auth, e.g. Firebase Auth) */
|
|
30
|
+
validator?: (c: Context<HonoEnv>) => Promise<AuthResult>;
|
|
31
|
+
/**
|
|
32
|
+
* A static secret key for server-to-server / script authentication.
|
|
33
|
+
*
|
|
34
|
+
* When a request sends `Authorization: Bearer <key>` and the key matches
|
|
35
|
+
* this value, the request is granted admin-level access (uid: `service`,
|
|
36
|
+
* roles: `["admin"]`) **without** JWT verification. The driver is scoped
|
|
37
|
+
* via `withAuth()` with the service identity.
|
|
38
|
+
*
|
|
39
|
+
* This is the Rebase equivalent of a Firebase Service Account key.
|
|
40
|
+
* Set via `REBASE_SERVICE_KEY` in `.env` and pass through the backend config.
|
|
41
|
+
*
|
|
42
|
+
* **Security:** The comparison uses constant-time equality to prevent
|
|
43
|
+
* timing attacks. The key must be at least 32 characters.
|
|
44
|
+
*/
|
|
45
|
+
serviceKey?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Hono middleware that requires a valid JWT token
|
|
50
|
+
* Returns 401 if token is missing or invalid
|
|
51
|
+
*/
|
|
52
|
+
export const requireAuth: MiddlewareHandler<HonoEnv> = async (
|
|
53
|
+
c,
|
|
54
|
+
next
|
|
55
|
+
) => {
|
|
56
|
+
const authHeader = c.req.header("authorization");
|
|
57
|
+
const queryToken = c.req.query("token");
|
|
58
|
+
const hasBearer = authHeader && authHeader.startsWith("Bearer ");
|
|
59
|
+
|
|
60
|
+
if (!hasBearer && !queryToken) {
|
|
61
|
+
return c.json({
|
|
62
|
+
error: {
|
|
63
|
+
message: "Authorization header or token query parameter missing or invalid",
|
|
64
|
+
code: "UNAUTHORIZED"
|
|
65
|
+
}
|
|
66
|
+
}, 401);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const token = hasBearer ? authHeader!.substring(7) : queryToken!;
|
|
70
|
+
const payload = verifyAccessToken(token);
|
|
71
|
+
|
|
72
|
+
if (!payload) {
|
|
73
|
+
return c.json({
|
|
74
|
+
error: {
|
|
75
|
+
message: "Invalid or expired token",
|
|
76
|
+
code: "UNAUTHORIZED"
|
|
77
|
+
}
|
|
78
|
+
}, 401);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
c.set("user", payload);
|
|
82
|
+
return next();
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Factory that creates a requireAuth middleware with optional service key support.
|
|
87
|
+
*
|
|
88
|
+
* When `serviceKey` is provided, the middleware will check if the Bearer token
|
|
89
|
+
* matches the service key using constant-time comparison. If it matches, the
|
|
90
|
+
* request is authenticated as a service user with admin privileges.
|
|
91
|
+
*
|
|
92
|
+
* This allows admin routes (which use standalone requireAuth + requireAdmin)
|
|
93
|
+
* to be accessed via service keys for scripts and server-to-server calls.
|
|
94
|
+
*/
|
|
95
|
+
export function createRequireAuth(options?: { serviceKey?: string }): MiddlewareHandler<HonoEnv> {
|
|
96
|
+
if (!options?.serviceKey) return requireAuth;
|
|
97
|
+
|
|
98
|
+
const key = options.serviceKey;
|
|
99
|
+
return async (c, next) => {
|
|
100
|
+
const authHeader = c.req.header("authorization");
|
|
101
|
+
const queryToken = c.req.query("token");
|
|
102
|
+
const hasBearer = authHeader && authHeader.startsWith("Bearer ");
|
|
103
|
+
|
|
104
|
+
if (!hasBearer && !queryToken) {
|
|
105
|
+
return c.json({
|
|
106
|
+
error: {
|
|
107
|
+
message: "Authorization header or token query parameter missing or invalid",
|
|
108
|
+
code: "UNAUTHORIZED"
|
|
109
|
+
}
|
|
110
|
+
}, 401);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const token = hasBearer ? authHeader!.substring(7) : queryToken!;
|
|
114
|
+
|
|
115
|
+
// Check service key first (constant-time comparison)
|
|
116
|
+
if (safeCompare(token, key)) {
|
|
117
|
+
c.set("user", { userId: "service",
|
|
118
|
+
roles: ["admin"] } as AccessTokenPayload);
|
|
119
|
+
return next();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Fall back to JWT verification
|
|
123
|
+
const payload = verifyAccessToken(token);
|
|
124
|
+
|
|
125
|
+
if (!payload) {
|
|
126
|
+
return c.json({
|
|
127
|
+
error: {
|
|
128
|
+
message: "Invalid or expired token",
|
|
129
|
+
code: "UNAUTHORIZED"
|
|
130
|
+
}
|
|
131
|
+
}, 401);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
c.set("user", payload);
|
|
135
|
+
return next();
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Middleware that requires the user to have an admin or schema-admin role.
|
|
141
|
+
* Must be used AFTER requireAuth or on a route where user is guaranteed.
|
|
142
|
+
*/
|
|
143
|
+
export const requireAdmin: MiddlewareHandler<HonoEnv> = async (
|
|
144
|
+
c,
|
|
145
|
+
next
|
|
146
|
+
) => {
|
|
147
|
+
const user = c.get("user");
|
|
148
|
+
if (!user) {
|
|
149
|
+
return c.json({
|
|
150
|
+
error: {
|
|
151
|
+
message: "User not authenticated. requireAuth middleware is missing?",
|
|
152
|
+
code: "UNAUTHORIZED"
|
|
153
|
+
}
|
|
154
|
+
}, 401);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const roles = (typeof user === "object" && user !== null && "roles" in user) ? (user.roles || []) : [];
|
|
158
|
+
const isAdmin = roles.some((role: string) => {
|
|
159
|
+
return role === "admin" || role === "schema-admin";
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (!isAdmin) {
|
|
163
|
+
return c.json({
|
|
164
|
+
error: {
|
|
165
|
+
message: "Admin privileges required for this operation",
|
|
166
|
+
code: "FORBIDDEN"
|
|
167
|
+
}
|
|
168
|
+
}, 403);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return next();
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Middleware that optionally extracts user from JWT
|
|
177
|
+
* Does not return 401 if token is missing - allows anonymous access
|
|
178
|
+
*/
|
|
179
|
+
export const optionalAuth: MiddlewareHandler<HonoEnv> = async (
|
|
180
|
+
c,
|
|
181
|
+
next
|
|
182
|
+
) => {
|
|
183
|
+
const authHeader = c.req.header("authorization");
|
|
184
|
+
const queryToken = c.req.query("token");
|
|
185
|
+
const hasBearer = authHeader && authHeader.startsWith("Bearer ");
|
|
186
|
+
|
|
187
|
+
if (hasBearer || queryToken) {
|
|
188
|
+
const token = hasBearer ? authHeader!.substring(7) : queryToken!;
|
|
189
|
+
const payload = verifyAccessToken(token);
|
|
190
|
+
if (payload) {
|
|
191
|
+
c.set("user", payload);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return next();
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Extract user from token - for WebSocket authentication
|
|
200
|
+
*/
|
|
201
|
+
export function extractUserFromToken(token: string): AccessTokenPayload | null {
|
|
202
|
+
return verifyAccessToken(token);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Helper to scope a DataDriver via withAuth() for RLS.
|
|
207
|
+
* SECURITY: If withAuth() is available but fails, the error is re-thrown
|
|
208
|
+
* so the request is denied rather than proceeding with unscoped access.
|
|
209
|
+
*/
|
|
210
|
+
async function scopeDataDriver(
|
|
211
|
+
driver: DataDriver,
|
|
212
|
+
user: { uid: string; roles?: string[] }
|
|
213
|
+
): Promise<DataDriver> {
|
|
214
|
+
if ("withAuth" in driver && typeof (driver as Record<string, unknown>).withAuth === "function") {
|
|
215
|
+
// Fail closed — do NOT catch and swallow errors here.
|
|
216
|
+
// If RLS scoping fails the request must be rejected.
|
|
217
|
+
return await (driver as unknown as { withAuth: (user: Record<string, unknown>) => Promise<DataDriver> }).withAuth(user);
|
|
218
|
+
}
|
|
219
|
+
return driver;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Create a configurable auth middleware that handles:
|
|
224
|
+
* 1. Token extraction (via custom validator or JWT Bearer token)
|
|
225
|
+
* 2. RLS-scoped DataDriver via withAuth()
|
|
226
|
+
* 3. Enforcement (401 when requireAuth is true and no user)
|
|
227
|
+
*
|
|
228
|
+
* **Secure by default:** `requireAuth` defaults to `true`. Anonymous
|
|
229
|
+
* access is only allowed when the developer explicitly opts out by
|
|
230
|
+
* setting `requireAuth: false`, indicating that Postgres RLS policies
|
|
231
|
+
* fully control access.
|
|
232
|
+
*
|
|
233
|
+
* **Fail-closed:** The raw unscoped driver is never placed in the
|
|
234
|
+
* request context. Every code path either scopes via `withAuth()` or
|
|
235
|
+
* rejects the request. This prevents silent RLS bypass.
|
|
236
|
+
*
|
|
237
|
+
* This is the single source of truth for HTTP auth in Rebase.
|
|
238
|
+
* Use this instead of manually parsing tokens in route handlers.
|
|
239
|
+
*/
|
|
240
|
+
/**
|
|
241
|
+
* Constant-time string comparison to prevent timing attacks on service keys.
|
|
242
|
+
*
|
|
243
|
+
* We intentionally avoid early-returning on length mismatch because that
|
|
244
|
+
* would leak the key's length through timing differences. Instead, both
|
|
245
|
+
* inputs are padded to the same length so `timingSafeEqual` always runs
|
|
246
|
+
* over equal-length buffers.
|
|
247
|
+
*/
|
|
248
|
+
function safeCompare(a: string, b: string): boolean {
|
|
249
|
+
const maxLen = Math.max(a.length, b.length);
|
|
250
|
+
// Pad both to maxLen so timingSafeEqual always compares equal-length buffers.
|
|
251
|
+
// If the original lengths differ the result will be false due to the padding
|
|
252
|
+
// difference, but the comparison still takes constant time.
|
|
253
|
+
const bufA = Buffer.alloc(maxLen);
|
|
254
|
+
const bufB = Buffer.alloc(maxLen);
|
|
255
|
+
bufA.write(a);
|
|
256
|
+
bufB.write(b);
|
|
257
|
+
try {
|
|
258
|
+
const isEqual = timingSafeEqual(bufA, bufB);
|
|
259
|
+
// Even though padding makes mismatched-length strings compare as
|
|
260
|
+
// different bytes, we still need to verify lengths match to avoid
|
|
261
|
+
// a padded shorter string accidentally equaling a longer one that
|
|
262
|
+
// has trailing null bytes.
|
|
263
|
+
return isEqual && a.length === b.length;
|
|
264
|
+
} catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function createAuthMiddleware(options: AuthMiddlewareOptions): MiddlewareHandler<HonoEnv> {
|
|
270
|
+
const { driver, requireAuth: enforceAuth = true, validator, serviceKey } = options;
|
|
271
|
+
|
|
272
|
+
return async (c, next) => {
|
|
273
|
+
if (validator) {
|
|
274
|
+
// Custom validator path (e.g., Firebase Auth, API keys)
|
|
275
|
+
try {
|
|
276
|
+
const authResult = await validator(c);
|
|
277
|
+
if (authResult && typeof authResult === "object") {
|
|
278
|
+
const id = ("userId" in authResult ? authResult.userId : undefined)
|
|
279
|
+
|| ("uid" in authResult ? authResult.uid : undefined);
|
|
280
|
+
if (id) {
|
|
281
|
+
const roles = authResult.roles || [];
|
|
282
|
+
c.set("user", { userId: id,
|
|
283
|
+
roles });
|
|
284
|
+
const user = { uid: id,
|
|
285
|
+
roles,
|
|
286
|
+
...authResult };
|
|
287
|
+
c.set("driver", await scopeDataDriver(driver, user));
|
|
288
|
+
} else {
|
|
289
|
+
// Validator returned an object but without an ID — scope as anon
|
|
290
|
+
c.set("driver", await scopeDataDriver(driver, { uid: "anon",
|
|
291
|
+
roles: ["anon"] }));
|
|
292
|
+
}
|
|
293
|
+
} else if (authResult === true) {
|
|
294
|
+
c.set("user", { userId: "default",
|
|
295
|
+
roles: [] });
|
|
296
|
+
c.set("driver", await scopeDataDriver(driver, { uid: "default",
|
|
297
|
+
roles: [] }));
|
|
298
|
+
} else {
|
|
299
|
+
// Not authenticated — scope as anon so RLS can evaluate.
|
|
300
|
+
// Fail closed: if anon scoping fails, reject instead of
|
|
301
|
+
// falling back to the raw driver.
|
|
302
|
+
c.set("driver", await scopeDataDriver(driver, { uid: "anon",
|
|
303
|
+
roles: ["anon"] }));
|
|
304
|
+
}
|
|
305
|
+
} catch (error) {
|
|
306
|
+
return c.json({ error: { message: "Unauthorized",
|
|
307
|
+
code: "UNAUTHORIZED" } }, 401);
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
// Default JWT path (with optional service key support)
|
|
311
|
+
const authHeader = c.req.header("authorization");
|
|
312
|
+
const queryToken = c.req.query("token");
|
|
313
|
+
const hasBearer = authHeader && authHeader.startsWith("Bearer ");
|
|
314
|
+
|
|
315
|
+
if (hasBearer || queryToken) {
|
|
316
|
+
const token = hasBearer ? authHeader!.substring(7) : queryToken!;
|
|
317
|
+
|
|
318
|
+
// ── Service Key check ──────────────────────────────────
|
|
319
|
+
// Check BEFORE JWT verification. Service keys are static
|
|
320
|
+
// secrets (like Firebase SA keys) that grant admin access
|
|
321
|
+
// for scripts, cron jobs, and server-to-server calls.
|
|
322
|
+
if (serviceKey && safeCompare(token, serviceKey)) {
|
|
323
|
+
const serviceUser: AccessTokenPayload = {
|
|
324
|
+
userId: "service",
|
|
325
|
+
roles: ["admin"]
|
|
326
|
+
};
|
|
327
|
+
c.set("user", serviceUser);
|
|
328
|
+
try {
|
|
329
|
+
c.set("driver", await scopeDataDriver(driver, {
|
|
330
|
+
uid: "service",
|
|
331
|
+
roles: ["admin"]
|
|
332
|
+
}));
|
|
333
|
+
} catch (error) {
|
|
334
|
+
console.error("[AUTH] RLS scoping failed for service key:", error);
|
|
335
|
+
return c.json({ error: { message: "Internal authentication error",
|
|
336
|
+
code: "INTERNAL_ERROR" } }, 500);
|
|
337
|
+
}
|
|
338
|
+
} else {
|
|
339
|
+
// ── JWT verification ───────────────────────────────────
|
|
340
|
+
const payload = extractUserFromToken(token);
|
|
341
|
+
|
|
342
|
+
if (payload) {
|
|
343
|
+
c.set("user", payload);
|
|
344
|
+
try {
|
|
345
|
+
const user = { uid: payload.userId,
|
|
346
|
+
roles: payload.roles };
|
|
347
|
+
c.set("driver", await scopeDataDriver(driver, user));
|
|
348
|
+
} catch (error) {
|
|
349
|
+
// withAuth() failed for a valid token — reject (fail closed)
|
|
350
|
+
console.error("[AUTH] RLS scoping failed for authenticated user:", error);
|
|
351
|
+
return c.json({ error: { message: "Internal authentication error",
|
|
352
|
+
code: "INTERNAL_ERROR" } }, 500);
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
// Token present but invalid — always reject.
|
|
356
|
+
// Providing a malformed token should never grant access,
|
|
357
|
+
// regardless of requireAuth setting.
|
|
358
|
+
return c.json({ error: { message: "Invalid or expired token",
|
|
359
|
+
code: "UNAUTHORIZED" } }, 401);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
// No token provided — scope as anon for RLS evaluation.
|
|
364
|
+
// Fail closed: if anon scoping fails, return 500 rather
|
|
365
|
+
// than silently proceeding with an unscoped driver.
|
|
366
|
+
try {
|
|
367
|
+
c.set("driver", await scopeDataDriver(driver, { uid: "anon",
|
|
368
|
+
roles: ["anon"] }));
|
|
369
|
+
} catch (error) {
|
|
370
|
+
console.error("[AUTH] Failed to create anon-scoped driver:", error);
|
|
371
|
+
return c.json({ error: { message: "Server configuration error",
|
|
372
|
+
code: "INTERNAL_ERROR" } }, 500);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (enforceAuth && !c.get("user")) {
|
|
378
|
+
return c.json({ error: { message: "Unauthorized: Authentication required",
|
|
379
|
+
code: "UNAUTHORIZED" } }, 401);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return next();
|
|
383
|
+
};
|
|
384
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { scrypt, randomBytes, timingSafeEqual } from "crypto";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
|
|
4
|
+
const scryptAsync = promisify(scrypt);
|
|
5
|
+
|
|
6
|
+
// Scrypt parameters (recommended values for 2024+)
|
|
7
|
+
const SALT_LENGTH = 32;
|
|
8
|
+
const KEY_LENGTH = 64;
|
|
9
|
+
const SCRYPT_PARAMS = { N: 16384,
|
|
10
|
+
r: 8,
|
|
11
|
+
p: 1 };
|
|
12
|
+
|
|
13
|
+
export interface PasswordValidationResult {
|
|
14
|
+
valid: boolean;
|
|
15
|
+
errors: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Password requirements:
|
|
20
|
+
* - Minimum 8 characters
|
|
21
|
+
* - At least 1 uppercase letter
|
|
22
|
+
* - At least 1 lowercase letter
|
|
23
|
+
* - At least 1 number
|
|
24
|
+
*/
|
|
25
|
+
export function validatePasswordStrength(password: string): PasswordValidationResult {
|
|
26
|
+
const errors: string[] = [];
|
|
27
|
+
|
|
28
|
+
if (password.length < 8) {
|
|
29
|
+
errors.push("Password must be at least 8 characters long");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!/[A-Z]/.test(password)) {
|
|
33
|
+
errors.push("Password must contain at least one uppercase letter");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!/[a-z]/.test(password)) {
|
|
37
|
+
errors.push("Password must contain at least one lowercase letter");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!/[0-9]/.test(password)) {
|
|
41
|
+
errors.push("Password must contain at least one number");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
valid: errors.length === 0,
|
|
46
|
+
errors
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Hash a password using Node's built-in scrypt
|
|
52
|
+
* Returns format: salt:hash (both hex encoded)
|
|
53
|
+
*/
|
|
54
|
+
export async function hashPassword(password: string): Promise<string> {
|
|
55
|
+
const salt = randomBytes(SALT_LENGTH);
|
|
56
|
+
const derivedKey = await scryptAsync(password, salt, KEY_LENGTH) as Buffer;
|
|
57
|
+
return `${salt.toString("hex")}:${derivedKey.toString("hex")}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Verify a password against a scrypt hash
|
|
62
|
+
* Expects format: salt:hash (both hex encoded)
|
|
63
|
+
*/
|
|
64
|
+
export async function verifyPassword(password: string, storedHash: string): Promise<boolean> {
|
|
65
|
+
const [saltHex, hashHex] = storedHash.split(":");
|
|
66
|
+
if (!saltHex || !hashHex) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const salt = Buffer.from(saltHex, "hex");
|
|
71
|
+
const storedKey = Buffer.from(hashHex, "hex");
|
|
72
|
+
|
|
73
|
+
const derivedKey = await scryptAsync(password, salt, KEY_LENGTH) as Buffer;
|
|
74
|
+
|
|
75
|
+
// Use timing-safe comparison to prevent timing attacks
|
|
76
|
+
return timingSafeEqual(derivedKey, storedKey);
|
|
77
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { MiddlewareHandler } from "hono";
|
|
2
|
+
import { HonoEnv } from "../api/types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* In-memory sliding-window rate limiter for Hono.
|
|
6
|
+
*
|
|
7
|
+
* Suitable for single-instance and moderate-scale deployments.
|
|
8
|
+
* For multi-instance setups, consider a shared store
|
|
9
|
+
* (e.g. PostgreSQL-backed counters or an external rate-limit service).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
interface RateLimitEntry {
|
|
13
|
+
timestamps: number[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface RateLimiterOptions {
|
|
17
|
+
/** Time window in milliseconds (default: 15 minutes) */
|
|
18
|
+
windowMs?: number;
|
|
19
|
+
/** Maximum requests per window (default: 100) */
|
|
20
|
+
limit?: number;
|
|
21
|
+
/** Key generator function. Defaults to IP-based keying. */
|
|
22
|
+
keyGenerator?: (c: Parameters<MiddlewareHandler<HonoEnv>>[0]) => string;
|
|
23
|
+
/** Custom message for rate limit responses */
|
|
24
|
+
message?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create a rate-limiting middleware.
|
|
29
|
+
*
|
|
30
|
+
* Uses a sliding window algorithm: only timestamps within the last
|
|
31
|
+
* `windowMs` milliseconds are counted. Old entries are garbage-collected
|
|
32
|
+
* every `windowMs` to prevent unbounded memory growth.
|
|
33
|
+
*/
|
|
34
|
+
export function createRateLimiter(options: RateLimiterOptions = {}): MiddlewareHandler<HonoEnv> {
|
|
35
|
+
const {
|
|
36
|
+
windowMs = 15 * 60 * 1000,
|
|
37
|
+
limit = 100,
|
|
38
|
+
keyGenerator = defaultKeyGenerator,
|
|
39
|
+
message = "Too many requests, please try again later."
|
|
40
|
+
} = options;
|
|
41
|
+
|
|
42
|
+
const store = new Map<string, RateLimitEntry>();
|
|
43
|
+
|
|
44
|
+
// Periodic cleanup to prevent memory leak from expired entries
|
|
45
|
+
const cleanupInterval = setInterval(() => {
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
for (const [key, entry] of store.entries()) {
|
|
48
|
+
entry.timestamps = entry.timestamps.filter(t => now - t < windowMs);
|
|
49
|
+
if (entry.timestamps.length === 0) {
|
|
50
|
+
store.delete(key);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}, windowMs);
|
|
54
|
+
|
|
55
|
+
// Allow the Node.js process to exit even if the interval is still running
|
|
56
|
+
if (cleanupInterval.unref) {
|
|
57
|
+
cleanupInterval.unref();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return async (c, next) => {
|
|
61
|
+
const key = keyGenerator(c);
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
|
|
64
|
+
let entry = store.get(key);
|
|
65
|
+
if (!entry) {
|
|
66
|
+
entry = { timestamps: [] };
|
|
67
|
+
store.set(key, entry);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Remove timestamps outside the current window
|
|
71
|
+
entry.timestamps = entry.timestamps.filter(t => now - t < windowMs);
|
|
72
|
+
|
|
73
|
+
if (entry.timestamps.length >= limit) {
|
|
74
|
+
const retryAfterMs = entry.timestamps[0] + windowMs - now;
|
|
75
|
+
const retryAfterSec = Math.ceil(retryAfterMs / 1000);
|
|
76
|
+
|
|
77
|
+
c.header("Retry-After", String(retryAfterSec));
|
|
78
|
+
c.header("X-RateLimit-Limit", String(limit));
|
|
79
|
+
c.header("X-RateLimit-Remaining", "0");
|
|
80
|
+
c.header("X-RateLimit-Reset", String(Math.ceil((now + retryAfterMs) / 1000)));
|
|
81
|
+
|
|
82
|
+
return c.json({
|
|
83
|
+
error: {
|
|
84
|
+
message,
|
|
85
|
+
code: "RATE_LIMITED"
|
|
86
|
+
}
|
|
87
|
+
}, 429);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
entry.timestamps.push(now);
|
|
91
|
+
|
|
92
|
+
// Set rate limit headers
|
|
93
|
+
c.header("X-RateLimit-Limit", String(limit));
|
|
94
|
+
c.header("X-RateLimit-Remaining", String(limit - entry.timestamps.length));
|
|
95
|
+
|
|
96
|
+
return next();
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Default key generator: extract client IP from standard headers.
|
|
102
|
+
*/
|
|
103
|
+
function defaultKeyGenerator(c: Parameters<MiddlewareHandler<HonoEnv>>[0]): string {
|
|
104
|
+
return (
|
|
105
|
+
c.req.header("x-forwarded-for")?.split(",")[0]?.trim() ||
|
|
106
|
+
c.req.header("x-real-ip") ||
|
|
107
|
+
"unknown"
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Pre-configured rate limiter for general auth endpoints (login, register).
|
|
113
|
+
* 200 requests per 15 minutes per IP.
|
|
114
|
+
*/
|
|
115
|
+
export const defaultAuthLimiter = createRateLimiter({
|
|
116
|
+
windowMs: 15 * 60 * 1000,
|
|
117
|
+
limit: 200,
|
|
118
|
+
message: "Too many authentication attempts, please try again later."
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Pre-configured strict rate limiter for sensitive endpoints (password reset, verification).
|
|
123
|
+
* 50 requests per 15 minutes per IP.
|
|
124
|
+
*/
|
|
125
|
+
export const strictAuthLimiter = createRateLimiter({
|
|
126
|
+
windowMs: 15 * 60 * 1000,
|
|
127
|
+
limit: 50,
|
|
128
|
+
message: "Too many requests to this sensitive endpoint, please try again later."
|
|
129
|
+
});
|