@rebasepro/server-core 0.2.3 → 0.2.4
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/dist/common/src/collections/default-collections.d.ts +12 -0
- package/dist/common/src/collections/index.d.ts +1 -0
- package/dist/common/src/util/permissions.d.ts +1 -0
- package/dist/{index-BZoAtuqi.js → index-Cr1D21av.js} +11 -3
- package/dist/index-Cr1D21av.js.map +1 -0
- package/dist/index.es.js +2166 -208
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +2155 -193
- package/dist/index.umd.js.map +1 -1
- package/dist/server-core/src/api/logs-routes.d.ts +37 -0
- package/dist/server-core/src/api/rest/api-generator.d.ts +6 -0
- package/dist/server-core/src/api/types.d.ts +6 -1
- package/dist/server-core/src/auth/adapter-middleware.d.ts +7 -3
- package/dist/server-core/src/auth/admin-routes.d.ts +3 -3
- package/dist/server-core/src/auth/api-keys/api-key-middleware.d.ts +39 -0
- package/dist/server-core/src/auth/api-keys/api-key-permission-guard.d.ts +32 -0
- package/dist/server-core/src/auth/api-keys/api-key-routes.d.ts +20 -0
- package/dist/server-core/src/auth/api-keys/api-key-store.d.ts +35 -0
- package/dist/server-core/src/auth/api-keys/api-key-types.d.ts +88 -0
- package/dist/server-core/src/auth/api-keys/index.d.ts +17 -0
- package/dist/server-core/src/auth/{auth-overrides.d.ts → auth-hooks.d.ts} +69 -11
- package/dist/server-core/src/auth/builtin-auth-adapter.d.ts +3 -3
- package/dist/server-core/src/auth/index.d.ts +5 -3
- package/dist/server-core/src/auth/interfaces.d.ts +93 -3
- package/dist/server-core/src/auth/jwt.d.ts +3 -1
- package/dist/server-core/src/auth/mfa.d.ts +49 -0
- package/dist/server-core/src/auth/middleware.d.ts +7 -0
- package/dist/server-core/src/auth/rate-limiter.d.ts +19 -0
- package/dist/server-core/src/auth/routes.d.ts +3 -3
- package/dist/server-core/src/env.d.ts +6 -0
- package/dist/server-core/src/index.d.ts +1 -0
- package/dist/server-core/src/init.d.ts +3 -3
- package/dist/server-core/src/services/webhook-service.d.ts +29 -0
- package/dist/server-core/src/storage/image-transform.d.ts +48 -0
- package/dist/server-core/src/storage/index.d.ts +3 -0
- package/dist/server-core/src/storage/tus-handler.d.ts +51 -0
- package/dist/types/src/controllers/auth.d.ts +2 -24
- package/dist/types/src/controllers/client.d.ts +0 -3
- package/dist/types/src/controllers/collection_registry.d.ts +1 -1
- package/dist/types/src/controllers/data_driver.d.ts +18 -0
- package/dist/types/src/controllers/registry.d.ts +5 -4
- package/dist/types/src/rebase_context.d.ts +1 -1
- package/dist/types/src/types/auth_adapter.d.ts +2 -4
- package/dist/types/src/types/collections.d.ts +0 -4
- package/dist/types/src/types/component_ref.d.ts +1 -1
- package/dist/types/src/types/cron.d.ts +1 -1
- package/dist/types/src/types/entity_views.d.ts +1 -0
- package/dist/types/src/types/export_import.d.ts +1 -1
- package/dist/types/src/types/formex.d.ts +2 -2
- package/dist/types/src/types/properties.d.ts +2 -2
- package/dist/types/src/types/translations.d.ts +28 -12
- package/dist/types/src/types/user_management_delegate.d.ts +6 -4
- package/dist/types/src/users/roles.d.ts +0 -8
- package/package.json +8 -6
- package/src/api/ast-schema-editor.ts +4 -4
- package/src/api/errors.ts +16 -7
- package/src/api/logs-routes.ts +129 -0
- package/src/api/rest/api-generator.ts +42 -2
- package/src/api/rest/query-parser.ts +37 -1
- package/src/api/types.ts +6 -1
- package/src/auth/adapter-middleware.ts +20 -4
- package/src/auth/admin-routes.ts +39 -14
- package/src/auth/api-keys/api-key-middleware.ts +126 -0
- package/src/auth/api-keys/api-key-permission-guard.ts +64 -0
- package/src/auth/api-keys/api-key-routes.ts +183 -0
- package/src/auth/api-keys/api-key-store.ts +317 -0
- package/src/auth/api-keys/api-key-types.ts +94 -0
- package/src/auth/api-keys/index.ts +37 -0
- package/src/auth/{auth-overrides.ts → auth-hooks.ts} +81 -14
- package/src/auth/builtin-auth-adapter.ts +31 -19
- package/src/auth/index.ts +7 -3
- package/src/auth/interfaces.ts +111 -3
- package/src/auth/jwt.ts +19 -5
- package/src/auth/mfa.ts +160 -0
- package/src/auth/middleware.ts +20 -1
- package/src/auth/rate-limiter.ts +92 -0
- package/src/auth/routes.ts +455 -24
- package/src/cron/cron-loader.ts +5 -10
- package/src/cron/cron-scheduler.ts +11 -12
- package/src/cron/cron-store.ts +8 -7
- package/src/env.ts +2 -0
- package/src/functions/function-loader.ts +6 -9
- package/src/index.ts +1 -2
- package/src/init.ts +37 -7
- package/src/serve-spa.ts +5 -4
- package/src/services/webhook-service.ts +155 -0
- package/src/storage/image-transform.ts +202 -0
- package/src/storage/index.ts +3 -0
- package/src/storage/routes.ts +56 -3
- package/src/storage/tus-handler.ts +315 -0
- package/src/utils/dev-port.ts +14 -0
- package/src/utils/logging.ts +9 -7
- package/test/admin-routes.test.ts +74 -7
- package/test/api-generator.test.ts +0 -1
- package/test/api-key-permission-guard.test.ts +132 -0
- package/test/ast-schema-editor.test.ts +26 -0
- package/test/auth-routes.test.ts +1 -2
- package/test/backend-hooks-admin.test.ts +3 -4
- package/test/email-templates.test.ts +169 -0
- package/test/function-loader.test.ts +124 -0
- package/test/jwt.test.ts +4 -2
- package/test/mfa.test.ts +197 -0
- package/test/middleware.test.ts +10 -5
- package/test/webhook-service.test.ts +249 -0
- package/vite.config.ts +3 -2
- package/dist/index-BZoAtuqi.js.map +0 -1
- package/dist/server-core/src/bootstrappers/index.d.ts +0 -0
- package/src/bootstrappers/index.ts +0 -1
- package/src/singleton.test.ts +0 -28
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import type { MiddlewareHandler } from "hono";
|
|
3
|
+
import type { HonoEnv } from "./types";
|
|
4
|
+
export interface LogEntry {
|
|
5
|
+
id: string;
|
|
6
|
+
timestamp: string;
|
|
7
|
+
level: "debug" | "info" | "warn" | "error";
|
|
8
|
+
source: "api" | "auth" | "storage" | "realtime" | "system";
|
|
9
|
+
message: string;
|
|
10
|
+
metadata?: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
declare class LogRingBuffer {
|
|
13
|
+
private buffer;
|
|
14
|
+
private maxSize;
|
|
15
|
+
private idCounter;
|
|
16
|
+
constructor(maxSize?: number);
|
|
17
|
+
push(entry: Omit<LogEntry, "id">): void;
|
|
18
|
+
query(options: {
|
|
19
|
+
level?: string;
|
|
20
|
+
source?: string;
|
|
21
|
+
search?: string;
|
|
22
|
+
limit?: number;
|
|
23
|
+
offset?: number;
|
|
24
|
+
since?: string;
|
|
25
|
+
}): {
|
|
26
|
+
entries: LogEntry[];
|
|
27
|
+
total: number;
|
|
28
|
+
};
|
|
29
|
+
getLatest(count?: number): LogEntry[];
|
|
30
|
+
}
|
|
31
|
+
export declare const logBuffer: LogRingBuffer;
|
|
32
|
+
/** Add a log entry */
|
|
33
|
+
export declare function addLog(level: LogEntry["level"], source: LogEntry["source"], message: string, metadata?: Record<string, unknown>): void;
|
|
34
|
+
/** Hono middleware to log API requests */
|
|
35
|
+
export declare function logMiddleware(): MiddlewareHandler<HonoEnv>;
|
|
36
|
+
declare const app: Hono<HonoEnv, import("hono/types").BlankSchema, "/">;
|
|
37
|
+
export default app;
|
|
@@ -17,6 +17,12 @@ export declare class RestApiGenerator {
|
|
|
17
17
|
* Generate REST routes using existing DataDriver
|
|
18
18
|
*/
|
|
19
19
|
generateRoutes(): Hono<HonoEnv>;
|
|
20
|
+
/**
|
|
21
|
+
* Check API key permissions for a collection operation.
|
|
22
|
+
* Throws 403 if the key doesn't have the required permission.
|
|
23
|
+
* No-ops if the request is not authenticated via an API key.
|
|
24
|
+
*/
|
|
25
|
+
private enforceApiKeyPermission;
|
|
20
26
|
/**
|
|
21
27
|
* Get the typed RestFetchService from a driver if it exposes one (for include support).
|
|
22
28
|
*/
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { EntityCollection } from "@rebasepro/types";
|
|
1
|
+
import { EntityCollection, VectorSearchParams } from "@rebasepro/types";
|
|
2
2
|
import { AuthResult } from "../auth/middleware";
|
|
3
3
|
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
4
4
|
import { DataDriver } from "@rebasepro/types";
|
|
5
|
+
import type { ApiKeyMasked } from "../auth/api-keys/api-key-types";
|
|
5
6
|
/**
|
|
6
7
|
* Hono Environment Variables
|
|
7
8
|
* Passed to generic Hono<HonoEnv> to type `c.get()`
|
|
@@ -13,6 +14,8 @@ export type HonoEnv = {
|
|
|
13
14
|
roles?: string[];
|
|
14
15
|
};
|
|
15
16
|
driver?: DataDriver;
|
|
17
|
+
/** Set when the request is authenticated via a Service API Key. */
|
|
18
|
+
apiKey?: ApiKeyMasked;
|
|
16
19
|
};
|
|
17
20
|
};
|
|
18
21
|
/**
|
|
@@ -79,6 +82,8 @@ export interface QueryOptions {
|
|
|
79
82
|
include?: string[];
|
|
80
83
|
/** Columns to return in the response (field-level selection) */
|
|
81
84
|
fields?: string[];
|
|
85
|
+
/** Vector similarity search configuration */
|
|
86
|
+
vectorSearch?: VectorSearchParams;
|
|
82
87
|
}
|
|
83
88
|
/**
|
|
84
89
|
* Relation resolution configuration
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
* an `AuthAdapter` to `initializeRebaseBackend()`.
|
|
7
7
|
*
|
|
8
8
|
* The middleware:
|
|
9
|
-
* 1.
|
|
10
|
-
* 2.
|
|
11
|
-
* 3.
|
|
9
|
+
* 1. Checks for API key tokens (`rk_` prefix) first — these are Rebase-level
|
|
10
|
+
* 2. Falls back to `adapter.verifyRequest(request)` to resolve the user
|
|
11
|
+
* 3. Scopes the DataDriver via `withAuth()` for RLS
|
|
12
|
+
* 4. Enforces auth (401) when `requireAuth` is true and no user is found
|
|
12
13
|
*
|
|
13
14
|
* The behavior is identical to `createAuthMiddleware()` — only the
|
|
14
15
|
* token verification strategy is pluggable.
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
import type { MiddlewareHandler } from "hono";
|
|
17
18
|
import type { DataDriver, AuthAdapter } from "@rebasepro/types";
|
|
18
19
|
import type { HonoEnv } from "../api/types";
|
|
20
|
+
import type { ApiKeyStore } from "./api-keys/api-key-store";
|
|
19
21
|
export interface AdapterAuthMiddlewareOptions {
|
|
20
22
|
/** The auth adapter to delegate verification to. */
|
|
21
23
|
adapter: AuthAdapter;
|
|
@@ -26,6 +28,8 @@ export interface AdapterAuthMiddlewareOptions {
|
|
|
26
28
|
* Defaults to `true` (secure by default).
|
|
27
29
|
*/
|
|
28
30
|
requireAuth?: boolean;
|
|
31
|
+
/** Optional API key store — when provided, `rk_` bearer tokens are accepted. */
|
|
32
|
+
apiKeyStore?: ApiKeyStore;
|
|
29
33
|
}
|
|
30
34
|
/**
|
|
31
35
|
* Create a Hono middleware that uses an `AuthAdapter` for request verification.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
-
import type {
|
|
2
|
+
import type { AuthHooks } from "./auth-hooks";
|
|
3
3
|
import { AuthModuleConfig } from "./routes";
|
|
4
4
|
import type { BackendHooks } from "@rebasepro/types";
|
|
5
5
|
interface AdminRouteOptions extends AuthModuleConfig {
|
|
@@ -14,10 +14,10 @@ interface AdminRouteOptions extends AuthModuleConfig {
|
|
|
14
14
|
*/
|
|
15
15
|
hooks?: BackendHooks;
|
|
16
16
|
/**
|
|
17
|
-
* Auth
|
|
17
|
+
* Auth hooks for customizing password hashing, credential
|
|
18
18
|
* verification, lifecycle hooks, etc.
|
|
19
19
|
*/
|
|
20
|
-
|
|
20
|
+
authHooks?: AuthHooks;
|
|
21
21
|
}
|
|
22
22
|
import { HonoEnv } from "../api/types";
|
|
23
23
|
/**
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hono middleware for authenticating requests via Service API Keys.
|
|
3
|
+
*
|
|
4
|
+
* This middleware is integrated into `createAuthMiddleware()` and
|
|
5
|
+
* activates only when the bearer token starts with `rk_`. It:
|
|
6
|
+
*
|
|
7
|
+
* 1. Hashes the token with SHA-256
|
|
8
|
+
* 2. Looks up the hash in the `rebase.api_keys` table
|
|
9
|
+
* 3. Validates the key is not revoked and not expired
|
|
10
|
+
* 4. Sets `c.set("user", ...)` and `c.set("apiKey", ...)` for downstream use
|
|
11
|
+
* 5. Scopes the DataDriver via `withAuth()` using the API key's service identity
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import type { Context } from "hono";
|
|
16
|
+
import type { DataDriver } from "@rebasepro/types";
|
|
17
|
+
import type { HonoEnv } from "../../api/types";
|
|
18
|
+
import type { ApiKeyStore } from "./api-key-store";
|
|
19
|
+
/**
|
|
20
|
+
* Check whether a token looks like a Rebase API key.
|
|
21
|
+
*/
|
|
22
|
+
export declare function isApiKeyToken(token: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Options for the API key authentication handler.
|
|
25
|
+
*/
|
|
26
|
+
export interface ApiKeyAuthOptions {
|
|
27
|
+
store: ApiKeyStore;
|
|
28
|
+
driver: DataDriver;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validate an API key token and populate the Hono context.
|
|
32
|
+
*
|
|
33
|
+
* Returns `true` if the key is valid and context has been populated,
|
|
34
|
+
* or returns an error Response if the key is invalid.
|
|
35
|
+
*
|
|
36
|
+
* This is NOT a standalone middleware — it's called from within
|
|
37
|
+
* `createAuthMiddleware()` when a `rk_` prefixed token is detected.
|
|
38
|
+
*/
|
|
39
|
+
export declare function validateApiKey(c: Context<HonoEnv>, token: string, options: ApiKeyAuthOptions): Promise<Response | true>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission guard for Service API Keys.
|
|
3
|
+
*
|
|
4
|
+
* Checks whether an API key's permission set allows a specific operation
|
|
5
|
+
* on a specific collection. Used by the REST API generator middleware to
|
|
6
|
+
* enforce fine-grained access control.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import type { ApiKeyPermission } from "./api-key-types";
|
|
11
|
+
/** Operations that map to HTTP methods. */
|
|
12
|
+
export type ApiKeyOperation = "read" | "write" | "delete";
|
|
13
|
+
/**
|
|
14
|
+
* Map an HTTP method string to an `ApiKeyOperation`.
|
|
15
|
+
*
|
|
16
|
+
* - `GET`, `HEAD`, `OPTIONS` → `"read"`
|
|
17
|
+
* - `POST`, `PUT`, `PATCH` → `"write"`
|
|
18
|
+
* - `DELETE` → `"delete"`
|
|
19
|
+
*/
|
|
20
|
+
export declare function httpMethodToOperation(method: string): ApiKeyOperation;
|
|
21
|
+
/**
|
|
22
|
+
* Check whether the given permissions array allows `operation` on `collection`.
|
|
23
|
+
*
|
|
24
|
+
* Supports the `"*"` wildcard for the collection field, which matches any
|
|
25
|
+
* collection. Returns `true` if at least one permission entry grants access.
|
|
26
|
+
*
|
|
27
|
+
* @param permissions - The API key's permission entries.
|
|
28
|
+
* @param collection - The target collection slug.
|
|
29
|
+
* @param operation - The requested operation.
|
|
30
|
+
* @returns `true` if the operation is permitted.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isOperationAllowed(permissions: ApiKeyPermission[], collection: string, operation: ApiKeyOperation): boolean;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin routes for managing Service API Keys.
|
|
3
|
+
*
|
|
4
|
+
* Mounted under `/api/admin/api-keys` with `requireAuth` + `requireAdmin`.
|
|
5
|
+
* All routes return masked keys (never the hash). The full plaintext key
|
|
6
|
+
* is returned exactly once in the POST response.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { Hono } from "hono";
|
|
11
|
+
import type { HonoEnv } from "../../api/types";
|
|
12
|
+
import type { ApiKeyStore } from "./api-key-store";
|
|
13
|
+
export interface ApiKeyRouteOptions {
|
|
14
|
+
store: ApiKeyStore;
|
|
15
|
+
serviceKey?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Create admin routes for API key management.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createApiKeyRoutes(options: ApiKeyRouteOptions): Hono<HonoEnv>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database operations for Service API Keys.
|
|
3
|
+
*
|
|
4
|
+
* Uses the DataDriver's `admin.executeSql` capability (same pattern as
|
|
5
|
+
* the cron-store and ensure-tables modules). All data lives in the
|
|
6
|
+
* `rebase.api_keys` table.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import type { DataDriver } from "@rebasepro/types";
|
|
11
|
+
import type { ApiKey, ApiKeyMasked, ApiKeyWithSecret, CreateApiKeyRequest, UpdateApiKeyRequest } from "./api-key-types";
|
|
12
|
+
export interface ApiKeyStore {
|
|
13
|
+
/** Ensure the `rebase.api_keys` table exists. Called once on startup. */
|
|
14
|
+
ensureTable(): Promise<void>;
|
|
15
|
+
/** Create a new API key. Returns the full plaintext key exactly once. */
|
|
16
|
+
createApiKey(request: CreateApiKeyRequest, createdBy: string): Promise<ApiKeyWithSecret>;
|
|
17
|
+
/** Look up an API key by its SHA-256 hash. Returns `null` if not found. */
|
|
18
|
+
findByKeyHash(hash: string): Promise<ApiKey | null>;
|
|
19
|
+
/** List all API keys (masked, never includes hash). */
|
|
20
|
+
listApiKeys(): Promise<ApiKeyMasked[]>;
|
|
21
|
+
/** Get a single API key by ID (masked). */
|
|
22
|
+
getApiKeyById(id: string): Promise<ApiKeyMasked | null>;
|
|
23
|
+
/** Update name, permissions, rate_limit, or expires_at. */
|
|
24
|
+
updateApiKey(id: string, updates: UpdateApiKeyRequest): Promise<ApiKeyMasked | null>;
|
|
25
|
+
/** Soft-delete: set `revoked_at` to now. */
|
|
26
|
+
revokeApiKey(id: string): Promise<boolean>;
|
|
27
|
+
/** Touch `last_used_at` to the current timestamp. */
|
|
28
|
+
updateLastUsed(id: string): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Create an `ApiKeyStore` backed by the driver's SQL admin capability.
|
|
32
|
+
*
|
|
33
|
+
* Returns `undefined` if the driver does not support `executeSql`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createApiKeyStore(driver: DataDriver): ApiKeyStore | undefined;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for Service API Keys.
|
|
3
|
+
*
|
|
4
|
+
* API keys provide machine-to-machine authentication for scripts, cron jobs,
|
|
5
|
+
* and third-party integrations. Each key is scoped to specific collections
|
|
6
|
+
* and operations via the `ApiKeyPermission` model.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* A single permission entry scoping an API key to a collection and set of operations.
|
|
12
|
+
*
|
|
13
|
+
* Use `"*"` as the collection value to grant access to all collections.
|
|
14
|
+
*/
|
|
15
|
+
export interface ApiKeyPermission {
|
|
16
|
+
/** Collection slug, or `"*"` for all collections. */
|
|
17
|
+
collection: string;
|
|
18
|
+
/** Allowed operations on the collection. */
|
|
19
|
+
operations: ("read" | "write" | "delete")[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Full database row for an API key.
|
|
23
|
+
* The `key_hash` is never exposed via the API — only stored for lookup.
|
|
24
|
+
*/
|
|
25
|
+
export interface ApiKey {
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
/** First 12 characters of the plaintext key, for display only. */
|
|
29
|
+
key_prefix: string;
|
|
30
|
+
/** SHA-256 hash of the full plaintext key. */
|
|
31
|
+
key_hash: string;
|
|
32
|
+
permissions: ApiKeyPermission[];
|
|
33
|
+
/** Requests per 15-minute window. `null` means unlimited. */
|
|
34
|
+
rate_limit: number | null;
|
|
35
|
+
created_by: string;
|
|
36
|
+
created_at: string;
|
|
37
|
+
updated_at: string;
|
|
38
|
+
last_used_at: string | null;
|
|
39
|
+
expires_at: string | null;
|
|
40
|
+
revoked_at: string | null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Masked version of an API key, safe for API responses.
|
|
44
|
+
* Omits `key_hash` and shows only the prefix.
|
|
45
|
+
*/
|
|
46
|
+
export interface ApiKeyMasked {
|
|
47
|
+
id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
key_prefix: string;
|
|
50
|
+
permissions: ApiKeyPermission[];
|
|
51
|
+
rate_limit: number | null;
|
|
52
|
+
created_by: string;
|
|
53
|
+
created_at: string;
|
|
54
|
+
updated_at: string;
|
|
55
|
+
last_used_at: string | null;
|
|
56
|
+
expires_at: string | null;
|
|
57
|
+
revoked_at: string | null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Request body for creating a new API key.
|
|
61
|
+
*/
|
|
62
|
+
export interface CreateApiKeyRequest {
|
|
63
|
+
name: string;
|
|
64
|
+
permissions: ApiKeyPermission[];
|
|
65
|
+
/** Requests per 15-minute window. Omit or `null` for unlimited. */
|
|
66
|
+
rate_limit?: number | null;
|
|
67
|
+
/** ISO-8601 expiration timestamp. Omit for no expiration. */
|
|
68
|
+
expires_at?: string | null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Request body for updating an existing API key.
|
|
72
|
+
* All fields are optional — only provided fields are updated.
|
|
73
|
+
*/
|
|
74
|
+
export interface UpdateApiKeyRequest {
|
|
75
|
+
name?: string;
|
|
76
|
+
permissions?: ApiKeyPermission[];
|
|
77
|
+
rate_limit?: number | null;
|
|
78
|
+
expires_at?: string | null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Returned exactly once when a key is created.
|
|
82
|
+
* The `key` field contains the full plaintext key — it is never stored
|
|
83
|
+
* or returned again after creation.
|
|
84
|
+
*/
|
|
85
|
+
export interface ApiKeyWithSecret extends ApiKeyMasked {
|
|
86
|
+
/** Full plaintext API key (e.g. `rk_live_abc123...`). */
|
|
87
|
+
key: string;
|
|
88
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service API Keys module.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports types, store, middleware, permission guard, and routes
|
|
5
|
+
* for the API key authentication system.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
export type { ApiKey, ApiKeyMasked, ApiKeyPermission, ApiKeyWithSecret, CreateApiKeyRequest, UpdateApiKeyRequest, } from "./api-key-types";
|
|
10
|
+
export { createApiKeyStore } from "./api-key-store";
|
|
11
|
+
export type { ApiKeyStore } from "./api-key-store";
|
|
12
|
+
export { isApiKeyToken, validateApiKey } from "./api-key-middleware";
|
|
13
|
+
export type { ApiKeyAuthOptions } from "./api-key-middleware";
|
|
14
|
+
export { httpMethodToOperation, isOperationAllowed, } from "./api-key-permission-guard";
|
|
15
|
+
export type { ApiKeyOperation } from "./api-key-permission-guard";
|
|
16
|
+
export { createApiKeyRoutes } from "./api-key-routes";
|
|
17
|
+
export type { ApiKeyRouteOptions } from "./api-key-routes";
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* AuthHooks
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Customize specific behaviors of the built-in Rebase auth system.
|
|
5
5
|
*
|
|
6
6
|
* Each method replaces one piece of the default implementation.
|
|
7
7
|
* Unset methods fall through to the built-in defaults (scrypt passwords,
|
|
8
8
|
* standard validation rules, etc.).
|
|
9
9
|
*
|
|
10
|
-
* This interface is intentionally open for extension — new
|
|
10
|
+
* This interface is intentionally open for extension — new hooks
|
|
11
11
|
* can be added as optional methods without breaking existing configurations.
|
|
12
12
|
*
|
|
13
13
|
* @example bcrypt password support
|
|
14
14
|
* ```ts
|
|
15
15
|
* import bcrypt from "bcrypt";
|
|
16
16
|
*
|
|
17
|
-
* const
|
|
17
|
+
* const hooks: AuthHooks = {
|
|
18
18
|
* hashPassword: (pw) => bcrypt.hash(pw, 12),
|
|
19
19
|
* verifyPassword: (pw, hash) => bcrypt.compare(pw, hash),
|
|
20
20
|
* validatePasswordStrength: (pw) => ({
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
*
|
|
27
27
|
* @example Override the entire login credential check
|
|
28
28
|
* ```ts
|
|
29
|
-
* const
|
|
29
|
+
* const hooks: AuthHooks = {
|
|
30
30
|
* verifyCredentials: async (email, password, repo) => {
|
|
31
31
|
* const user = await repo.getUserByEmail(email);
|
|
32
32
|
* if (!user || !user.passwordHash) return null;
|
|
@@ -41,14 +41,14 @@ import type { AuthRepository, UserData, CreateUserData } from "./interfaces";
|
|
|
41
41
|
/**
|
|
42
42
|
* Authentication method identifier for lifecycle hooks.
|
|
43
43
|
*/
|
|
44
|
-
export type AuthMethod = "login" | "register" | "oauth" | "refresh" | "password-reset";
|
|
44
|
+
export type AuthMethod = "login" | "register" | "oauth" | "refresh" | "password-reset" | "anonymous" | "magic-link" | "mfa";
|
|
45
45
|
/**
|
|
46
|
-
*
|
|
46
|
+
* Hook specific parts of the built-in Rebase auth implementation.
|
|
47
47
|
*
|
|
48
48
|
* Every method is optional. The built-in defaults apply for any method
|
|
49
49
|
* that is not provided.
|
|
50
50
|
*/
|
|
51
|
-
export interface
|
|
51
|
+
export interface AuthHooks {
|
|
52
52
|
/**
|
|
53
53
|
* Hash a cleartext password for storage.
|
|
54
54
|
*
|
|
@@ -118,10 +118,68 @@ export interface AuthOverrides {
|
|
|
118
118
|
* This is fire-and-forget — errors are logged but do not fail the request.
|
|
119
119
|
*/
|
|
120
120
|
afterUserCreate?(user: UserData): Promise<void>;
|
|
121
|
+
/**
|
|
122
|
+
* Pre-login validation. Called before credential verification.
|
|
123
|
+
*
|
|
124
|
+
* Throw an error to reject the login attempt (e.g. for account lockout,
|
|
125
|
+
* IP-based restrictions, etc.).
|
|
126
|
+
*/
|
|
127
|
+
beforeLogin?(email: string, method: AuthMethod): Promise<void>;
|
|
128
|
+
/**
|
|
129
|
+
* Post-logout cleanup.
|
|
130
|
+
*
|
|
131
|
+
* Called after a user's session has been invalidated.
|
|
132
|
+
* Use for audit logging, cleanup of temporary resources, etc.
|
|
133
|
+
*
|
|
134
|
+
* This is fire-and-forget — errors are logged but do not fail the request.
|
|
135
|
+
*/
|
|
136
|
+
afterLogout?(userId: string): Promise<void>;
|
|
137
|
+
/**
|
|
138
|
+
* Called after successful MFA verification.
|
|
139
|
+
*
|
|
140
|
+
* Use for audit logging, notifying external systems, etc.
|
|
141
|
+
*
|
|
142
|
+
* This is fire-and-forget — errors are logged but do not fail the request.
|
|
143
|
+
*/
|
|
144
|
+
onMfaVerified?(userId: string, factorId: string): Promise<void>;
|
|
145
|
+
/**
|
|
146
|
+
* Customize JWT access token claims before signing.
|
|
147
|
+
*
|
|
148
|
+
* Return the modified claims object. The returned claims are merged
|
|
149
|
+
* into the JWT payload alongside standard claims (userId, roles).
|
|
150
|
+
*
|
|
151
|
+
* @param claims - The default claims that would be included.
|
|
152
|
+
* @param user - The authenticated user data.
|
|
153
|
+
* @returns Modified claims to include in the JWT.
|
|
154
|
+
*/
|
|
155
|
+
customizeAccessToken?(claims: Record<string, unknown>, user: UserData): Promise<Record<string, unknown>>;
|
|
156
|
+
/**
|
|
157
|
+
* Called after a successful password reset.
|
|
158
|
+
*
|
|
159
|
+
* Use for audit logging, sending confirmation notifications, etc.
|
|
160
|
+
*
|
|
161
|
+
* This is fire-and-forget — errors are logged but do not fail the request.
|
|
162
|
+
*/
|
|
163
|
+
onPasswordReset?(userId: string): Promise<void>;
|
|
164
|
+
/**
|
|
165
|
+
* Called before a user is deleted.
|
|
166
|
+
*
|
|
167
|
+
* Throw an error to prevent deletion (e.g. for users with active
|
|
168
|
+
* subscriptions, pending transactions, etc.).
|
|
169
|
+
*/
|
|
170
|
+
beforeUserDelete?(userId: string): Promise<void>;
|
|
171
|
+
/**
|
|
172
|
+
* Called after a user is deleted.
|
|
173
|
+
*
|
|
174
|
+
* Use for cleaning up external resources, audit logging, etc.
|
|
175
|
+
*
|
|
176
|
+
* This is fire-and-forget — errors are logged but do not fail the request.
|
|
177
|
+
*/
|
|
178
|
+
afterUserDelete?(userId: string): Promise<void>;
|
|
121
179
|
}
|
|
122
180
|
/**
|
|
123
181
|
* Resolved auth operations — every method is guaranteed to exist.
|
|
124
|
-
* Created by `
|
|
182
|
+
* Created by `resolveAuthHooks()` which merges user hooks
|
|
125
183
|
* with built-in defaults.
|
|
126
184
|
*/
|
|
127
185
|
export interface ResolvedAuthOperations {
|
|
@@ -130,10 +188,10 @@ export interface ResolvedAuthOperations {
|
|
|
130
188
|
validatePasswordStrength(password: string): PasswordValidationResult;
|
|
131
189
|
}
|
|
132
190
|
/**
|
|
133
|
-
* Merge user-provided
|
|
191
|
+
* Merge user-provided hooks with the built-in defaults to produce
|
|
134
192
|
* a complete set of resolved operations.
|
|
135
193
|
*
|
|
136
194
|
* This is the single point where defaults are applied — all consumers
|
|
137
195
|
* call this once and use the resolved operations throughout.
|
|
138
196
|
*/
|
|
139
|
-
export declare function
|
|
197
|
+
export declare function resolveAuthHooks(hooks?: AuthHooks): ResolvedAuthOperations;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { AuthAdapter, BackendHooks } from "@rebasepro/types";
|
|
13
13
|
import type { AuthRepository, OAuthProvider } from "./interfaces";
|
|
14
|
-
import type {
|
|
14
|
+
import type { AuthHooks } from "./auth-hooks";
|
|
15
15
|
import type { EmailService, EmailConfig } from "../email";
|
|
16
16
|
/**
|
|
17
17
|
* Configuration for the built-in Rebase auth adapter.
|
|
@@ -36,8 +36,8 @@ export interface BuiltinAuthAdapterConfig {
|
|
|
36
36
|
serviceKey?: string;
|
|
37
37
|
/** Backend hooks for intercepting admin data. */
|
|
38
38
|
hooks?: BackendHooks;
|
|
39
|
-
/** Auth
|
|
40
|
-
|
|
39
|
+
/** Auth hooks for customizing password, credentials, lifecycle, etc. */
|
|
40
|
+
authHooks?: AuthHooks;
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
43
|
* Create the built-in Rebase auth adapter.
|
|
@@ -3,8 +3,8 @@ export { configureJwt, generateAccessToken, verifyAccessToken, generateRefreshTo
|
|
|
3
3
|
export type { JwtConfig, AccessTokenPayload } from "./jwt";
|
|
4
4
|
export { hashPassword, verifyPassword, validatePasswordStrength } from "./password";
|
|
5
5
|
export type { PasswordValidationResult } from "./password";
|
|
6
|
-
export type {
|
|
7
|
-
export {
|
|
6
|
+
export type { AuthHooks, AuthMethod, ResolvedAuthOperations } from "./auth-hooks";
|
|
7
|
+
export { resolveAuthHooks } from "./auth-hooks";
|
|
8
8
|
export { createGoogleProvider } from "./google-oauth";
|
|
9
9
|
export type { GoogleProviderConfig } from "./google-oauth";
|
|
10
10
|
export { createLinkedinProvider } from "./linkedin-oauth";
|
|
@@ -23,7 +23,9 @@ export type { AuthMiddlewareOptions, AuthResult } from "./middleware";
|
|
|
23
23
|
export { createAuthRoutes } from "./routes";
|
|
24
24
|
export type { AuthModuleConfig } from "./routes";
|
|
25
25
|
export { createAdminRoutes } from "./admin-routes";
|
|
26
|
-
export { createRateLimiter, defaultAuthLimiter, strictAuthLimiter } from "./rate-limiter";
|
|
26
|
+
export { createRateLimiter, defaultAuthLimiter, strictAuthLimiter, createApiKeyRateLimiter, apiKeyKeyGenerator } from "./rate-limiter";
|
|
27
|
+
export { createApiKeyStore, createApiKeyRoutes, isApiKeyToken, validateApiKey, httpMethodToOperation, isOperationAllowed } from "./api-keys";
|
|
28
|
+
export type { ApiKey, ApiKeyMasked, ApiKeyPermission, ApiKeyWithSecret, CreateApiKeyRequest, UpdateApiKeyRequest, ApiKeyStore, ApiKeyOperation } from "./api-keys";
|
|
27
29
|
export { createBuiltinAuthAdapter } from "./builtin-auth-adapter";
|
|
28
30
|
export type { BuiltinAuthAdapterConfig } from "./builtin-auth-adapter";
|
|
29
31
|
export { createCustomAuthAdapter } from "./custom-auth-adapter";
|
|
@@ -18,6 +18,7 @@ export interface UserData {
|
|
|
18
18
|
emailVerified: boolean;
|
|
19
19
|
emailVerificationToken?: string | null;
|
|
20
20
|
emailVerificationSentAt?: Date | null;
|
|
21
|
+
isAnonymous?: boolean;
|
|
21
22
|
metadata?: Record<string, unknown>;
|
|
22
23
|
createdAt: Date;
|
|
23
24
|
updatedAt: Date;
|
|
@@ -31,6 +32,7 @@ export interface CreateUserData {
|
|
|
31
32
|
displayName?: string;
|
|
32
33
|
photoUrl?: string;
|
|
33
34
|
emailVerified?: boolean;
|
|
35
|
+
isAnonymous?: boolean;
|
|
34
36
|
metadata?: Record<string, unknown>;
|
|
35
37
|
}
|
|
36
38
|
/**
|
|
@@ -84,7 +86,6 @@ export interface RoleData {
|
|
|
84
86
|
edit?: boolean;
|
|
85
87
|
delete?: boolean;
|
|
86
88
|
}> | null;
|
|
87
|
-
config: Record<string, unknown> | null;
|
|
88
89
|
}
|
|
89
90
|
/**
|
|
90
91
|
* Data for creating a new role
|
|
@@ -95,7 +96,6 @@ export interface CreateRoleData {
|
|
|
95
96
|
isAdmin?: boolean;
|
|
96
97
|
defaultPermissions?: RoleData["defaultPermissions"];
|
|
97
98
|
collectionPermissions?: RoleData["collectionPermissions"];
|
|
98
|
-
config?: RoleData["config"];
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
101
101
|
* Refresh token info
|
|
@@ -304,8 +304,98 @@ export interface TokenRepository {
|
|
|
304
304
|
*/
|
|
305
305
|
deleteExpiredTokens(): Promise<void>;
|
|
306
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* MFA factor data structure
|
|
309
|
+
*/
|
|
310
|
+
export interface MfaFactor {
|
|
311
|
+
id: string;
|
|
312
|
+
userId: string;
|
|
313
|
+
factorType: "totp";
|
|
314
|
+
friendlyName?: string;
|
|
315
|
+
verified: boolean;
|
|
316
|
+
createdAt: Date;
|
|
317
|
+
updatedAt: Date;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* MFA challenge information
|
|
321
|
+
*/
|
|
322
|
+
export interface MfaChallengeInfo {
|
|
323
|
+
id: string;
|
|
324
|
+
factorId: string;
|
|
325
|
+
createdAt: Date;
|
|
326
|
+
verifiedAt?: Date;
|
|
327
|
+
ipAddress?: string;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Recovery code data structure
|
|
331
|
+
*/
|
|
332
|
+
export interface RecoveryCode {
|
|
333
|
+
id: string;
|
|
334
|
+
userId: string;
|
|
335
|
+
usedAt?: Date;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Abstract MFA repository interface.
|
|
339
|
+
* Handles all MFA-related database operations.
|
|
340
|
+
*/
|
|
341
|
+
export interface MfaRepository {
|
|
342
|
+
/**
|
|
343
|
+
* Create a new MFA factor for a user
|
|
344
|
+
*/
|
|
345
|
+
createMfaFactor(userId: string, factorType: "totp", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor>;
|
|
346
|
+
/**
|
|
347
|
+
* Get all MFA factors for a user
|
|
348
|
+
*/
|
|
349
|
+
getMfaFactors(userId: string): Promise<MfaFactor[]>;
|
|
350
|
+
/**
|
|
351
|
+
* Get a specific MFA factor by ID
|
|
352
|
+
*/
|
|
353
|
+
getMfaFactorById(factorId: string): Promise<(MfaFactor & {
|
|
354
|
+
secretEncrypted: string;
|
|
355
|
+
}) | null>;
|
|
356
|
+
/**
|
|
357
|
+
* Mark an MFA factor as verified
|
|
358
|
+
*/
|
|
359
|
+
verifyMfaFactor(factorId: string): Promise<void>;
|
|
360
|
+
/**
|
|
361
|
+
* Delete an MFA factor
|
|
362
|
+
*/
|
|
363
|
+
deleteMfaFactor(factorId: string, userId: string): Promise<void>;
|
|
364
|
+
/**
|
|
365
|
+
* Create an MFA challenge
|
|
366
|
+
*/
|
|
367
|
+
createMfaChallenge(factorId: string, ipAddress?: string): Promise<MfaChallengeInfo>;
|
|
368
|
+
/**
|
|
369
|
+
* Get an MFA challenge by ID
|
|
370
|
+
*/
|
|
371
|
+
getMfaChallengeById(challengeId: string): Promise<MfaChallengeInfo | null>;
|
|
372
|
+
/**
|
|
373
|
+
* Mark an MFA challenge as verified
|
|
374
|
+
*/
|
|
375
|
+
verifyMfaChallenge(challengeId: string): Promise<void>;
|
|
376
|
+
/**
|
|
377
|
+
* Create recovery codes for a user
|
|
378
|
+
*/
|
|
379
|
+
createRecoveryCodes(userId: string, codeHashes: string[]): Promise<void>;
|
|
380
|
+
/**
|
|
381
|
+
* Use a recovery code (mark as used)
|
|
382
|
+
*/
|
|
383
|
+
useRecoveryCode(userId: string, codeHash: string): Promise<boolean>;
|
|
384
|
+
/**
|
|
385
|
+
* Get unused recovery code count for a user
|
|
386
|
+
*/
|
|
387
|
+
getUnusedRecoveryCodeCount(userId: string): Promise<number>;
|
|
388
|
+
/**
|
|
389
|
+
* Delete all recovery codes for a user
|
|
390
|
+
*/
|
|
391
|
+
deleteAllRecoveryCodes(userId: string): Promise<void>;
|
|
392
|
+
/**
|
|
393
|
+
* Check if a user has any verified MFA factors
|
|
394
|
+
*/
|
|
395
|
+
hasVerifiedMfaFactors(userId: string): Promise<boolean>;
|
|
396
|
+
}
|
|
307
397
|
/**
|
|
308
398
|
* Combined auth repository interface for convenience
|
|
309
399
|
*/
|
|
310
|
-
export interface AuthRepository extends UserRepository, RoleRepository, TokenRepository {
|
|
400
|
+
export interface AuthRepository extends UserRepository, RoleRepository, TokenRepository, MfaRepository {
|
|
311
401
|
}
|