@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.
Files changed (254) hide show
  1. package/LICENSE +6 -0
  2. package/README.md +40 -0
  3. package/build-errors.txt +52 -0
  4. package/coverage/clover.xml +3739 -0
  5. package/coverage/coverage-final.json +31 -0
  6. package/coverage/lcov-report/base.css +224 -0
  7. package/coverage/lcov-report/block-navigation.js +87 -0
  8. package/coverage/lcov-report/favicon.png +0 -0
  9. package/coverage/lcov-report/index.html +266 -0
  10. package/coverage/lcov-report/prettify.css +1 -0
  11. package/coverage/lcov-report/prettify.js +2 -0
  12. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  13. package/coverage/lcov-report/sorter.js +210 -0
  14. package/coverage/lcov-report/src/api/ast-schema-editor.ts.html +952 -0
  15. package/coverage/lcov-report/src/api/errors.ts.html +472 -0
  16. package/coverage/lcov-report/src/api/graphql/graphql-schema-generator.ts.html +1069 -0
  17. package/coverage/lcov-report/src/api/graphql/index.html +116 -0
  18. package/coverage/lcov-report/src/api/index.html +176 -0
  19. package/coverage/lcov-report/src/api/openapi-generator.ts.html +565 -0
  20. package/coverage/lcov-report/src/api/rest/api-generator.ts.html +994 -0
  21. package/coverage/lcov-report/src/api/rest/index.html +131 -0
  22. package/coverage/lcov-report/src/api/rest/query-parser.ts.html +550 -0
  23. package/coverage/lcov-report/src/api/schema-editor-routes.ts.html +202 -0
  24. package/coverage/lcov-report/src/api/server.ts.html +823 -0
  25. package/coverage/lcov-report/src/auth/admin-routes.ts.html +973 -0
  26. package/coverage/lcov-report/src/auth/index.html +176 -0
  27. package/coverage/lcov-report/src/auth/jwt.ts.html +574 -0
  28. package/coverage/lcov-report/src/auth/middleware.ts.html +745 -0
  29. package/coverage/lcov-report/src/auth/password.ts.html +310 -0
  30. package/coverage/lcov-report/src/auth/services.ts.html +2074 -0
  31. package/coverage/lcov-report/src/collections/index.html +116 -0
  32. package/coverage/lcov-report/src/collections/loader.ts.html +232 -0
  33. package/coverage/lcov-report/src/db/auth-schema.ts.html +523 -0
  34. package/coverage/lcov-report/src/db/data-transformer.ts.html +1753 -0
  35. package/coverage/lcov-report/src/db/entityService.ts.html +700 -0
  36. package/coverage/lcov-report/src/db/index.html +146 -0
  37. package/coverage/lcov-report/src/db/services/EntityFetchService.ts.html +4048 -0
  38. package/coverage/lcov-report/src/db/services/EntityPersistService.ts.html +883 -0
  39. package/coverage/lcov-report/src/db/services/RelationService.ts.html +3121 -0
  40. package/coverage/lcov-report/src/db/services/entity-helpers.ts.html +442 -0
  41. package/coverage/lcov-report/src/db/services/index.html +176 -0
  42. package/coverage/lcov-report/src/db/services/index.ts.html +124 -0
  43. package/coverage/lcov-report/src/generate-drizzle-schema-logic.ts.html +1960 -0
  44. package/coverage/lcov-report/src/index.html +116 -0
  45. package/coverage/lcov-report/src/services/driver-registry.ts.html +631 -0
  46. package/coverage/lcov-report/src/services/index.html +131 -0
  47. package/coverage/lcov-report/src/services/postgresDataDriver.ts.html +3025 -0
  48. package/coverage/lcov-report/src/storage/LocalStorageController.ts.html +1189 -0
  49. package/coverage/lcov-report/src/storage/S3StorageController.ts.html +970 -0
  50. package/coverage/lcov-report/src/storage/index.html +161 -0
  51. package/coverage/lcov-report/src/storage/storage-registry.ts.html +646 -0
  52. package/coverage/lcov-report/src/storage/types.ts.html +451 -0
  53. package/coverage/lcov-report/src/utils/drizzle-conditions.ts.html +3082 -0
  54. package/coverage/lcov-report/src/utils/index.html +116 -0
  55. package/coverage/lcov.info +7179 -0
  56. package/dist/common/src/collections/CollectionRegistry.d.ts +48 -0
  57. package/dist/common/src/collections/index.d.ts +1 -0
  58. package/dist/common/src/data/buildRebaseData.d.ts +14 -0
  59. package/dist/common/src/index.d.ts +3 -0
  60. package/dist/common/src/util/builders.d.ts +57 -0
  61. package/dist/common/src/util/callbacks.d.ts +6 -0
  62. package/dist/common/src/util/collections.d.ts +11 -0
  63. package/dist/common/src/util/common.d.ts +2 -0
  64. package/dist/common/src/util/conditions.d.ts +26 -0
  65. package/dist/common/src/util/entities.d.ts +36 -0
  66. package/dist/common/src/util/enums.d.ts +3 -0
  67. package/dist/common/src/util/index.d.ts +16 -0
  68. package/dist/common/src/util/navigation_from_path.d.ts +34 -0
  69. package/dist/common/src/util/navigation_utils.d.ts +20 -0
  70. package/dist/common/src/util/parent_references_from_path.d.ts +6 -0
  71. package/dist/common/src/util/paths.d.ts +14 -0
  72. package/dist/common/src/util/permissions.d.ts +5 -0
  73. package/dist/common/src/util/references.d.ts +2 -0
  74. package/dist/common/src/util/relations.d.ts +12 -0
  75. package/dist/common/src/util/resolutions.d.ts +72 -0
  76. package/dist/common/src/util/storage.d.ts +24 -0
  77. package/dist/index-BeMqpmfQ.js +239 -0
  78. package/dist/index-BeMqpmfQ.js.map +1 -0
  79. package/dist/index-bl4J3lNb.js +55823 -0
  80. package/dist/index-bl4J3lNb.js.map +1 -0
  81. package/dist/index.es.js +58 -0
  82. package/dist/index.es.js.map +1 -0
  83. package/dist/index.umd.js +56062 -0
  84. package/dist/index.umd.js.map +1 -0
  85. package/dist/server-core/src/api/ast-schema-editor.d.ts +21 -0
  86. package/dist/server-core/src/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
  87. package/dist/server-core/src/api/errors.d.ts +35 -0
  88. package/dist/server-core/src/api/graphql/graphql-schema-generator.d.ts +35 -0
  89. package/dist/server-core/src/api/graphql/index.d.ts +1 -0
  90. package/dist/server-core/src/api/index.d.ts +9 -0
  91. package/dist/server-core/src/api/openapi-generator.d.ts +2 -0
  92. package/dist/server-core/src/api/rest/api-generator.d.ts +64 -0
  93. package/dist/server-core/src/api/rest/index.d.ts +1 -0
  94. package/dist/server-core/src/api/rest/query-parser.d.ts +9 -0
  95. package/dist/server-core/src/api/schema-editor-routes.d.ts +3 -0
  96. package/dist/server-core/src/api/server.d.ts +40 -0
  97. package/dist/server-core/src/api/types.d.ts +90 -0
  98. package/dist/server-core/src/auth/admin-routes.d.ts +7 -0
  99. package/dist/server-core/src/auth/google-oauth.d.ts +20 -0
  100. package/dist/server-core/src/auth/index.d.ts +12 -0
  101. package/dist/server-core/src/auth/interfaces.d.ts +270 -0
  102. package/dist/server-core/src/auth/jwt.d.ts +42 -0
  103. package/dist/server-core/src/auth/middleware.d.ts +56 -0
  104. package/dist/server-core/src/auth/password.d.ts +22 -0
  105. package/dist/server-core/src/auth/rate-limiter.d.ts +31 -0
  106. package/dist/server-core/src/auth/routes.d.ts +17 -0
  107. package/dist/server-core/src/bootstrappers/index.d.ts +0 -0
  108. package/dist/server-core/src/collections/BackendCollectionRegistry.d.ts +13 -0
  109. package/dist/server-core/src/collections/loader.d.ts +5 -0
  110. package/dist/server-core/src/db/interfaces.d.ts +18 -0
  111. package/dist/server-core/src/email/index.d.ts +6 -0
  112. package/dist/server-core/src/email/smtp-email-service.d.ts +25 -0
  113. package/dist/server-core/src/email/templates.d.ts +33 -0
  114. package/dist/server-core/src/email/types.d.ts +110 -0
  115. package/dist/server-core/src/functions/function-loader.d.ts +17 -0
  116. package/dist/server-core/src/functions/function-routes.d.ts +10 -0
  117. package/dist/server-core/src/functions/index.d.ts +3 -0
  118. package/dist/server-core/src/history/history-routes.d.ts +23 -0
  119. package/dist/server-core/src/history/index.d.ts +1 -0
  120. package/dist/server-core/src/index.d.ts +24 -0
  121. package/dist/server-core/src/init.d.ts +49 -0
  122. package/dist/server-core/src/serve-spa.d.ts +30 -0
  123. package/dist/server-core/src/services/driver-registry.d.ts +78 -0
  124. package/dist/server-core/src/storage/LocalStorageController.d.ts +46 -0
  125. package/dist/server-core/src/storage/S3StorageController.d.ts +36 -0
  126. package/dist/server-core/src/storage/index.d.ts +18 -0
  127. package/dist/server-core/src/storage/routes.d.ts +38 -0
  128. package/dist/server-core/src/storage/storage-registry.d.ts +78 -0
  129. package/dist/server-core/src/storage/types.d.ts +91 -0
  130. package/dist/server-core/src/types/index.d.ts +11 -0
  131. package/dist/server-core/src/utils/logging.d.ts +9 -0
  132. package/dist/server-core/src/utils/sql.d.ts +27 -0
  133. package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
  134. package/dist/types/src/controllers/auth.d.ts +117 -0
  135. package/dist/types/src/controllers/client.d.ts +58 -0
  136. package/dist/types/src/controllers/collection_registry.d.ts +44 -0
  137. package/dist/types/src/controllers/customization_controller.d.ts +54 -0
  138. package/dist/types/src/controllers/data.d.ts +141 -0
  139. package/dist/types/src/controllers/data_driver.d.ts +168 -0
  140. package/dist/types/src/controllers/database_admin.d.ts +11 -0
  141. package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
  142. package/dist/types/src/controllers/effective_role.d.ts +4 -0
  143. package/dist/types/src/controllers/index.d.ts +17 -0
  144. package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
  145. package/dist/types/src/controllers/navigation.d.ts +213 -0
  146. package/dist/types/src/controllers/registry.d.ts +51 -0
  147. package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
  148. package/dist/types/src/controllers/side_entity_controller.d.ts +89 -0
  149. package/dist/types/src/controllers/snackbar.d.ts +24 -0
  150. package/dist/types/src/controllers/storage.d.ts +173 -0
  151. package/dist/types/src/index.d.ts +4 -0
  152. package/dist/types/src/rebase_context.d.ts +101 -0
  153. package/dist/types/src/types/backend.d.ts +533 -0
  154. package/dist/types/src/types/builders.d.ts +14 -0
  155. package/dist/types/src/types/chips.d.ts +5 -0
  156. package/dist/types/src/types/collections.d.ts +812 -0
  157. package/dist/types/src/types/data_source.d.ts +64 -0
  158. package/dist/types/src/types/entities.d.ts +145 -0
  159. package/dist/types/src/types/entity_actions.d.ts +98 -0
  160. package/dist/types/src/types/entity_callbacks.d.ts +173 -0
  161. package/dist/types/src/types/entity_link_builder.d.ts +7 -0
  162. package/dist/types/src/types/entity_overrides.d.ts +9 -0
  163. package/dist/types/src/types/entity_views.d.ts +61 -0
  164. package/dist/types/src/types/export_import.d.ts +21 -0
  165. package/dist/types/src/types/index.d.ts +22 -0
  166. package/dist/types/src/types/locales.d.ts +4 -0
  167. package/dist/types/src/types/modify_collections.d.ts +5 -0
  168. package/dist/types/src/types/plugins.d.ts +225 -0
  169. package/dist/types/src/types/properties.d.ts +1091 -0
  170. package/dist/types/src/types/property_config.d.ts +70 -0
  171. package/dist/types/src/types/relations.d.ts +336 -0
  172. package/dist/types/src/types/slots.d.ts +228 -0
  173. package/dist/types/src/types/translations.d.ts +826 -0
  174. package/dist/types/src/types/user_management_delegate.d.ts +120 -0
  175. package/dist/types/src/types/websockets.d.ts +78 -0
  176. package/dist/types/src/users/index.d.ts +2 -0
  177. package/dist/types/src/users/roles.d.ts +22 -0
  178. package/dist/types/src/users/user.d.ts +46 -0
  179. package/history_diff.log +385 -0
  180. package/jest.config.cjs +16 -0
  181. package/package.json +86 -0
  182. package/scratch.ts +8 -0
  183. package/src/api/ast-schema-editor.ts +289 -0
  184. package/src/api/collections_for_test/callbacks_test_collection.ts +57 -0
  185. package/src/api/errors.ts +155 -0
  186. package/src/api/graphql/graphql-schema-generator.ts +334 -0
  187. package/src/api/graphql/index.ts +2 -0
  188. package/src/api/index.ts +11 -0
  189. package/src/api/openapi-generator.ts +160 -0
  190. package/src/api/rest/api-generator.ts +466 -0
  191. package/src/api/rest/index.ts +2 -0
  192. package/src/api/rest/query-parser.ts +155 -0
  193. package/src/api/schema-editor-routes.ts +39 -0
  194. package/src/api/server.ts +245 -0
  195. package/src/api/types.ts +90 -0
  196. package/src/auth/admin-routes.ts +488 -0
  197. package/src/auth/google-oauth.ts +60 -0
  198. package/src/auth/index.ts +21 -0
  199. package/src/auth/interfaces.ts +316 -0
  200. package/src/auth/jwt.ts +164 -0
  201. package/src/auth/middleware.ts +235 -0
  202. package/src/auth/password.ts +75 -0
  203. package/src/auth/rate-limiter.ts +129 -0
  204. package/src/auth/routes.ts +730 -0
  205. package/src/bootstrappers/index.ts +1 -0
  206. package/src/collections/BackendCollectionRegistry.ts +20 -0
  207. package/src/collections/loader.ts +49 -0
  208. package/src/db/interfaces.ts +60 -0
  209. package/src/email/index.ts +17 -0
  210. package/src/email/smtp-email-service.ts +88 -0
  211. package/src/email/templates.ts +301 -0
  212. package/src/email/types.ts +112 -0
  213. package/src/functions/function-loader.ts +91 -0
  214. package/src/functions/function-routes.ts +31 -0
  215. package/src/functions/index.ts +3 -0
  216. package/src/history/history-routes.ts +128 -0
  217. package/src/history/index.ts +2 -0
  218. package/src/index.ts +56 -0
  219. package/src/init.ts +309 -0
  220. package/src/serve-spa.ts +81 -0
  221. package/src/services/driver-registry.ts +182 -0
  222. package/src/storage/LocalStorageController.ts +368 -0
  223. package/src/storage/S3StorageController.ts +295 -0
  224. package/src/storage/index.ts +32 -0
  225. package/src/storage/routes.ts +247 -0
  226. package/src/storage/storage-registry.ts +187 -0
  227. package/src/storage/types.ts +122 -0
  228. package/src/types/index.ts +27 -0
  229. package/src/utils/logging.ts +35 -0
  230. package/src/utils/sql.ts +38 -0
  231. package/test/admin-routes.test.ts +591 -0
  232. package/test/api-generator.test.ts +458 -0
  233. package/test/ast-schema-editor.test.ts +61 -0
  234. package/test/auth-middleware-hono.test.ts +321 -0
  235. package/test/auth-routes.test.ts +868 -0
  236. package/test/driver-registry.test.ts +280 -0
  237. package/test/errors-hono.test.ts +133 -0
  238. package/test/errors.test.ts +150 -0
  239. package/test/jwt-security.test.ts +173 -0
  240. package/test/jwt.test.ts +311 -0
  241. package/test/middleware.test.ts +295 -0
  242. package/test/password.test.ts +165 -0
  243. package/test/query-parser.test.ts +258 -0
  244. package/test/rate-limiter.test.ts +102 -0
  245. package/test/storage-local.test.ts +278 -0
  246. package/test/storage-registry.test.ts +280 -0
  247. package/test/storage-routes.test.ts +218 -0
  248. package/test/storage-s3.test.ts +301 -0
  249. package/test-ast.ts +28 -0
  250. package/test_output.txt +1133 -0
  251. package/tsconfig.json +49 -0
  252. package/tsconfig.prod.json +20 -0
  253. package/vite.config.ts +78 -0
  254. 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 { }
@@ -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
+ }