@rebasepro/server-core 0.7.0 → 0.9.0
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 +2 -2
- package/dist/api/ast-schema-editor.d.ts +21 -0
- package/dist/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
- package/dist/api/errors.d.ts +53 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/logs-routes.d.ts +37 -0
- package/dist/api/openapi-generator.d.ts +16 -0
- package/dist/api/rest/api-generator.d.ts +68 -0
- package/{src/api/rest/index.ts → dist/api/rest/index.d.ts} +0 -1
- package/dist/api/rest/query-parser.d.ts +6 -0
- package/dist/api/schema-editor-routes.d.ts +3 -0
- package/dist/api/types.d.ts +56 -0
- package/dist/auth/adapter-middleware.d.ts +43 -0
- package/dist/auth/admin-roles-route.d.ts +18 -0
- package/dist/auth/admin-user-ops.d.ts +79 -0
- package/dist/auth/admin-users-route.d.ts +28 -0
- package/dist/auth/api-keys/api-key-middleware.d.ts +39 -0
- package/{src/auth/api-keys/api-key-permission-guard.ts → dist/auth/api-keys/api-key-permission-guard.d.ts} +2 -34
- package/dist/auth/api-keys/api-key-routes.d.ts +20 -0
- package/dist/auth/api-keys/api-key-store.d.ts +35 -0
- package/{src/auth/api-keys/api-key-types.ts → dist/auth/api-keys/api-key-types.d.ts} +0 -6
- package/{src/auth/api-keys/index.ts → dist/auth/api-keys/index.d.ts} +2 -22
- package/dist/auth/apple-oauth.d.ts +30 -0
- package/{src/auth/auth-hooks.ts → dist/auth/auth-hooks.d.ts} +15 -73
- package/dist/auth/bitbucket-oauth.d.ts +11 -0
- package/dist/auth/builtin-auth-adapter.d.ts +55 -0
- package/dist/auth/cookie-utils.d.ts +32 -0
- package/{src/auth/crypto-utils.ts → dist/auth/crypto-utils.d.ts} +1 -16
- package/dist/auth/custom-auth-adapter.d.ts +39 -0
- package/dist/auth/discord-oauth.d.ts +14 -0
- package/dist/auth/facebook-oauth.d.ts +14 -0
- package/dist/auth/github-oauth.d.ts +15 -0
- package/dist/auth/gitlab-oauth.d.ts +13 -0
- package/dist/auth/google-oauth.d.ts +47 -0
- package/{src/auth/index.ts → dist/auth/index.d.ts} +4 -22
- package/{src/auth/interfaces.ts → dist/auth/interfaces.d.ts} +20 -89
- package/dist/auth/jwt.d.ts +67 -0
- package/dist/auth/linkedin-oauth.d.ts +18 -0
- package/dist/auth/magic-link-routes.d.ts +32 -0
- package/dist/auth/mfa-crypto.d.ts +23 -0
- package/dist/auth/mfa-routes.d.ts +7 -0
- package/dist/auth/mfa.d.ts +49 -0
- package/dist/auth/microsoft-oauth.d.ts +16 -0
- package/dist/auth/middleware.d.ts +158 -0
- package/dist/auth/password.d.ts +22 -0
- package/dist/auth/rate-limiter.d.ts +50 -0
- package/dist/auth/reset-password-admin.d.ts +29 -0
- package/dist/auth/rls-scope.d.ts +31 -0
- package/dist/auth/routes.d.ts +66 -0
- package/dist/auth/session-routes.d.ts +29 -0
- package/dist/auth/slack-oauth.d.ts +12 -0
- package/dist/auth/spotify-oauth.d.ts +12 -0
- package/dist/auth/twitter-oauth.d.ts +18 -0
- package/dist/collections/BackendCollectionRegistry.d.ts +13 -0
- package/dist/collections/loader.d.ts +5 -0
- package/dist/cron/cron-loader.d.ts +18 -0
- package/dist/cron/cron-routes.d.ts +14 -0
- package/dist/cron/cron-scheduler.d.ts +106 -0
- package/dist/cron/cron-store.d.ts +32 -0
- package/dist/cron/define-cron.d.ts +28 -0
- package/{src/cron/index.ts → dist/cron/index.d.ts} +1 -0
- package/dist/db/interfaces.d.ts +18 -0
- package/dist/dynamic-import-Dvh-K5fl.js +22 -0
- package/dist/dynamic-import-Dvh-K5fl.js.map +1 -0
- package/dist/email/index.d.ts +6 -0
- package/dist/email/smtp-email-service.d.ts +30 -0
- package/dist/email/templates.d.ts +50 -0
- package/{src/email/types.ts → dist/email/types.d.ts} +40 -36
- package/dist/env.d.ts +102 -0
- package/dist/functions/define-function.d.ts +55 -0
- package/dist/functions/function-loader.d.ts +18 -0
- package/dist/functions/function-routes.d.ts +10 -0
- package/{src/functions/index.ts → dist/functions/index.d.ts} +2 -0
- package/dist/history/history-routes.d.ts +23 -0
- package/{src/history/index.ts → dist/history/index.d.ts} +0 -1
- package/dist/index.d.ts +35 -0
- package/dist/index.es.js +18184 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +48629 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/init/docs.d.ts +4 -0
- package/dist/init/health.d.ts +2 -0
- package/dist/init/middlewares.d.ts +10 -0
- package/dist/init/shutdown.d.ts +65 -0
- package/dist/init/storage.d.ts +5 -0
- package/dist/init.d.ts +344 -0
- package/dist/jwt-BwIn8xmk.js +4200 -0
- package/dist/jwt-BwIn8xmk.js.map +1 -0
- package/dist/logger-BYU66ENZ.js +94 -0
- package/dist/logger-BYU66ENZ.js.map +1 -0
- package/dist/ms-DnYXB-Wd.js +162 -0
- package/dist/ms-DnYXB-Wd.js.map +1 -0
- package/dist/openapi-generator-Z9oYWLf_.js +586 -0
- package/dist/openapi-generator-Z9oYWLf_.js.map +1 -0
- package/dist/schema-editor-routes-D3ef8zu1.js +219 -0
- package/dist/schema-editor-routes-D3ef8zu1.js.map +1 -0
- package/dist/serve-spa.d.ts +36 -0
- package/dist/services/driver-registry.d.ts +78 -0
- package/dist/services/routed-realtime-service.d.ts +43 -0
- package/dist/services/webhook-service.d.ts +29 -0
- package/dist/singleton.d.ts +51 -0
- package/dist/src-B4OLmNVa.js +437 -0
- package/dist/src-B4OLmNVa.js.map +1 -0
- package/dist/src-B5WkP1Rv.js +24778 -0
- package/dist/src-B5WkP1Rv.js.map +1 -0
- package/dist/storage/GCSStorageController.d.ts +43 -0
- package/dist/storage/LocalStorageController.d.ts +46 -0
- package/dist/storage/S3StorageController.d.ts +40 -0
- package/dist/storage/image-transform.d.ts +50 -0
- package/{src/storage/index.ts → dist/storage/index.d.ts} +6 -21
- package/dist/storage/routes.d.ts +70 -0
- package/dist/storage/storage-registry.d.ts +78 -0
- package/dist/storage/tus-handler.d.ts +53 -0
- package/{src/storage/types.ts → dist/storage/types.d.ts} +32 -38
- package/dist/types/index.d.ts +11 -0
- package/dist/utils/dev-port.d.ts +45 -0
- package/dist/utils/dynamic-import.d.ts +25 -0
- package/dist/utils/logger.d.ts +31 -0
- package/dist/utils/logging.d.ts +9 -0
- package/dist/utils/request-id.d.ts +4 -0
- package/dist/utils/request-logger.d.ts +19 -0
- package/{src/utils/sql.ts → dist/utils/sql.d.ts} +5 -16
- package/package.json +49 -22
- package/coverage/clover.xml +0 -3739
- package/coverage/coverage-final.json +0 -31
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -266
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov-report/src/api/ast-schema-editor.ts.html +0 -952
- package/coverage/lcov-report/src/api/errors.ts.html +0 -472
- package/coverage/lcov-report/src/api/graphql/graphql-schema-generator.ts.html +0 -1069
- package/coverage/lcov-report/src/api/graphql/index.html +0 -116
- package/coverage/lcov-report/src/api/index.html +0 -176
- package/coverage/lcov-report/src/api/openapi-generator.ts.html +0 -565
- package/coverage/lcov-report/src/api/rest/api-generator.ts.html +0 -994
- package/coverage/lcov-report/src/api/rest/index.html +0 -131
- package/coverage/lcov-report/src/api/rest/query-parser.ts.html +0 -550
- package/coverage/lcov-report/src/api/schema-editor-routes.ts.html +0 -202
- package/coverage/lcov-report/src/api/server.ts.html +0 -823
- package/coverage/lcov-report/src/auth/admin-routes.ts.html +0 -973
- package/coverage/lcov-report/src/auth/index.html +0 -176
- package/coverage/lcov-report/src/auth/jwt.ts.html +0 -574
- package/coverage/lcov-report/src/auth/middleware.ts.html +0 -745
- package/coverage/lcov-report/src/auth/password.ts.html +0 -310
- package/coverage/lcov-report/src/auth/services.ts.html +0 -2074
- package/coverage/lcov-report/src/collections/index.html +0 -116
- package/coverage/lcov-report/src/collections/loader.ts.html +0 -232
- package/coverage/lcov-report/src/db/auth-schema.ts.html +0 -523
- package/coverage/lcov-report/src/db/data-transformer.ts.html +0 -1753
- package/coverage/lcov-report/src/db/entityService.ts.html +0 -700
- package/coverage/lcov-report/src/db/index.html +0 -146
- package/coverage/lcov-report/src/db/services/EntityFetchService.ts.html +0 -4048
- package/coverage/lcov-report/src/db/services/EntityPersistService.ts.html +0 -883
- package/coverage/lcov-report/src/db/services/RelationService.ts.html +0 -3121
- package/coverage/lcov-report/src/db/services/entity-helpers.ts.html +0 -442
- package/coverage/lcov-report/src/db/services/index.html +0 -176
- package/coverage/lcov-report/src/db/services/index.ts.html +0 -124
- package/coverage/lcov-report/src/generate-drizzle-schema-logic.ts.html +0 -1960
- package/coverage/lcov-report/src/index.html +0 -116
- package/coverage/lcov-report/src/services/driver-registry.ts.html +0 -631
- package/coverage/lcov-report/src/services/index.html +0 -131
- package/coverage/lcov-report/src/services/postgresDataDriver.ts.html +0 -3025
- package/coverage/lcov-report/src/storage/LocalStorageController.ts.html +0 -1189
- package/coverage/lcov-report/src/storage/S3StorageController.ts.html +0 -970
- package/coverage/lcov-report/src/storage/index.html +0 -161
- package/coverage/lcov-report/src/storage/storage-registry.ts.html +0 -646
- package/coverage/lcov-report/src/storage/types.ts.html +0 -451
- package/coverage/lcov-report/src/utils/drizzle-conditions.ts.html +0 -3082
- package/coverage/lcov-report/src/utils/index.html +0 -116
- package/coverage/lcov.info +0 -7179
- package/jest.config.cjs +0 -19
- package/src/api/ast-schema-editor.ts +0 -289
- package/src/api/collections_for_test/callbacks_test_collection.ts +0 -61
- package/src/api/errors.ts +0 -278
- package/src/api/graphql/graphql-schema-generator.ts +0 -383
- package/src/api/graphql/index.ts +0 -2
- package/src/api/index.ts +0 -11
- package/src/api/logs-routes.ts +0 -132
- package/src/api/openapi-generator.ts +0 -727
- package/src/api/rest/api-generator-count.test.ts +0 -126
- package/src/api/rest/api-generator.ts +0 -747
- package/src/api/rest/query-parser.ts +0 -300
- package/src/api/schema-editor-routes.ts +0 -41
- package/src/api/server.ts +0 -271
- package/src/api/types.ts +0 -104
- package/src/auth/adapter-middleware.ts +0 -114
- package/src/auth/admin-roles-route.ts +0 -36
- package/src/auth/admin-user-ops.ts +0 -236
- package/src/auth/admin-users-route.ts +0 -302
- package/src/auth/api-keys/api-key-middleware.ts +0 -133
- package/src/auth/api-keys/api-key-routes.ts +0 -193
- package/src/auth/api-keys/api-key-store.ts +0 -340
- package/src/auth/apple-oauth.ts +0 -122
- package/src/auth/bitbucket-oauth.ts +0 -84
- package/src/auth/builtin-auth-adapter.ts +0 -380
- package/src/auth/custom-auth-adapter.ts +0 -86
- package/src/auth/discord-oauth.ts +0 -85
- package/src/auth/facebook-oauth.ts +0 -74
- package/src/auth/github-oauth.ts +0 -112
- package/src/auth/gitlab-oauth.ts +0 -72
- package/src/auth/google-oauth.ts +0 -223
- package/src/auth/jwt.ts +0 -208
- package/src/auth/linkedin-oauth.ts +0 -83
- package/src/auth/magic-link-routes.ts +0 -167
- package/src/auth/mfa-crypto.ts +0 -91
- package/src/auth/mfa-routes.ts +0 -323
- package/src/auth/mfa.ts +0 -161
- package/src/auth/microsoft-oauth.ts +0 -90
- package/src/auth/middleware.ts +0 -369
- package/src/auth/password.ts +0 -77
- package/src/auth/rate-limiter.ts +0 -225
- package/src/auth/reset-password-admin.ts +0 -160
- package/src/auth/rls-scope.ts +0 -58
- package/src/auth/routes.ts +0 -802
- package/src/auth/session-routes.ts +0 -353
- package/src/auth/slack-oauth.ts +0 -73
- package/src/auth/spotify-oauth.ts +0 -69
- package/src/auth/twitter-oauth.ts +0 -128
- package/src/collections/BackendCollectionRegistry.ts +0 -20
- package/src/collections/loader.ts +0 -50
- package/src/cron/cron-loader.ts +0 -84
- package/src/cron/cron-routes.test.ts +0 -265
- package/src/cron/cron-routes.ts +0 -85
- package/src/cron/cron-scheduler.test.ts +0 -559
- package/src/cron/cron-scheduler.ts +0 -581
- package/src/cron/cron-store.ts +0 -166
- package/src/db/interfaces.ts +0 -60
- package/src/email/index.ts +0 -19
- package/src/email/smtp-email-service.ts +0 -123
- package/src/email/templates.ts +0 -470
- package/src/env.ts +0 -227
- package/src/functions/function-loader.ts +0 -116
- package/src/functions/function-routes.ts +0 -31
- package/src/history/history-routes.ts +0 -129
- package/src/index.ts +0 -70
- package/src/init/docs.ts +0 -47
- package/src/init/health.ts +0 -37
- package/src/init/middlewares.ts +0 -61
- package/src/init/shutdown.ts +0 -56
- package/src/init/storage.ts +0 -57
- package/src/init.ts +0 -1041
- package/src/serve-spa.ts +0 -89
- package/src/services/driver-registry.ts +0 -183
- package/src/services/routed-realtime-service.ts +0 -113
- package/src/services/webhook-service.ts +0 -155
- package/src/singleton.ts +0 -70
- package/src/storage/LocalStorageController.ts +0 -389
- package/src/storage/S3StorageController.ts +0 -300
- package/src/storage/image-transform.ts +0 -218
- package/src/storage/routes.ts +0 -368
- package/src/storage/storage-registry.ts +0 -188
- package/src/storage/tus-handler.ts +0 -315
- package/src/types/index.ts +0 -27
- package/src/utils/dev-port.ts +0 -196
- package/src/utils/logger.ts +0 -143
- package/src/utils/logging.ts +0 -40
- package/src/utils/request-id.ts +0 -40
- package/src/utils/request-logger.ts +0 -72
- package/test/api-generator.test.ts +0 -588
- package/test/api-key-permission-guard.test.ts +0 -144
- package/test/ast-schema-editor.test.ts +0 -89
- package/test/auth-middleware-hono.test.ts +0 -556
- package/test/auth-routes.test.ts +0 -1098
- package/test/backend-hooks-data.test.ts +0 -477
- package/test/custom-auth-adapter.test.ts +0 -189
- package/test/driver-registry.test.ts +0 -282
- package/test/email-templates.test.ts +0 -174
- package/test/env.test.ts +0 -128
- package/test/error-propagation.test.ts +0 -226
- package/test/errors-hono.test.ts +0 -133
- package/test/errors.test.ts +0 -155
- package/test/function-loader.test.ts +0 -127
- package/test/graphql-schema-generator.test.ts +0 -554
- package/test/jwt-security.test.ts +0 -182
- package/test/jwt.test.ts +0 -326
- package/test/mfa.test.ts +0 -197
- package/test/middleware.test.ts +0 -305
- package/test/multi-datasource-routing.test.ts +0 -113
- package/test/password.test.ts +0 -165
- package/test/query-parser.test.ts +0 -233
- package/test/rate-limiter.test.ts +0 -102
- package/test/reset-password-admin.test.ts +0 -113
- package/test/routed-realtime-service.test.ts +0 -86
- package/test/safe-compare.test.ts +0 -66
- package/test/singleton.test.ts +0 -59
- package/test/smtp-email-service.test.ts +0 -169
- package/test/storage-local.test.ts +0 -271
- package/test/storage-registry.test.ts +0 -282
- package/test/storage-routes.test.ts +0 -222
- package/test/storage-s3.test.ts +0 -304
- package/test/transform-auth-response.test.ts +0 -305
- package/test/webhook-service.test.ts +0 -260
- package/tsconfig.json +0 -54
- package/tsconfig.prod.json +0 -23
- package/vite.config.ts +0 -86
package/src/auth/rate-limiter.ts
DELETED
|
@@ -1,225 +0,0 @@
|
|
|
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
|
-
const forwardedFor = c.req.header("x-forwarded-for");
|
|
105
|
-
if (forwardedFor) {
|
|
106
|
-
const ips = forwardedFor.split(",");
|
|
107
|
-
// The leftmost IP can be easily spoofed by the client in the initial request.
|
|
108
|
-
// Reverse proxies append to the right. We take the rightmost IP as the most
|
|
109
|
-
// reliable indicator of the true client IP (the one closest to our server).
|
|
110
|
-
return ips[ips.length - 1].trim();
|
|
111
|
-
}
|
|
112
|
-
return c.req.header("x-real-ip") || "unknown";
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Pre-configured rate limiter for general auth endpoints (login, register).
|
|
117
|
-
* 200 requests per 15 minutes per IP.
|
|
118
|
-
*/
|
|
119
|
-
export const defaultAuthLimiter = createRateLimiter({
|
|
120
|
-
windowMs: 15 * 60 * 1000,
|
|
121
|
-
limit: 200,
|
|
122
|
-
message: "Too many authentication attempts, please try again later."
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Pre-configured strict rate limiter for sensitive endpoints (password reset, verification).
|
|
127
|
-
* 50 requests per 15 minutes per IP.
|
|
128
|
-
*/
|
|
129
|
-
export const strictAuthLimiter = createRateLimiter({
|
|
130
|
-
windowMs: 15 * 60 * 1000,
|
|
131
|
-
limit: 50,
|
|
132
|
-
message: "Too many requests to this sensitive endpoint, please try again later."
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Key generator for API-key-based rate limiting.
|
|
137
|
-
*
|
|
138
|
-
* Uses the API key ID (from `c.get("apiKey")`) as the rate limit key.
|
|
139
|
-
* Falls back to IP-based keying when the request is not authenticated
|
|
140
|
-
* via an API key.
|
|
141
|
-
*/
|
|
142
|
-
export function apiKeyKeyGenerator(c: Parameters<MiddlewareHandler<HonoEnv>>[0]): string {
|
|
143
|
-
const apiKey = c.get("apiKey") as { id: string } | undefined;
|
|
144
|
-
if (apiKey) {
|
|
145
|
-
return `api-key:${apiKey.id}`;
|
|
146
|
-
}
|
|
147
|
-
return defaultKeyGenerator(c);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Create a rate limiter specifically for API key requests.
|
|
152
|
-
*
|
|
153
|
-
* When a request is authenticated via an API key that has a `rate_limit`
|
|
154
|
-
* configured, this limiter enforces per-key limits using the key's ID
|
|
155
|
-
* as the rate limit bucket.
|
|
156
|
-
*
|
|
157
|
-
* @param defaultLimit - Fallback limit when the key has no `rate_limit` set.
|
|
158
|
-
* @param windowMs - Time window in milliseconds (default: 15 minutes).
|
|
159
|
-
*/
|
|
160
|
-
export function createApiKeyRateLimiter(
|
|
161
|
-
defaultLimit = 1000,
|
|
162
|
-
windowMs = 15 * 60 * 1000
|
|
163
|
-
): MiddlewareHandler<HonoEnv> {
|
|
164
|
-
// We maintain a single shared store keyed by API key ID.
|
|
165
|
-
// The actual limit is resolved per-request from the key's metadata.
|
|
166
|
-
const store = new Map<string, { timestamps: number[] }>();
|
|
167
|
-
|
|
168
|
-
const cleanupInterval = setInterval(() => {
|
|
169
|
-
const now = Date.now();
|
|
170
|
-
for (const [key, entry] of store.entries()) {
|
|
171
|
-
entry.timestamps = entry.timestamps.filter(t => now - t < windowMs);
|
|
172
|
-
if (entry.timestamps.length === 0) {
|
|
173
|
-
store.delete(key);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}, windowMs);
|
|
177
|
-
|
|
178
|
-
if (cleanupInterval.unref) {
|
|
179
|
-
cleanupInterval.unref();
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return async (c, next) => {
|
|
183
|
-
const apiKey = c.get("apiKey") as { id: string; rate_limit: number | null } | undefined;
|
|
184
|
-
if (!apiKey) {
|
|
185
|
-
// Not an API key request — skip this limiter
|
|
186
|
-
return next();
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const limit = apiKey.rate_limit ?? defaultLimit;
|
|
190
|
-
const key = `api-key:${apiKey.id}`;
|
|
191
|
-
const now = Date.now();
|
|
192
|
-
|
|
193
|
-
let entry = store.get(key);
|
|
194
|
-
if (!entry) {
|
|
195
|
-
entry = { timestamps: [] };
|
|
196
|
-
store.set(key, entry);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
entry.timestamps = entry.timestamps.filter(t => now - t < windowMs);
|
|
200
|
-
|
|
201
|
-
if (entry.timestamps.length >= limit) {
|
|
202
|
-
const retryAfterMs = entry.timestamps[0] + windowMs - now;
|
|
203
|
-
const retryAfterSec = Math.ceil(retryAfterMs / 1000);
|
|
204
|
-
|
|
205
|
-
c.header("Retry-After", String(retryAfterSec));
|
|
206
|
-
c.header("X-RateLimit-Limit", String(limit));
|
|
207
|
-
c.header("X-RateLimit-Remaining", "0");
|
|
208
|
-
c.header("X-RateLimit-Reset", String(Math.ceil((now + retryAfterMs) / 1000)));
|
|
209
|
-
|
|
210
|
-
return c.json({
|
|
211
|
-
error: {
|
|
212
|
-
message: "API key rate limit exceeded, please try again later.",
|
|
213
|
-
code: "RATE_LIMITED"
|
|
214
|
-
}
|
|
215
|
-
}, 429);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
entry.timestamps.push(now);
|
|
219
|
-
|
|
220
|
-
c.header("X-RateLimit-Limit", String(limit));
|
|
221
|
-
c.header("X-RateLimit-Remaining", String(limit - entry.timestamps.length));
|
|
222
|
-
|
|
223
|
-
return next();
|
|
224
|
-
};
|
|
225
|
-
}
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Standalone admin endpoint for resetting a user's password.
|
|
3
|
-
*
|
|
4
|
-
* Hook resolution order:
|
|
5
|
-
* 1. Collection-level hook (`auth.onResetPassword` on the collection)
|
|
6
|
-
* 2. Backend-level hook (`AuthHooks.onAdminResetPassword`)
|
|
7
|
-
* 3. Built-in default (send reset email, or generate temp password)
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { Hono } from "hono";
|
|
11
|
-
import { ApiError, errorHandler } from "../api/errors";
|
|
12
|
-
import type { AuthRepository } from "./interfaces";
|
|
13
|
-
import { createRequireAuth, requireAdmin } from "./middleware";
|
|
14
|
-
import type { AuthHooks } from "./auth-hooks";
|
|
15
|
-
import { resolveAuthHooks } from "./auth-hooks";
|
|
16
|
-
import { generateSecurePassword, generateSecureToken, hashToken } from "./admin-user-ops";
|
|
17
|
-
import { getPasswordResetTemplate } from "../email/templates";
|
|
18
|
-
import type { EmailService, EmailConfig } from "../email";
|
|
19
|
-
import type { HonoEnv } from "../api/types";
|
|
20
|
-
import type { AuthCollectionConfig } from "@rebasepro/types";
|
|
21
|
-
import { logger } from "../utils/logger";
|
|
22
|
-
|
|
23
|
-
export interface ResetPasswordRouteConfig {
|
|
24
|
-
authRepo: AuthRepository;
|
|
25
|
-
emailService?: EmailService;
|
|
26
|
-
emailConfig?: EmailConfig;
|
|
27
|
-
serviceKey?: string;
|
|
28
|
-
authHooks?: AuthHooks;
|
|
29
|
-
/** The parsed auth config from the collection, if available. */
|
|
30
|
-
collectionAuthConfig?: AuthCollectionConfig;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Create a standalone admin route for resetting user passwords.
|
|
35
|
-
*
|
|
36
|
-
* Mounts: POST /users/:userId/reset-password
|
|
37
|
-
*/
|
|
38
|
-
export function createResetPasswordRoute(config: ResetPasswordRouteConfig): Hono<HonoEnv> {
|
|
39
|
-
const router = new Hono<HonoEnv>();
|
|
40
|
-
const authRepo = config.authRepo;
|
|
41
|
-
const { emailService, emailConfig, collectionAuthConfig } = config;
|
|
42
|
-
const ops = resolveAuthHooks(config.authHooks);
|
|
43
|
-
|
|
44
|
-
router.onError(errorHandler);
|
|
45
|
-
router.use("/*", createRequireAuth({ serviceKey: config.serviceKey }));
|
|
46
|
-
|
|
47
|
-
router.post("/users/:userId/reset-password", requireAdmin, async (c) => {
|
|
48
|
-
const userId = c.req.param("userId");
|
|
49
|
-
const existing = await authRepo.getUserById(userId);
|
|
50
|
-
if (!existing) {
|
|
51
|
-
throw ApiError.notFound("User not found");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
let invitationSent = false;
|
|
55
|
-
let temporaryPassword: string | undefined;
|
|
56
|
-
|
|
57
|
-
// Parse optional body — if a password is provided, set it directly
|
|
58
|
-
const body = await c.req.json().catch(() => ({}));
|
|
59
|
-
|
|
60
|
-
if (body.password) {
|
|
61
|
-
const password = body.password as string;
|
|
62
|
-
const validation = ops.validatePasswordStrength(password);
|
|
63
|
-
if (!validation.valid) {
|
|
64
|
-
throw ApiError.badRequest(
|
|
65
|
-
`Password too weak: ${validation.errors.join(", ")}`
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
const passwordHash = await ops.hashPassword(password);
|
|
69
|
-
await authRepo.updatePassword(existing.id, passwordHash);
|
|
70
|
-
temporaryPassword = undefined;
|
|
71
|
-
invitationSent = false;
|
|
72
|
-
}
|
|
73
|
-
// 1. Collection-level hook (closest to the data)
|
|
74
|
-
else if (collectionAuthConfig?.onResetPassword) {
|
|
75
|
-
const isEmailConfigured = !!(emailService && emailService.isConfigured());
|
|
76
|
-
const hookResult = await collectionAuthConfig.onResetPassword(existing.id, {
|
|
77
|
-
hashPassword: (password: string) => ops.hashPassword(password),
|
|
78
|
-
sendEmail: isEmailConfigured
|
|
79
|
-
? (options) => emailService!.send(options)
|
|
80
|
-
: undefined,
|
|
81
|
-
emailConfigured: isEmailConfigured,
|
|
82
|
-
appName: emailConfig?.appName || "Rebase",
|
|
83
|
-
resetPasswordUrl: emailConfig?.resetPasswordUrl || ""
|
|
84
|
-
});
|
|
85
|
-
temporaryPassword = hookResult.temporaryPassword;
|
|
86
|
-
invitationSent = hookResult.invitationSent ?? false;
|
|
87
|
-
}
|
|
88
|
-
// 2. Backend-level hook (global override)
|
|
89
|
-
else if (ops.onAdminResetPassword) {
|
|
90
|
-
const hookResult = await ops.onAdminResetPassword(existing.id, {
|
|
91
|
-
authRepo,
|
|
92
|
-
emailService,
|
|
93
|
-
emailConfig
|
|
94
|
-
});
|
|
95
|
-
temporaryPassword = hookResult.temporaryPassword;
|
|
96
|
-
invitationSent = hookResult.invitationSent ?? false;
|
|
97
|
-
}
|
|
98
|
-
// 3. Built-in default
|
|
99
|
-
else {
|
|
100
|
-
const isEmailConfigured = !!(emailService && emailService.isConfigured());
|
|
101
|
-
|
|
102
|
-
if (isEmailConfigured) {
|
|
103
|
-
try {
|
|
104
|
-
const token = generateSecureToken();
|
|
105
|
-
const tokenHash = hashToken(token);
|
|
106
|
-
const expiresAt = new Date(Date.now() + 60 * 60 * 1000); // 1 hour
|
|
107
|
-
|
|
108
|
-
await authRepo.createPasswordResetToken(existing.id, tokenHash, expiresAt);
|
|
109
|
-
|
|
110
|
-
const baseUrl = emailConfig?.resetPasswordUrl || "";
|
|
111
|
-
const setPasswordUrl = `${baseUrl}/reset-password?token=${token}`;
|
|
112
|
-
|
|
113
|
-
const appName = emailConfig?.appName || "Rebase";
|
|
114
|
-
const templateFn = emailConfig?.templates?.passwordReset;
|
|
115
|
-
const emailContent = templateFn
|
|
116
|
-
? templateFn(setPasswordUrl, { email: existing.email,
|
|
117
|
-
displayName: existing.displayName })
|
|
118
|
-
: getPasswordResetTemplate(setPasswordUrl, { email: existing.email,
|
|
119
|
-
displayName: existing.displayName }, appName);
|
|
120
|
-
|
|
121
|
-
await emailService!.send({
|
|
122
|
-
to: existing.email,
|
|
123
|
-
subject: emailContent.subject,
|
|
124
|
-
html: emailContent.html,
|
|
125
|
-
text: emailContent.text
|
|
126
|
-
});
|
|
127
|
-
invitationSent = true;
|
|
128
|
-
} catch (emailError: unknown) {
|
|
129
|
-
logger.error("Failed to send reset email", { error: emailError instanceof Error ? emailError.message : emailError });
|
|
130
|
-
// Fall back to returning the temporary password
|
|
131
|
-
const clearPassword = generateSecurePassword();
|
|
132
|
-
const passwordHash = await ops.hashPassword(clearPassword);
|
|
133
|
-
await authRepo.updatePassword(existing.id, passwordHash);
|
|
134
|
-
temporaryPassword = clearPassword;
|
|
135
|
-
}
|
|
136
|
-
} else {
|
|
137
|
-
// No email service — generate password, set it, and return one-time
|
|
138
|
-
const clearPassword = generateSecurePassword();
|
|
139
|
-
const passwordHash = await ops.hashPassword(clearPassword);
|
|
140
|
-
await authRepo.updatePassword(existing.id, passwordHash);
|
|
141
|
-
temporaryPassword = clearPassword;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const userRoles = await authRepo.getUserRoleIds(existing.id);
|
|
146
|
-
|
|
147
|
-
return c.json({
|
|
148
|
-
user: {
|
|
149
|
-
uid: existing.id,
|
|
150
|
-
email: existing.email,
|
|
151
|
-
displayName: existing.displayName,
|
|
152
|
-
roles: userRoles
|
|
153
|
-
},
|
|
154
|
-
invitationSent,
|
|
155
|
-
...(temporaryPassword ? { temporaryPassword } : {})
|
|
156
|
-
}, 200);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
return router;
|
|
160
|
-
}
|
package/src/auth/rls-scope.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared RLS (Row-Level Security) scoping helper.
|
|
3
|
-
*
|
|
4
|
-
* DataDrivers may implement a `withAuth()` method that returns a scoped
|
|
5
|
-
* clone of the driver with RLS policies applied for the given user.
|
|
6
|
-
* This is database-specific (e.g. Postgres SET LOCAL ROLE) and is not
|
|
7
|
-
* part of the core DataDriver interface.
|
|
8
|
-
*
|
|
9
|
-
* This module provides the shared duck-typing logic used by the
|
|
10
|
-
* adapter-aware middleware.
|
|
11
|
-
*
|
|
12
|
-
* @module
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { DataDriver } from "@rebasepro/types";
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* A DataDriver that supports RLS scoping via `withAuth()`.
|
|
19
|
-
*
|
|
20
|
-
* This is not part of the public DataDriver interface because not all
|
|
21
|
-
* database implementations support RLS. Drivers that do (e.g. Postgres)
|
|
22
|
-
* extend DataDriver with this method.
|
|
23
|
-
*/
|
|
24
|
-
interface RLSScopedDriver extends DataDriver {
|
|
25
|
-
withAuth(user: { uid: string; roles?: string[] }): Promise<DataDriver>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Returns true if the driver supports RLS scoping via `withAuth()`.
|
|
30
|
-
*/
|
|
31
|
-
function isRLSScopedDriver(driver: DataDriver): driver is RLSScopedDriver {
|
|
32
|
-
return "withAuth" in driver && typeof (driver as Record<string, unknown>).withAuth === "function";
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Scope a DataDriver via `withAuth()` for RLS.
|
|
37
|
-
*
|
|
38
|
-
* SECURITY: If `withAuth()` is available but fails, the error is re-thrown
|
|
39
|
-
* so the request is **denied** rather than proceeding with unscoped access
|
|
40
|
-
* (fail-closed behavior).
|
|
41
|
-
*
|
|
42
|
-
* If the driver does not support RLS, the original driver is returned.
|
|
43
|
-
*
|
|
44
|
-
* @param driver - The DataDriver to scope.
|
|
45
|
-
* @param user - The authenticated user identity for RLS.
|
|
46
|
-
* @returns The RLS-scoped DataDriver (or the original if RLS is unsupported).
|
|
47
|
-
*/
|
|
48
|
-
export async function scopeDataDriver(
|
|
49
|
-
driver: DataDriver,
|
|
50
|
-
user: { uid: string; roles?: string[] }
|
|
51
|
-
): Promise<DataDriver> {
|
|
52
|
-
if (isRLSScopedDriver(driver)) {
|
|
53
|
-
// Fail closed — do NOT catch and swallow errors here.
|
|
54
|
-
// If RLS scoping fails the request must be rejected.
|
|
55
|
-
return await driver.withAuth(user);
|
|
56
|
-
}
|
|
57
|
-
return driver;
|
|
58
|
-
}
|