@rebasepro/server-core 0.0.1-canary.4d4fb3e
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 +48 -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 +36 -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 +12 -0
- package/dist/common/src/util/resolutions.d.ts +72 -0
- package/dist/common/src/util/storage.d.ts +24 -0
- package/dist/index-BeMqpmfQ.js +239 -0
- package/dist/index-BeMqpmfQ.js.map +1 -0
- package/dist/index-bl4J3lNb.js +55823 -0
- package/dist/index-bl4J3lNb.js.map +1 -0
- package/dist/index.es.js +58 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +56062 -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 +2 -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 +7 -0
- package/dist/server-core/src/auth/google-oauth.d.ts +20 -0
- package/dist/server-core/src/auth/index.d.ts +12 -0
- package/dist/server-core/src/auth/interfaces.d.ts +270 -0
- package/dist/server-core/src/auth/jwt.d.ts +42 -0
- package/dist/server-core/src/auth/middleware.d.ts +56 -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 +17 -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/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 +33 -0
- package/dist/server-core/src/email/types.d.ts +110 -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 +24 -0
- package/dist/server-core/src/init.d.ts +49 -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/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 +18 -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 +91 -0
- package/dist/server-core/src/types/index.d.ts +11 -0
- package/dist/server-core/src/utils/logging.d.ts +9 -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 +117 -0
- package/dist/types/src/controllers/client.d.ts +58 -0
- package/dist/types/src/controllers/collection_registry.d.ts +44 -0
- package/dist/types/src/controllers/customization_controller.d.ts +54 -0
- package/dist/types/src/controllers/data.d.ts +141 -0
- package/dist/types/src/controllers/data_driver.d.ts +168 -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/index.d.ts +17 -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 +51 -0
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
- package/dist/types/src/controllers/side_entity_controller.d.ts +89 -0
- package/dist/types/src/controllers/snackbar.d.ts +24 -0
- package/dist/types/src/controllers/storage.d.ts +173 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/rebase_context.d.ts +101 -0
- package/dist/types/src/types/backend.d.ts +533 -0
- package/dist/types/src/types/builders.d.ts +14 -0
- package/dist/types/src/types/chips.d.ts +5 -0
- package/dist/types/src/types/collections.d.ts +812 -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 +9 -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 +22 -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 +225 -0
- package/dist/types/src/types/properties.d.ts +1091 -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 +228 -0
- package/dist/types/src/types/translations.d.ts +826 -0
- package/dist/types/src/types/user_management_delegate.d.ts +120 -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 +8 -0
- package/src/api/ast-schema-editor.ts +289 -0
- package/src/api/collections_for_test/callbacks_test_collection.ts +57 -0
- package/src/api/errors.ts +155 -0
- package/src/api/graphql/graphql-schema-generator.ts +334 -0
- package/src/api/graphql/index.ts +2 -0
- package/src/api/index.ts +11 -0
- package/src/api/openapi-generator.ts +160 -0
- package/src/api/rest/api-generator.ts +466 -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 +39 -0
- package/src/api/server.ts +245 -0
- package/src/api/types.ts +90 -0
- package/src/auth/admin-routes.ts +488 -0
- package/src/auth/google-oauth.ts +60 -0
- package/src/auth/index.ts +21 -0
- package/src/auth/interfaces.ts +316 -0
- package/src/auth/jwt.ts +164 -0
- package/src/auth/middleware.ts +235 -0
- package/src/auth/password.ts +75 -0
- package/src/auth/rate-limiter.ts +129 -0
- package/src/auth/routes.ts +730 -0
- package/src/bootstrappers/index.ts +1 -0
- package/src/collections/BackendCollectionRegistry.ts +20 -0
- package/src/collections/loader.ts +49 -0
- package/src/db/interfaces.ts +60 -0
- package/src/email/index.ts +17 -0
- package/src/email/smtp-email-service.ts +88 -0
- package/src/email/templates.ts +301 -0
- package/src/email/types.ts +112 -0
- package/src/functions/function-loader.ts +91 -0
- package/src/functions/function-routes.ts +31 -0
- package/src/functions/index.ts +3 -0
- package/src/history/history-routes.ts +128 -0
- package/src/history/index.ts +2 -0
- package/src/index.ts +56 -0
- package/src/init.ts +309 -0
- package/src/serve-spa.ts +81 -0
- package/src/services/driver-registry.ts +182 -0
- package/src/storage/LocalStorageController.ts +368 -0
- package/src/storage/S3StorageController.ts +295 -0
- package/src/storage/index.ts +32 -0
- package/src/storage/routes.ts +247 -0
- package/src/storage/storage-registry.ts +187 -0
- package/src/storage/types.ts +122 -0
- package/src/types/index.ts +27 -0
- package/src/utils/logging.ts +35 -0
- package/src/utils/sql.ts +38 -0
- package/test/admin-routes.test.ts +591 -0
- package/test/api-generator.test.ts +458 -0
- package/test/ast-schema-editor.test.ts +61 -0
- package/test/auth-middleware-hono.test.ts +321 -0
- package/test/auth-routes.test.ts +868 -0
- package/test/driver-registry.test.ts +280 -0
- package/test/errors-hono.test.ts +133 -0
- package/test/errors.test.ts +150 -0
- package/test/jwt-security.test.ts +173 -0
- package/test/jwt.test.ts +311 -0
- package/test/middleware.test.ts +295 -0
- package/test/password.test.ts +165 -0
- package/test/query-parser.test.ts +258 -0
- package/test/rate-limiter.test.ts +102 -0
- package/test/storage-local.test.ts +278 -0
- package/test/storage-registry.test.ts +280 -0
- package/test/storage-routes.test.ts +218 -0
- package/test/storage-s3.test.ts +301 -0
- package/test-ast.ts +28 -0
- package/test_output.txt +1133 -0
- package/tsconfig.json +49 -0
- package/tsconfig.prod.json +20 -0
- package/vite.config.ts +78 -0
- package/vite.config.ts.timestamp-1775065397568-8a853255edf6e.mjs +46 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Abstraction Interfaces
|
|
3
|
+
*
|
|
4
|
+
* These interfaces define the contracts for authentication-related operations.
|
|
5
|
+
* Implementations can use different databases (PostgreSQL, MongoDB, etc.) to
|
|
6
|
+
* store user, role, and token data.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* User data structure
|
|
11
|
+
*/
|
|
12
|
+
export interface UserData {
|
|
13
|
+
id: string;
|
|
14
|
+
email: string;
|
|
15
|
+
passwordHash?: string | null;
|
|
16
|
+
displayName?: string | null;
|
|
17
|
+
photoUrl?: string | null;
|
|
18
|
+
provider: string;
|
|
19
|
+
googleId?: string | null;
|
|
20
|
+
emailVerified: boolean;
|
|
21
|
+
emailVerificationToken?: string | null;
|
|
22
|
+
emailVerificationSentAt?: Date | null;
|
|
23
|
+
createdAt: Date;
|
|
24
|
+
updatedAt: Date;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Data for creating a new user
|
|
29
|
+
*/
|
|
30
|
+
export interface CreateUserData {
|
|
31
|
+
email: string;
|
|
32
|
+
passwordHash?: string;
|
|
33
|
+
displayName?: string;
|
|
34
|
+
photoUrl?: string;
|
|
35
|
+
provider?: string;
|
|
36
|
+
googleId?: string;
|
|
37
|
+
emailVerified?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Role data structure
|
|
42
|
+
*/
|
|
43
|
+
export interface RoleData {
|
|
44
|
+
id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
isAdmin: boolean;
|
|
47
|
+
defaultPermissions: {
|
|
48
|
+
read?: boolean;
|
|
49
|
+
create?: boolean;
|
|
50
|
+
edit?: boolean;
|
|
51
|
+
delete?: boolean;
|
|
52
|
+
} | null;
|
|
53
|
+
collectionPermissions: Record<string, {
|
|
54
|
+
read?: boolean;
|
|
55
|
+
create?: boolean;
|
|
56
|
+
edit?: boolean;
|
|
57
|
+
delete?: boolean;
|
|
58
|
+
}> | null;
|
|
59
|
+
config: Record<string, unknown> | null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Data for creating a new role
|
|
64
|
+
*/
|
|
65
|
+
export interface CreateRoleData {
|
|
66
|
+
id: string;
|
|
67
|
+
name: string;
|
|
68
|
+
isAdmin?: boolean;
|
|
69
|
+
defaultPermissions?: RoleData["defaultPermissions"];
|
|
70
|
+
collectionPermissions?: RoleData["collectionPermissions"];
|
|
71
|
+
config?: RoleData["config"];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Refresh token info
|
|
76
|
+
*/
|
|
77
|
+
export interface RefreshTokenInfo {
|
|
78
|
+
id: string;
|
|
79
|
+
userId: string;
|
|
80
|
+
tokenHash: string;
|
|
81
|
+
expiresAt: Date;
|
|
82
|
+
createdAt: Date;
|
|
83
|
+
userAgent?: string | null;
|
|
84
|
+
ipAddress?: string | null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Password reset token info
|
|
89
|
+
*/
|
|
90
|
+
export interface PasswordResetTokenInfo {
|
|
91
|
+
userId: string;
|
|
92
|
+
expiresAt: Date;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// =============================================================================
|
|
96
|
+
// AUTH REPOSITORY INTERFACES
|
|
97
|
+
// =============================================================================
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Options for paginated user listing
|
|
101
|
+
*/
|
|
102
|
+
export interface ListUsersOptions {
|
|
103
|
+
/** Max results per page (default 25) */
|
|
104
|
+
limit?: number;
|
|
105
|
+
/** Number of results to skip (default 0) */
|
|
106
|
+
offset?: number;
|
|
107
|
+
/** Search term — matches against email and displayName (case-insensitive) */
|
|
108
|
+
search?: string;
|
|
109
|
+
/** Field to sort by (default "createdAt") */
|
|
110
|
+
orderBy?: string;
|
|
111
|
+
/** Sort direction (default "desc") */
|
|
112
|
+
orderDir?: "asc" | "desc";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Result of a paginated user listing
|
|
117
|
+
*/
|
|
118
|
+
export interface PaginatedUsersResult {
|
|
119
|
+
users: UserData[];
|
|
120
|
+
/** Total number of users matching the filters (ignoring limit/offset) */
|
|
121
|
+
total: number;
|
|
122
|
+
limit: number;
|
|
123
|
+
offset: number;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Abstract user repository interface.
|
|
128
|
+
* Handles all user-related database operations.
|
|
129
|
+
*/
|
|
130
|
+
export interface UserRepository {
|
|
131
|
+
/**
|
|
132
|
+
* Create a new user
|
|
133
|
+
*/
|
|
134
|
+
createUser(data: CreateUserData): Promise<UserData>;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get a user by ID
|
|
138
|
+
*/
|
|
139
|
+
getUserById(id: string): Promise<UserData | null>;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get a user by email
|
|
143
|
+
*/
|
|
144
|
+
getUserByEmail(email: string): Promise<UserData | null>;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get a user by Google ID
|
|
148
|
+
*/
|
|
149
|
+
getUserByGoogleId(googleId: string): Promise<UserData | null>;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Update a user
|
|
153
|
+
*/
|
|
154
|
+
updateUser(id: string, data: Partial<Omit<CreateUserData, "id">>): Promise<UserData | null>;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Delete a user
|
|
158
|
+
*/
|
|
159
|
+
deleteUser(id: string): Promise<void>;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* List all users (unbounded — use listUsersPaginated for large datasets)
|
|
163
|
+
*/
|
|
164
|
+
listUsers(): Promise<UserData[]>;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* List users with server-side pagination, search, and sorting.
|
|
168
|
+
*/
|
|
169
|
+
listUsersPaginated(options?: ListUsersOptions): Promise<PaginatedUsersResult>;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Update user's password hash
|
|
173
|
+
*/
|
|
174
|
+
updatePassword(id: string, passwordHash: string): Promise<void>;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Set email verification status
|
|
178
|
+
*/
|
|
179
|
+
setEmailVerified(id: string, verified: boolean): Promise<void>;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Set email verification token
|
|
183
|
+
*/
|
|
184
|
+
setVerificationToken(id: string, token: string | null): Promise<void>;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Find user by email verification token
|
|
188
|
+
*/
|
|
189
|
+
getUserByVerificationToken(token: string): Promise<UserData | null>;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get roles for a user
|
|
193
|
+
*/
|
|
194
|
+
getUserRoles(userId: string): Promise<RoleData[]>;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get role IDs for a user
|
|
198
|
+
*/
|
|
199
|
+
getUserRoleIds(userId: string): Promise<string[]>;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Set roles for a user (replaces existing roles)
|
|
203
|
+
*/
|
|
204
|
+
setUserRoles(userId: string, roleIds: string[]): Promise<void>;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Assign a specific role to a new user
|
|
208
|
+
*/
|
|
209
|
+
assignDefaultRole(userId: string, roleId: string): Promise<void>;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get user with their roles
|
|
213
|
+
*/
|
|
214
|
+
getUserWithRoles(userId: string): Promise<{ user: UserData; roles: RoleData[] } | null>;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Abstract role repository interface.
|
|
219
|
+
* Handles all role-related database operations.
|
|
220
|
+
*/
|
|
221
|
+
export interface RoleRepository {
|
|
222
|
+
/**
|
|
223
|
+
* Get a role by ID
|
|
224
|
+
*/
|
|
225
|
+
getRoleById(id: string): Promise<RoleData | null>;
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* List all roles
|
|
229
|
+
*/
|
|
230
|
+
listRoles(): Promise<RoleData[]>;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Create a new role
|
|
234
|
+
*/
|
|
235
|
+
createRole(data: CreateRoleData): Promise<RoleData>;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Update a role
|
|
239
|
+
*/
|
|
240
|
+
updateRole(id: string, data: Partial<Omit<RoleData, "id">>): Promise<RoleData | null>;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Delete a role
|
|
244
|
+
*/
|
|
245
|
+
deleteRole(id: string): Promise<void>;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Abstract token repository interface.
|
|
250
|
+
* Handles refresh tokens and password reset tokens.
|
|
251
|
+
*/
|
|
252
|
+
export interface TokenRepository {
|
|
253
|
+
// Refresh tokens
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Create a new refresh token
|
|
257
|
+
*/
|
|
258
|
+
createRefreshToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Find a refresh token by hash
|
|
262
|
+
*/
|
|
263
|
+
findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null>;
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Delete a refresh token by hash
|
|
267
|
+
*/
|
|
268
|
+
deleteRefreshToken(tokenHash: string): Promise<void>;
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Delete all refresh tokens for a user
|
|
272
|
+
*/
|
|
273
|
+
deleteAllRefreshTokensForUser(userId: string): Promise<void>;
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* List all refresh tokens for a user
|
|
277
|
+
*/
|
|
278
|
+
listRefreshTokensForUser(userId: string): Promise<RefreshTokenInfo[]>;
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Delete a specific refresh token by its primary key ID
|
|
282
|
+
*/
|
|
283
|
+
deleteRefreshTokenById(id: string, userId: string): Promise<void>;
|
|
284
|
+
|
|
285
|
+
// Password reset tokens
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Create a password reset token
|
|
289
|
+
*/
|
|
290
|
+
createPasswordResetToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void>;
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Find a valid (not expired, not used) password reset token by hash
|
|
294
|
+
*/
|
|
295
|
+
findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null>;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Mark a password reset token as used
|
|
299
|
+
*/
|
|
300
|
+
markPasswordResetTokenUsed(tokenHash: string): Promise<void>;
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Delete all password reset tokens for a user
|
|
304
|
+
*/
|
|
305
|
+
deleteAllPasswordResetTokensForUser(userId: string): Promise<void>;
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Clean up expired tokens
|
|
309
|
+
*/
|
|
310
|
+
deleteExpiredTokens(): Promise<void>;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Combined auth repository interface for convenience
|
|
315
|
+
*/
|
|
316
|
+
export interface AuthRepository extends UserRepository, RoleRepository, TokenRepository { }
|
package/src/auth/jwt.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import jwt from "jsonwebtoken";
|
|
2
|
+
import { createHash, randomBytes } from "crypto";
|
|
3
|
+
|
|
4
|
+
export interface JwtConfig {
|
|
5
|
+
secret: string;
|
|
6
|
+
accessExpiresIn?: string;
|
|
7
|
+
refreshExpiresIn?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface AccessTokenPayload {
|
|
11
|
+
userId: string;
|
|
12
|
+
roles: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let jwtConfig: JwtConfig = {
|
|
16
|
+
secret: "",
|
|
17
|
+
accessExpiresIn: "1h",
|
|
18
|
+
refreshExpiresIn: "30d"
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Configure JWT settings - call this during initialization.
|
|
23
|
+
* Validates the secret strength to prevent deployment with default/weak secrets.
|
|
24
|
+
*/
|
|
25
|
+
export function configureJwt(config: JwtConfig): void {
|
|
26
|
+
// Reject obviously weak/default secrets
|
|
27
|
+
const weakSecrets = new Set([
|
|
28
|
+
"secret",
|
|
29
|
+
"jwt-secret",
|
|
30
|
+
"jwt_secret",
|
|
31
|
+
"your-secret",
|
|
32
|
+
"your-super-secret-jwt-key-change-in-production",
|
|
33
|
+
"change-me",
|
|
34
|
+
"changeme",
|
|
35
|
+
"password",
|
|
36
|
+
"test"
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
if (!config.secret || config.secret.length < 32) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
"JWT secret is too short. Must be at least 32 characters. " +
|
|
42
|
+
"Generate one with: node -e \"console.log(require('crypto').randomBytes(48).toString('base64'))\""
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (weakSecrets.has(config.secret.toLowerCase())) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
"JWT secret is a known default/weak value. Please use a strong, randomly generated secret. " +
|
|
49
|
+
"Generate one with: node -e \"console.log(require('crypto').randomBytes(48).toString('base64'))\""
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
jwtConfig = {
|
|
54
|
+
...jwtConfig,
|
|
55
|
+
...config
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Generate an access token (short-lived, 1 hour by default)
|
|
61
|
+
*/
|
|
62
|
+
export function generateAccessToken(userId: string, roles: string[]): string {
|
|
63
|
+
if (!jwtConfig.secret) {
|
|
64
|
+
throw new Error("JWT secret not configured. Call configureJwt() first.");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const payload: AccessTokenPayload = { userId, roles };
|
|
68
|
+
|
|
69
|
+
return jwt.sign(payload, jwtConfig.secret, {
|
|
70
|
+
expiresIn: jwtConfig.accessExpiresIn as jwt.SignOptions["expiresIn"],
|
|
71
|
+
algorithm: "HS256"
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get the expiration time of an access token in milliseconds from now
|
|
77
|
+
*/
|
|
78
|
+
export function getAccessTokenExpiryMs(): number {
|
|
79
|
+
const duration = jwtConfig.accessExpiresIn || "1h";
|
|
80
|
+
const match = duration.match(/^(\d+)([dhms])$/);
|
|
81
|
+
|
|
82
|
+
if (!match) {
|
|
83
|
+
// Default to 1 hour
|
|
84
|
+
return 60 * 60 * 1000;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const value = parseInt(match[1], 10);
|
|
88
|
+
const unit = match[2];
|
|
89
|
+
|
|
90
|
+
switch (unit) {
|
|
91
|
+
case "d": return value * 24 * 60 * 60 * 1000;
|
|
92
|
+
case "h": return value * 60 * 60 * 1000;
|
|
93
|
+
case "m": return value * 60 * 1000;
|
|
94
|
+
case "s": return value * 1000;
|
|
95
|
+
default: return 60 * 60 * 1000;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get the expiration timestamp for an access token
|
|
101
|
+
*/
|
|
102
|
+
export function getAccessTokenExpiry(): number {
|
|
103
|
+
return Date.now() + getAccessTokenExpiryMs();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Verify and decode an access token
|
|
108
|
+
*/
|
|
109
|
+
export function verifyAccessToken(token: string): AccessTokenPayload | null {
|
|
110
|
+
if (!jwtConfig.secret) {
|
|
111
|
+
throw new Error("JWT secret not configured. Call configureJwt() first.");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const decoded = jwt.verify(token, jwtConfig.secret, { algorithms: ["HS256"] }) as jwt.JwtPayload & AccessTokenPayload;
|
|
116
|
+
return {
|
|
117
|
+
userId: decoded.userId,
|
|
118
|
+
roles: decoded.roles
|
|
119
|
+
};
|
|
120
|
+
} catch (error) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Generate a random refresh token (long-lived, 30 days by default)
|
|
127
|
+
*/
|
|
128
|
+
export function generateRefreshToken(): string {
|
|
129
|
+
return randomBytes(40).toString("hex");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Hash a refresh token for database storage (don't store raw tokens)
|
|
134
|
+
*/
|
|
135
|
+
export function hashRefreshToken(token: string): string {
|
|
136
|
+
return createHash("sha256").update(token).digest("hex");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Calculate refresh token expiration date
|
|
141
|
+
*/
|
|
142
|
+
export function getRefreshTokenExpiry(): Date {
|
|
143
|
+
const duration = jwtConfig.refreshExpiresIn || "30d";
|
|
144
|
+
const match = duration.match(/^(\d+)([dhms])$/);
|
|
145
|
+
|
|
146
|
+
if (!match) {
|
|
147
|
+
// Default to 30 days
|
|
148
|
+
return new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const value = parseInt(match[1], 10);
|
|
152
|
+
const unit = match[2];
|
|
153
|
+
|
|
154
|
+
let ms: number;
|
|
155
|
+
switch (unit) {
|
|
156
|
+
case "d": ms = value * 24 * 60 * 60 * 1000; break;
|
|
157
|
+
case "h": ms = value * 60 * 60 * 1000; break;
|
|
158
|
+
case "m": ms = value * 60 * 1000; break;
|
|
159
|
+
case "s": ms = value * 1000; break;
|
|
160
|
+
default: ms = 30 * 24 * 60 * 60 * 1000;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return new Date(Date.now() + ms);
|
|
164
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
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
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Result from a custom auth validator.
|
|
8
|
+
* - `false`/`null`/`undefined` = not authenticated
|
|
9
|
+
* - `true` = authenticated as default user
|
|
10
|
+
* - object with `userId` or `uid` = authenticated with user info
|
|
11
|
+
*/
|
|
12
|
+
export type AuthResult = boolean | null | undefined | { userId?: string; uid?: string; roles?: string[]; [key: string]: unknown };
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Options for creating an auth middleware via createAuthMiddleware()
|
|
16
|
+
*/
|
|
17
|
+
export interface AuthMiddlewareOptions {
|
|
18
|
+
/** DataDriver to scope via withAuth() for RLS */
|
|
19
|
+
driver: DataDriver;
|
|
20
|
+
/** If true, return 401 when no valid token is present (default: false) */
|
|
21
|
+
requireAuth?: boolean;
|
|
22
|
+
/** Optional custom validator (for non-JWT auth, e.g. Firebase Auth) */
|
|
23
|
+
validator?: (c: Context<HonoEnv>) => Promise<AuthResult>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Express middleware that requires a valid JWT token
|
|
28
|
+
* Returns 401 if token is missing or invalid
|
|
29
|
+
*/
|
|
30
|
+
export const requireAuth: MiddlewareHandler<HonoEnv> = async (
|
|
31
|
+
c,
|
|
32
|
+
next
|
|
33
|
+
) => {
|
|
34
|
+
const authHeader = c.req.header("authorization");
|
|
35
|
+
const queryToken = c.req.query("token");
|
|
36
|
+
const hasBearer = authHeader && authHeader.startsWith("Bearer ");
|
|
37
|
+
|
|
38
|
+
if (!hasBearer && !queryToken) {
|
|
39
|
+
return c.json({
|
|
40
|
+
error: {
|
|
41
|
+
message: "Authorization header or token query parameter missing or invalid",
|
|
42
|
+
code: "UNAUTHORIZED"
|
|
43
|
+
}
|
|
44
|
+
}, 401);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const token = hasBearer ? authHeader!.substring(7) : queryToken!;
|
|
48
|
+
const payload = verifyAccessToken(token);
|
|
49
|
+
|
|
50
|
+
if (!payload) {
|
|
51
|
+
return c.json({
|
|
52
|
+
error: {
|
|
53
|
+
message: "Invalid or expired token",
|
|
54
|
+
code: "UNAUTHORIZED"
|
|
55
|
+
}
|
|
56
|
+
}, 401);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
c.set("user", payload);
|
|
60
|
+
return next();
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Middleware that requires the user to have an admin or schema-admin role.
|
|
65
|
+
* Must be used AFTER requireAuth or on a route where user is guaranteed.
|
|
66
|
+
*/
|
|
67
|
+
export const requireAdmin: MiddlewareHandler<HonoEnv> = async (
|
|
68
|
+
c,
|
|
69
|
+
next
|
|
70
|
+
) => {
|
|
71
|
+
const user = c.get("user");
|
|
72
|
+
if (!user) {
|
|
73
|
+
return c.json({
|
|
74
|
+
error: {
|
|
75
|
+
message: "User not authenticated. requireAuth middleware is missing?",
|
|
76
|
+
code: "UNAUTHORIZED"
|
|
77
|
+
}
|
|
78
|
+
}, 401);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const roles = (typeof user === "object" && user !== null && "roles" in user) ? (user.roles || []) : [];
|
|
82
|
+
const isAdmin = roles.some((role: string) => {
|
|
83
|
+
return role === "admin" || role === "schema-admin";
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (!isAdmin) {
|
|
87
|
+
return c.json({
|
|
88
|
+
error: {
|
|
89
|
+
message: "Admin privileges required for this operation",
|
|
90
|
+
code: "FORBIDDEN"
|
|
91
|
+
}
|
|
92
|
+
}, 403);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return next();
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Middleware that optionally extracts user from JWT
|
|
101
|
+
* Does not return 401 if token is missing - allows anonymous access
|
|
102
|
+
*/
|
|
103
|
+
export const optionalAuth: MiddlewareHandler<HonoEnv> = async (
|
|
104
|
+
c,
|
|
105
|
+
next
|
|
106
|
+
) => {
|
|
107
|
+
const authHeader = c.req.header("authorization");
|
|
108
|
+
const queryToken = c.req.query("token");
|
|
109
|
+
const hasBearer = authHeader && authHeader.startsWith("Bearer ");
|
|
110
|
+
|
|
111
|
+
if (hasBearer || queryToken) {
|
|
112
|
+
const token = hasBearer ? authHeader!.substring(7) : queryToken!;
|
|
113
|
+
const payload = verifyAccessToken(token);
|
|
114
|
+
if (payload) {
|
|
115
|
+
c.set("user", payload);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return next();
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Extract user from token - for WebSocket authentication
|
|
124
|
+
*/
|
|
125
|
+
export function extractUserFromToken(token: string): AccessTokenPayload | null {
|
|
126
|
+
return verifyAccessToken(token);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Helper to scope a DataDriver via withAuth() for RLS.
|
|
131
|
+
* SECURITY: If withAuth() is available but fails, the error is re-thrown
|
|
132
|
+
* so the request is denied rather than proceeding with unscoped access.
|
|
133
|
+
*/
|
|
134
|
+
async function scopeDataDriver(
|
|
135
|
+
driver: DataDriver,
|
|
136
|
+
user: { uid: string; roles?: string[] }
|
|
137
|
+
): Promise<DataDriver> {
|
|
138
|
+
if ("withAuth" in driver && typeof (driver as Record<string, unknown>).withAuth === "function") {
|
|
139
|
+
// Fail closed — do NOT catch and swallow errors here.
|
|
140
|
+
// If RLS scoping fails the request must be rejected.
|
|
141
|
+
return await (driver as unknown as { withAuth: (user: Record<string, unknown>) => Promise<DataDriver> }).withAuth(user);
|
|
142
|
+
}
|
|
143
|
+
return driver;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Create a configurable auth middleware that handles:
|
|
148
|
+
* 1. Token extraction (via custom validator or JWT Bearer token)
|
|
149
|
+
* 2. RLS-scoped DataDriver via withAuth()
|
|
150
|
+
* 3. Optional enforcement (401 when requireAuth is true and no user)
|
|
151
|
+
*
|
|
152
|
+
* This is the single source of truth for HTTP auth in Rebase.
|
|
153
|
+
* Use this instead of manually parsing tokens in route handlers.
|
|
154
|
+
*/
|
|
155
|
+
export function createAuthMiddleware(options: AuthMiddlewareOptions): MiddlewareHandler<HonoEnv> {
|
|
156
|
+
const { driver, requireAuth: enforceAuth = false, validator } = options;
|
|
157
|
+
|
|
158
|
+
return async (c, next) => {
|
|
159
|
+
if (validator) {
|
|
160
|
+
// Custom validator path (e.g., Firebase Auth, API keys)
|
|
161
|
+
try {
|
|
162
|
+
const authResult = await validator(c);
|
|
163
|
+
if (authResult && typeof authResult === "object") {
|
|
164
|
+
const id = ("userId" in authResult ? authResult.userId : undefined)
|
|
165
|
+
|| ("uid" in authResult ? authResult.uid : undefined);
|
|
166
|
+
if (id) {
|
|
167
|
+
const roles = authResult.roles || [];
|
|
168
|
+
c.set("user", { userId: id, roles });
|
|
169
|
+
const user = { uid: id, roles, ...authResult };
|
|
170
|
+
c.set("driver", await scopeDataDriver(driver, user));
|
|
171
|
+
} else {
|
|
172
|
+
c.set("driver", driver);
|
|
173
|
+
}
|
|
174
|
+
} else if (authResult === true) {
|
|
175
|
+
c.set("user", { userId: "default", roles: [] });
|
|
176
|
+
c.set("driver", driver);
|
|
177
|
+
} else {
|
|
178
|
+
// Not authenticated - inject anon scope explicitly
|
|
179
|
+
try {
|
|
180
|
+
c.set("driver", await scopeDataDriver(driver, { uid: "anon", roles: ["anon"] }));
|
|
181
|
+
} catch (error) {
|
|
182
|
+
c.set("driver", driver);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} catch (error) {
|
|
186
|
+
return c.json({ error: { message: "Unauthorized", code: "UNAUTHORIZED" } }, 401);
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
// Default JWT path
|
|
190
|
+
try {
|
|
191
|
+
const authHeader = c.req.header("authorization");
|
|
192
|
+
const queryToken = c.req.query("token");
|
|
193
|
+
const hasBearer = authHeader && authHeader.startsWith("Bearer ");
|
|
194
|
+
|
|
195
|
+
if (hasBearer || queryToken) {
|
|
196
|
+
const token = hasBearer ? authHeader!.substring(7) : queryToken!;
|
|
197
|
+
const payload = extractUserFromToken(token);
|
|
198
|
+
|
|
199
|
+
if (payload) {
|
|
200
|
+
c.set("user", payload);
|
|
201
|
+
const user = { uid: payload.userId, roles: payload.roles };
|
|
202
|
+
c.set("driver", await scopeDataDriver(driver, user));
|
|
203
|
+
} else {
|
|
204
|
+
console.error("[AUTH] Token payload empty or invalid (length: " + token.length + ")");
|
|
205
|
+
c.set("driver", driver);
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
// Try to inject anon scope to allow RLS policies to match against public connections
|
|
209
|
+
try {
|
|
210
|
+
c.set("driver", await scopeDataDriver(driver, { uid: "anon", roles: ["anon"] }));
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error("[AUTH] Error scoping default anon driver", error);
|
|
213
|
+
c.set("driver", driver);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error("Default auth validation error", error);
|
|
218
|
+
|
|
219
|
+
// Fallback to anon scope
|
|
220
|
+
try {
|
|
221
|
+
c.set("driver", await scopeDataDriver(driver, { uid: "anon", roles: ["anon"] }));
|
|
222
|
+
} catch (anonError) {
|
|
223
|
+
c.set("driver", driver);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (enforceAuth && !c.get("user")) {
|
|
229
|
+
console.error("[AUTH] Rejecting with 401. Path:", c.req.path);
|
|
230
|
+
return c.json({ error: { message: "Unauthorized: Invalid or missing token", code: "UNAUTHORIZED" } }, 401);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return next();
|
|
234
|
+
};
|
|
235
|
+
}
|