@rebasepro/server-core 0.0.1-canary.09e5ec5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) 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 +56 -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 +58 -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 +22 -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-DXVBFp5V.js +37 -0
  78. package/dist/index-DXVBFp5V.js.map +1 -0
  79. package/dist/index.es.js +49934 -0
  80. package/dist/index.es.js.map +1 -0
  81. package/dist/index.umd.js +49968 -0
  82. package/dist/index.umd.js.map +1 -0
  83. package/dist/server-core/src/api/ast-schema-editor.d.ts +21 -0
  84. package/dist/server-core/src/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
  85. package/dist/server-core/src/api/errors.d.ts +35 -0
  86. package/dist/server-core/src/api/graphql/graphql-schema-generator.d.ts +35 -0
  87. package/dist/server-core/src/api/graphql/index.d.ts +1 -0
  88. package/dist/server-core/src/api/index.d.ts +9 -0
  89. package/dist/server-core/src/api/openapi-generator.d.ts +16 -0
  90. package/dist/server-core/src/api/rest/api-generator.d.ts +64 -0
  91. package/dist/server-core/src/api/rest/index.d.ts +1 -0
  92. package/dist/server-core/src/api/rest/query-parser.d.ts +9 -0
  93. package/dist/server-core/src/api/schema-editor-routes.d.ts +3 -0
  94. package/dist/server-core/src/api/server.d.ts +40 -0
  95. package/dist/server-core/src/api/types.d.ts +90 -0
  96. package/dist/server-core/src/auth/admin-routes.d.ts +16 -0
  97. package/dist/server-core/src/auth/apple-oauth.d.ts +30 -0
  98. package/dist/server-core/src/auth/bitbucket-oauth.d.ts +11 -0
  99. package/dist/server-core/src/auth/discord-oauth.d.ts +14 -0
  100. package/dist/server-core/src/auth/facebook-oauth.d.ts +14 -0
  101. package/dist/server-core/src/auth/github-oauth.d.ts +15 -0
  102. package/dist/server-core/src/auth/gitlab-oauth.d.ts +13 -0
  103. package/dist/server-core/src/auth/google-oauth.d.ts +14 -0
  104. package/dist/server-core/src/auth/index.d.ts +23 -0
  105. package/dist/server-core/src/auth/interfaces.d.ts +309 -0
  106. package/dist/server-core/src/auth/jwt.d.ts +43 -0
  107. package/dist/server-core/src/auth/linkedin-oauth.d.ts +18 -0
  108. package/dist/server-core/src/auth/microsoft-oauth.d.ts +16 -0
  109. package/dist/server-core/src/auth/middleware.d.ts +81 -0
  110. package/dist/server-core/src/auth/password.d.ts +22 -0
  111. package/dist/server-core/src/auth/rate-limiter.d.ts +31 -0
  112. package/dist/server-core/src/auth/routes.d.ts +27 -0
  113. package/dist/server-core/src/auth/slack-oauth.d.ts +12 -0
  114. package/dist/server-core/src/auth/spotify-oauth.d.ts +12 -0
  115. package/dist/server-core/src/auth/twitter-oauth.d.ts +18 -0
  116. package/dist/server-core/src/bootstrappers/index.d.ts +0 -0
  117. package/dist/server-core/src/collections/BackendCollectionRegistry.d.ts +13 -0
  118. package/dist/server-core/src/collections/loader.d.ts +5 -0
  119. package/dist/server-core/src/cron/cron-loader.d.ts +17 -0
  120. package/dist/server-core/src/cron/cron-routes.d.ts +14 -0
  121. package/dist/server-core/src/cron/cron-scheduler.d.ts +61 -0
  122. package/dist/server-core/src/cron/cron-store.d.ts +32 -0
  123. package/dist/server-core/src/cron/index.d.ts +6 -0
  124. package/dist/server-core/src/db/interfaces.d.ts +18 -0
  125. package/dist/server-core/src/email/index.d.ts +6 -0
  126. package/dist/server-core/src/email/smtp-email-service.d.ts +25 -0
  127. package/dist/server-core/src/email/templates.d.ts +42 -0
  128. package/dist/server-core/src/email/types.d.ts +107 -0
  129. package/dist/server-core/src/functions/function-loader.d.ts +17 -0
  130. package/dist/server-core/src/functions/function-routes.d.ts +10 -0
  131. package/dist/server-core/src/functions/index.d.ts +3 -0
  132. package/dist/server-core/src/history/history-routes.d.ts +23 -0
  133. package/dist/server-core/src/history/index.d.ts +1 -0
  134. package/dist/server-core/src/index.d.ts +29 -0
  135. package/dist/server-core/src/init.d.ts +159 -0
  136. package/dist/server-core/src/serve-spa.d.ts +30 -0
  137. package/dist/server-core/src/services/driver-registry.d.ts +78 -0
  138. package/dist/server-core/src/singleton.d.ts +35 -0
  139. package/dist/server-core/src/storage/LocalStorageController.d.ts +46 -0
  140. package/dist/server-core/src/storage/S3StorageController.d.ts +36 -0
  141. package/dist/server-core/src/storage/index.d.ts +25 -0
  142. package/dist/server-core/src/storage/routes.d.ts +38 -0
  143. package/dist/server-core/src/storage/storage-registry.d.ts +78 -0
  144. package/dist/server-core/src/storage/types.d.ts +103 -0
  145. package/dist/server-core/src/types/index.d.ts +11 -0
  146. package/dist/server-core/src/utils/dev-port.d.ts +35 -0
  147. package/dist/server-core/src/utils/logger.d.ts +31 -0
  148. package/dist/server-core/src/utils/logging.d.ts +9 -0
  149. package/dist/server-core/src/utils/request-logger.d.ts +19 -0
  150. package/dist/server-core/src/utils/sql.d.ts +27 -0
  151. package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
  152. package/dist/types/src/controllers/auth.d.ts +119 -0
  153. package/dist/types/src/controllers/client.d.ts +170 -0
  154. package/dist/types/src/controllers/collection_registry.d.ts +45 -0
  155. package/dist/types/src/controllers/customization_controller.d.ts +60 -0
  156. package/dist/types/src/controllers/data.d.ts +168 -0
  157. package/dist/types/src/controllers/data_driver.d.ts +160 -0
  158. package/dist/types/src/controllers/database_admin.d.ts +11 -0
  159. package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
  160. package/dist/types/src/controllers/effective_role.d.ts +4 -0
  161. package/dist/types/src/controllers/email.d.ts +34 -0
  162. package/dist/types/src/controllers/index.d.ts +18 -0
  163. package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
  164. package/dist/types/src/controllers/navigation.d.ts +213 -0
  165. package/dist/types/src/controllers/registry.d.ts +54 -0
  166. package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
  167. package/dist/types/src/controllers/side_entity_controller.d.ts +90 -0
  168. package/dist/types/src/controllers/snackbar.d.ts +24 -0
  169. package/dist/types/src/controllers/storage.d.ts +171 -0
  170. package/dist/types/src/index.d.ts +4 -0
  171. package/dist/types/src/rebase_context.d.ts +105 -0
  172. package/dist/types/src/types/backend.d.ts +536 -0
  173. package/dist/types/src/types/builders.d.ts +15 -0
  174. package/dist/types/src/types/chips.d.ts +5 -0
  175. package/dist/types/src/types/collections.d.ts +856 -0
  176. package/dist/types/src/types/cron.d.ts +102 -0
  177. package/dist/types/src/types/data_source.d.ts +64 -0
  178. package/dist/types/src/types/entities.d.ts +145 -0
  179. package/dist/types/src/types/entity_actions.d.ts +98 -0
  180. package/dist/types/src/types/entity_callbacks.d.ts +173 -0
  181. package/dist/types/src/types/entity_link_builder.d.ts +7 -0
  182. package/dist/types/src/types/entity_overrides.d.ts +10 -0
  183. package/dist/types/src/types/entity_views.d.ts +61 -0
  184. package/dist/types/src/types/export_import.d.ts +21 -0
  185. package/dist/types/src/types/index.d.ts +23 -0
  186. package/dist/types/src/types/locales.d.ts +4 -0
  187. package/dist/types/src/types/modify_collections.d.ts +5 -0
  188. package/dist/types/src/types/plugins.d.ts +279 -0
  189. package/dist/types/src/types/properties.d.ts +1176 -0
  190. package/dist/types/src/types/property_config.d.ts +70 -0
  191. package/dist/types/src/types/relations.d.ts +336 -0
  192. package/dist/types/src/types/slots.d.ts +252 -0
  193. package/dist/types/src/types/translations.d.ts +870 -0
  194. package/dist/types/src/types/user_management_delegate.d.ts +121 -0
  195. package/dist/types/src/types/websockets.d.ts +78 -0
  196. package/dist/types/src/users/index.d.ts +2 -0
  197. package/dist/types/src/users/roles.d.ts +22 -0
  198. package/dist/types/src/users/user.d.ts +46 -0
  199. package/history_diff.log +385 -0
  200. package/jest.config.cjs +16 -0
  201. package/package.json +86 -0
  202. package/scratch.ts +9 -0
  203. package/src/api/ast-schema-editor.ts +289 -0
  204. package/src/api/collections_for_test/callbacks_test_collection.ts +60 -0
  205. package/src/api/errors.ts +179 -0
  206. package/src/api/graphql/graphql-schema-generator.ts +336 -0
  207. package/src/api/graphql/index.ts +2 -0
  208. package/src/api/index.ts +11 -0
  209. package/src/api/openapi-generator.ts +715 -0
  210. package/src/api/rest/api-generator.ts +472 -0
  211. package/src/api/rest/index.ts +2 -0
  212. package/src/api/rest/query-parser.ts +155 -0
  213. package/src/api/schema-editor-routes.ts +41 -0
  214. package/src/api/server.ts +248 -0
  215. package/src/api/types.ts +90 -0
  216. package/src/auth/admin-routes.ts +529 -0
  217. package/src/auth/apple-oauth.ts +130 -0
  218. package/src/auth/bitbucket-oauth.ts +82 -0
  219. package/src/auth/discord-oauth.ts +83 -0
  220. package/src/auth/facebook-oauth.ts +72 -0
  221. package/src/auth/github-oauth.ts +110 -0
  222. package/src/auth/gitlab-oauth.ts +70 -0
  223. package/src/auth/google-oauth.ts +48 -0
  224. package/src/auth/index.ts +34 -0
  225. package/src/auth/interfaces.ts +363 -0
  226. package/src/auth/jwt.ts +181 -0
  227. package/src/auth/linkedin-oauth.ts +81 -0
  228. package/src/auth/microsoft-oauth.ts +88 -0
  229. package/src/auth/middleware.ts +384 -0
  230. package/src/auth/password.ts +77 -0
  231. package/src/auth/rate-limiter.ts +129 -0
  232. package/src/auth/routes.ts +788 -0
  233. package/src/auth/slack-oauth.ts +71 -0
  234. package/src/auth/spotify-oauth.ts +67 -0
  235. package/src/auth/twitter-oauth.ts +120 -0
  236. package/src/bootstrappers/index.ts +1 -0
  237. package/src/collections/BackendCollectionRegistry.ts +20 -0
  238. package/src/collections/loader.ts +49 -0
  239. package/src/cron/cron-loader.ts +89 -0
  240. package/src/cron/cron-routes.test.ts +265 -0
  241. package/src/cron/cron-routes.ts +85 -0
  242. package/src/cron/cron-scheduler.test.ts +421 -0
  243. package/src/cron/cron-scheduler.ts +413 -0
  244. package/src/cron/cron-store.ts +163 -0
  245. package/src/cron/index.ts +6 -0
  246. package/src/db/interfaces.ts +60 -0
  247. package/src/email/index.ts +18 -0
  248. package/src/email/smtp-email-service.ts +91 -0
  249. package/src/email/templates.ts +388 -0
  250. package/src/email/types.ts +105 -0
  251. package/src/functions/function-loader.ts +119 -0
  252. package/src/functions/function-routes.ts +31 -0
  253. package/src/functions/index.ts +3 -0
  254. package/src/history/history-routes.ts +129 -0
  255. package/src/history/index.ts +2 -0
  256. package/src/index.ts +66 -0
  257. package/src/init.ts +727 -0
  258. package/src/serve-spa.ts +81 -0
  259. package/src/services/driver-registry.ts +182 -0
  260. package/src/singleton.test.ts +28 -0
  261. package/src/singleton.ts +70 -0
  262. package/src/storage/LocalStorageController.ts +365 -0
  263. package/src/storage/S3StorageController.ts +298 -0
  264. package/src/storage/index.ts +43 -0
  265. package/src/storage/routes.ts +264 -0
  266. package/src/storage/storage-registry.ts +187 -0
  267. package/src/storage/types.ts +134 -0
  268. package/src/types/index.ts +27 -0
  269. package/src/utils/dev-port.ts +176 -0
  270. package/src/utils/logger.ts +143 -0
  271. package/src/utils/logging.ts +38 -0
  272. package/src/utils/request-logger.ts +66 -0
  273. package/src/utils/sql.ts +38 -0
  274. package/test/admin-routes.test.ts +640 -0
  275. package/test/api-generator.test.ts +501 -0
  276. package/test/ast-schema-editor.test.ts +63 -0
  277. package/test/auth-middleware-hono.test.ts +556 -0
  278. package/test/auth-routes.test.ts +1047 -0
  279. package/test/driver-registry.test.ts +282 -0
  280. package/test/error-propagation.test.ts +226 -0
  281. package/test/errors-hono.test.ts +133 -0
  282. package/test/errors.test.ts +155 -0
  283. package/test/jwt-security.test.ts +182 -0
  284. package/test/jwt.test.ts +324 -0
  285. package/test/middleware.test.ts +300 -0
  286. package/test/password.test.ts +165 -0
  287. package/test/query-parser.test.ts +263 -0
  288. package/test/rate-limiter.test.ts +102 -0
  289. package/test/safe-compare.test.ts +66 -0
  290. package/test/singleton.test.ts +59 -0
  291. package/test/storage-local.test.ts +271 -0
  292. package/test/storage-registry.test.ts +282 -0
  293. package/test/storage-routes.test.ts +222 -0
  294. package/test/storage-s3.test.ts +304 -0
  295. package/test-ast.ts +28 -0
  296. package/test.ts +6 -0
  297. package/test_output.txt +1133 -0
  298. package/tsconfig.json +49 -0
  299. package/tsconfig.prod.json +20 -0
  300. package/vite.config.ts +80 -0
@@ -0,0 +1,363 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Authentication Abstraction Interfaces
5
+ *
6
+ * These interfaces define the contracts for authentication-related operations.
7
+ * Implementations can use different databases (PostgreSQL, MongoDB, etc.) to
8
+ * store user, role, and token data.
9
+ */
10
+
11
+ /**
12
+ * User data structure
13
+ */
14
+ export interface UserData {
15
+ id: string;
16
+ email: string;
17
+ passwordHash?: string | null;
18
+ displayName?: string | null;
19
+ photoUrl?: 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
+ emailVerified?: boolean;
36
+ }
37
+
38
+ /**
39
+ * User Identity Data (OAuth accounts linked to user)
40
+ */
41
+ export interface UserIdentityData {
42
+ id: string;
43
+ userId: string;
44
+ provider: string;
45
+ providerId: string;
46
+ profileData?: Record<string, unknown> | null;
47
+ createdAt: Date;
48
+ updatedAt: Date;
49
+ }
50
+
51
+ /**
52
+ * Standardized profile data returned by an OAuth provider verification payload
53
+ */
54
+ export interface OAuthProviderProfile {
55
+ providerId: string;
56
+ email: string;
57
+ displayName?: string | null;
58
+ photoUrl?: string | null;
59
+ }
60
+
61
+ /**
62
+ * Pluggable OAuth Provider integration strategy
63
+ */
64
+ export interface OAuthProvider<T = unknown> {
65
+ /** The identifier of the provider (e.g. "github", "google") */
66
+ id: string;
67
+
68
+ /** Zod schema validating the expected request payload (e.g. { code: string }) */
69
+ schema: z.ZodSchema<T>;
70
+
71
+ /** Verify external tokens/codes and return a standardized user profile */
72
+ verify: (payload: T) => Promise<OAuthProviderProfile | null>;
73
+ }
74
+
75
+ /**
76
+ * Role data structure
77
+ */
78
+ export interface RoleData {
79
+ id: string;
80
+ name: string;
81
+ isAdmin: boolean;
82
+ defaultPermissions: {
83
+ read?: boolean;
84
+ create?: boolean;
85
+ edit?: boolean;
86
+ delete?: boolean;
87
+ } | null;
88
+ collectionPermissions: Record<string, {
89
+ read?: boolean;
90
+ create?: boolean;
91
+ edit?: boolean;
92
+ delete?: boolean;
93
+ }> | null;
94
+ config: Record<string, unknown> | null;
95
+ }
96
+
97
+ /**
98
+ * Data for creating a new role
99
+ */
100
+ export interface CreateRoleData {
101
+ id: string;
102
+ name: string;
103
+ isAdmin?: boolean;
104
+ defaultPermissions?: RoleData["defaultPermissions"];
105
+ collectionPermissions?: RoleData["collectionPermissions"];
106
+ config?: RoleData["config"];
107
+ }
108
+
109
+ /**
110
+ * Refresh token info
111
+ */
112
+ export interface RefreshTokenInfo {
113
+ id: string;
114
+ userId: string;
115
+ tokenHash: string;
116
+ expiresAt: Date;
117
+ createdAt: Date;
118
+ userAgent?: string | null;
119
+ ipAddress?: string | null;
120
+ }
121
+
122
+ /**
123
+ * Password reset token info
124
+ */
125
+ export interface PasswordResetTokenInfo {
126
+ userId: string;
127
+ expiresAt: Date;
128
+ }
129
+
130
+ // =============================================================================
131
+ // AUTH REPOSITORY INTERFACES
132
+ // =============================================================================
133
+
134
+ /**
135
+ * Options for paginated user listing
136
+ */
137
+ export interface ListUsersOptions {
138
+ /** Max results per page (default 25) */
139
+ limit?: number;
140
+ /** Number of results to skip (default 0) */
141
+ offset?: number;
142
+ /** Search term — matches against email and displayName (case-insensitive) */
143
+ search?: string;
144
+ /** Field to sort by (default "createdAt") */
145
+ orderBy?: string;
146
+ /** Sort direction (default "desc") */
147
+ orderDir?: "asc" | "desc";
148
+ /** Filter by role ID */
149
+ roleId?: string;
150
+ }
151
+
152
+ /**
153
+ * Result of a paginated user listing
154
+ */
155
+ export interface PaginatedUsersResult {
156
+ users: UserData[];
157
+ /** Total number of users matching the filters (ignoring limit/offset) */
158
+ total: number;
159
+ limit: number;
160
+ offset: number;
161
+ }
162
+
163
+ /**
164
+ * Abstract user repository interface.
165
+ * Handles all user-related database operations.
166
+ */
167
+ export interface UserRepository {
168
+ /**
169
+ * Create a new user
170
+ */
171
+ createUser(data: CreateUserData): Promise<UserData>;
172
+
173
+ /**
174
+ * Get a user by ID
175
+ */
176
+ getUserById(id: string): Promise<UserData | null>;
177
+
178
+ /**
179
+ * Get a user by email
180
+ */
181
+ getUserByEmail(email: string): Promise<UserData | null>;
182
+
183
+ /**
184
+ * Get a user by an OAuth identity
185
+ */
186
+ getUserByIdentity(provider: string, providerId: string): Promise<UserData | null>;
187
+
188
+ /**
189
+ * Get all identities linked to a user
190
+ */
191
+ getUserIdentities(userId: string): Promise<UserIdentityData[]>;
192
+
193
+ /**
194
+ * Link a new OAuth identity to a user
195
+ */
196
+ linkUserIdentity(userId: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void>;
197
+
198
+ /**
199
+ * Update a user
200
+ */
201
+ updateUser(id: string, data: Partial<Omit<CreateUserData, "id">>): Promise<UserData | null>;
202
+
203
+ /**
204
+ * Delete a user
205
+ */
206
+ deleteUser(id: string): Promise<void>;
207
+
208
+ /**
209
+ * List all users (unbounded — use listUsersPaginated for large datasets)
210
+ */
211
+ listUsers(): Promise<UserData[]>;
212
+
213
+ /**
214
+ * List users with server-side pagination, search, and sorting.
215
+ */
216
+ listUsersPaginated(options?: ListUsersOptions): Promise<PaginatedUsersResult>;
217
+
218
+ /**
219
+ * Update user's password hash
220
+ */
221
+ updatePassword(id: string, passwordHash: string): Promise<void>;
222
+
223
+ /**
224
+ * Set email verification status
225
+ */
226
+ setEmailVerified(id: string, verified: boolean): Promise<void>;
227
+
228
+ /**
229
+ * Set email verification token
230
+ */
231
+ setVerificationToken(id: string, token: string | null): Promise<void>;
232
+
233
+ /**
234
+ * Find user by email verification token
235
+ */
236
+ getUserByVerificationToken(token: string): Promise<UserData | null>;
237
+
238
+ /**
239
+ * Get roles for a user
240
+ */
241
+ getUserRoles(userId: string): Promise<RoleData[]>;
242
+
243
+ /**
244
+ * Get role IDs for a user
245
+ */
246
+ getUserRoleIds(userId: string): Promise<string[]>;
247
+
248
+ /**
249
+ * Set roles for a user (replaces existing roles)
250
+ */
251
+ setUserRoles(userId: string, roleIds: string[]): Promise<void>;
252
+
253
+ /**
254
+ * Assign a specific role to a new user
255
+ */
256
+ assignDefaultRole(userId: string, roleId: string): Promise<void>;
257
+
258
+ /**
259
+ * Get user with their roles
260
+ */
261
+ getUserWithRoles(userId: string): Promise<{ user: UserData; roles: RoleData[] } | null>;
262
+ }
263
+
264
+ /**
265
+ * Abstract role repository interface.
266
+ * Handles all role-related database operations.
267
+ */
268
+ export interface RoleRepository {
269
+ /**
270
+ * Get a role by ID
271
+ */
272
+ getRoleById(id: string): Promise<RoleData | null>;
273
+
274
+ /**
275
+ * List all roles
276
+ */
277
+ listRoles(): Promise<RoleData[]>;
278
+
279
+ /**
280
+ * Create a new role
281
+ */
282
+ createRole(data: CreateRoleData): Promise<RoleData>;
283
+
284
+ /**
285
+ * Update a role
286
+ */
287
+ updateRole(id: string, data: Partial<Omit<RoleData, "id">>): Promise<RoleData | null>;
288
+
289
+ /**
290
+ * Delete a role
291
+ */
292
+ deleteRole(id: string): Promise<void>;
293
+ }
294
+
295
+ /**
296
+ * Abstract token repository interface.
297
+ * Handles refresh tokens and password reset tokens.
298
+ */
299
+ export interface TokenRepository {
300
+ // Refresh tokens
301
+
302
+ /**
303
+ * Create a new refresh token
304
+ */
305
+ createRefreshToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
306
+
307
+ /**
308
+ * Find a refresh token by hash
309
+ */
310
+ findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null>;
311
+
312
+ /**
313
+ * Delete a refresh token by hash
314
+ */
315
+ deleteRefreshToken(tokenHash: string): Promise<void>;
316
+
317
+ /**
318
+ * Delete all refresh tokens for a user
319
+ */
320
+ deleteAllRefreshTokensForUser(userId: string): Promise<void>;
321
+
322
+ /**
323
+ * List all refresh tokens for a user
324
+ */
325
+ listRefreshTokensForUser(userId: string): Promise<RefreshTokenInfo[]>;
326
+
327
+ /**
328
+ * Delete a specific refresh token by its primary key ID
329
+ */
330
+ deleteRefreshTokenById(id: string, userId: string): Promise<void>;
331
+
332
+ // Password reset tokens
333
+
334
+ /**
335
+ * Create a password reset token
336
+ */
337
+ createPasswordResetToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void>;
338
+
339
+ /**
340
+ * Find a valid (not expired, not used) password reset token by hash
341
+ */
342
+ findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null>;
343
+
344
+ /**
345
+ * Mark a password reset token as used
346
+ */
347
+ markPasswordResetTokenUsed(tokenHash: string): Promise<void>;
348
+
349
+ /**
350
+ * Delete all password reset tokens for a user
351
+ */
352
+ deleteAllPasswordResetTokensForUser(userId: string): Promise<void>;
353
+
354
+ /**
355
+ * Clean up expired tokens
356
+ */
357
+ deleteExpiredTokens(): Promise<void>;
358
+ }
359
+
360
+ /**
361
+ * Combined auth repository interface for convenience
362
+ */
363
+ export interface AuthRepository extends UserRepository, RoleRepository, TokenRepository { }
@@ -0,0 +1,181 @@
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
+ uid?: string;
14
+ }
15
+
16
+ let jwtConfig: JwtConfig = {
17
+ secret: "",
18
+ accessExpiresIn: "1h",
19
+ refreshExpiresIn: "30d"
20
+ };
21
+
22
+ /**
23
+ * Configure JWT settings - call this during initialization.
24
+ * Validates the secret strength to prevent deployment with default/weak secrets.
25
+ */
26
+ export function configureJwt(config: JwtConfig): void {
27
+ // Reject obviously weak/default secrets
28
+ const weakSecrets = new Set([
29
+ "secret",
30
+ "jwt-secret",
31
+ "jwt_secret",
32
+ "your-secret",
33
+ "your-super-secret-jwt-key-change-in-production",
34
+ "super-secret-jwt-key-change-in-production",
35
+ "change-me",
36
+ "changeme",
37
+ "password",
38
+ "test",
39
+ "mysecret",
40
+ "my-secret",
41
+ "my_secret",
42
+ "example-secret",
43
+ "please-change-me",
44
+ "replace-this-with-a-real-secret",
45
+ "default-secret"
46
+ ]);
47
+
48
+ if (!config.secret || config.secret.length < 32) {
49
+ throw new Error(
50
+ "JWT secret is too short. Must be at least 32 characters. " +
51
+ "Generate one with: node -e \"console.log(require('crypto').randomBytes(48).toString('base64'))\""
52
+ );
53
+ }
54
+
55
+ if (weakSecrets.has(config.secret.toLowerCase())) {
56
+ throw new Error(
57
+ "JWT secret is a known default/weak value. Please use a strong, randomly generated secret. " +
58
+ "Generate one with: node -e \"console.log(require('crypto').randomBytes(48).toString('base64'))\""
59
+ );
60
+ }
61
+
62
+ jwtConfig = {
63
+ ...jwtConfig,
64
+ ...config
65
+ };
66
+ }
67
+
68
+ /**
69
+ * Generate an access token (short-lived, 1 hour by default)
70
+ */
71
+ export function generateAccessToken(userId: string, roles: string[]): string {
72
+ if (!jwtConfig.secret) {
73
+ throw new Error("JWT secret not configured. Call configureJwt() first.");
74
+ }
75
+
76
+ const payload: AccessTokenPayload = { userId,
77
+ roles };
78
+
79
+ return jwt.sign(payload, jwtConfig.secret, {
80
+ expiresIn: jwtConfig.accessExpiresIn as jwt.SignOptions["expiresIn"],
81
+ algorithm: "HS256"
82
+ });
83
+ }
84
+
85
+ /**
86
+ * Get the expiration time of an access token in milliseconds from now
87
+ */
88
+ export function getAccessTokenExpiryMs(): number {
89
+ const duration = jwtConfig.accessExpiresIn || "1h";
90
+ const match = duration.match(/^(\d+)([dhms])$/);
91
+
92
+ if (!match) {
93
+ // Default to 1 hour
94
+ return 60 * 60 * 1000;
95
+ }
96
+
97
+ const value = parseInt(match[1], 10);
98
+ const unit = match[2];
99
+
100
+ switch (unit) {
101
+ case "d": return value * 24 * 60 * 60 * 1000;
102
+ case "h": return value * 60 * 60 * 1000;
103
+ case "m": return value * 60 * 1000;
104
+ case "s": return value * 1000;
105
+ default: return 60 * 60 * 1000;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Get the expiration timestamp for an access token
111
+ */
112
+ export function getAccessTokenExpiry(): number {
113
+ return Date.now() + getAccessTokenExpiryMs();
114
+ }
115
+
116
+ /**
117
+ * Verify and decode an access token
118
+ */
119
+ export function verifyAccessToken(token: string): AccessTokenPayload | null {
120
+ if (!jwtConfig.secret) {
121
+ throw new Error("JWT secret not configured. Call configureJwt() first.");
122
+ }
123
+
124
+ try {
125
+ const decoded = jwt.verify(token, jwtConfig.secret, { algorithms: ["HS256"] }) as { userId?: string; uid?: string; sub?: string; roles?: string[] };
126
+ const id = decoded.userId || decoded.uid || decoded.sub;
127
+ if (!id) {
128
+ console.error("[JWT] Verification failed: missing id in payload", decoded);
129
+ return null;
130
+ }
131
+
132
+ return {
133
+ userId: id,
134
+ roles: decoded.roles || []
135
+ };
136
+ } catch (error) {
137
+ console.error("[JWT] Verification failed:", error, "Token start:", token.substring(0, 15));
138
+ return null;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Generate a random refresh token (long-lived, 30 days by default)
144
+ */
145
+ export function generateRefreshToken(): string {
146
+ return randomBytes(40).toString("hex");
147
+ }
148
+
149
+ /**
150
+ * Hash a refresh token for database storage (don't store raw tokens)
151
+ */
152
+ export function hashRefreshToken(token: string): string {
153
+ return createHash("sha256").update(token).digest("hex");
154
+ }
155
+
156
+ /**
157
+ * Calculate refresh token expiration date
158
+ */
159
+ export function getRefreshTokenExpiry(): Date {
160
+ const duration = jwtConfig.refreshExpiresIn || "30d";
161
+ const match = duration.match(/^(\d+)([dhms])$/);
162
+
163
+ if (!match) {
164
+ // Default to 30 days
165
+ return new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
166
+ }
167
+
168
+ const value = parseInt(match[1], 10);
169
+ const unit = match[2];
170
+
171
+ let ms: number;
172
+ switch (unit) {
173
+ case "d": ms = value * 24 * 60 * 60 * 1000; break;
174
+ case "h": ms = value * 60 * 60 * 1000; break;
175
+ case "m": ms = value * 60 * 1000; break;
176
+ case "s": ms = value * 1000; break;
177
+ default: ms = 30 * 24 * 60 * 60 * 1000;
178
+ }
179
+
180
+ return new Date(Date.now() + ms);
181
+ }
@@ -0,0 +1,81 @@
1
+ import type { OAuthProvider, OAuthProviderProfile } from "./interfaces";
2
+ import { z } from "zod";
3
+
4
+ export interface LinkedinUserInfo {
5
+ linkedinId: string;
6
+ email: string;
7
+ displayName: string | null;
8
+ photoUrl: string | null;
9
+ emailVerified: boolean;
10
+ }
11
+
12
+ /**
13
+ * Creates a LinkedIn OAuth Provider integration
14
+ */
15
+ export function createLinkedinProvider(config: { clientId: string, clientSecret: string }): OAuthProvider<{ code: string; redirectUri: string }> {
16
+ return {
17
+ id: "linkedin",
18
+ schema: z.object({
19
+ code: z.string().min(1, "Auth code is required"),
20
+ redirectUri: z.string().url("Valid redirect URI is required")
21
+ }),
22
+ verify: async (payload: { code: string; redirectUri: string }): Promise<OAuthProviderProfile | null> => {
23
+ try {
24
+ // Exchange code for access token
25
+ const tokenResponse = await fetch("https://www.linkedin.com/oauth/v2/accessToken", {
26
+ method: "POST",
27
+ headers: {
28
+ "Content-Type": "application/x-www-form-urlencoded"
29
+ },
30
+ body: new URLSearchParams({
31
+ grant_type: "authorization_code",
32
+ code: payload.code,
33
+ redirect_uri: payload.redirectUri,
34
+ client_id: config.clientId,
35
+ client_secret: config.clientSecret
36
+ })
37
+ });
38
+
39
+ if (!tokenResponse.ok) {
40
+ const errBody = await tokenResponse.text();
41
+ console.error("Failed to get LinkedIn access token:", errBody);
42
+ return null;
43
+ }
44
+
45
+ const tokenData = await tokenResponse.json() as { access_token: string; id_token?: string };
46
+ const accessToken = tokenData.access_token;
47
+
48
+ // Fetch User Info using OIDC userinfo endpoint
49
+ const profileResponse = await fetch("https://api.linkedin.com/v2/userinfo", {
50
+ headers: {
51
+ "Authorization": `Bearer ${accessToken}`
52
+ }
53
+ });
54
+
55
+ if (!profileResponse.ok) {
56
+ const errBody = await profileResponse.text();
57
+ console.error("Failed to get LinkedIn user info:", errBody);
58
+ return null;
59
+ }
60
+
61
+ const profileData = await profileResponse.json() as {
62
+ sub: string;
63
+ email: string;
64
+ name?: string;
65
+ picture?: string;
66
+ email_verified?: boolean;
67
+ };
68
+
69
+ return {
70
+ providerId: profileData.sub,
71
+ email: profileData.email,
72
+ displayName: profileData.name || null,
73
+ photoUrl: profileData.picture || null
74
+ };
75
+ } catch (error) {
76
+ console.error("LinkedIn OAuth error:", error);
77
+ return null;
78
+ }
79
+ }
80
+ };
81
+ }
@@ -0,0 +1,88 @@
1
+ import type { OAuthProvider, OAuthProviderProfile } from "./interfaces";
2
+ import { z } from "zod";
3
+
4
+ /**
5
+ * Creates a Microsoft / Entra ID (Azure AD) OAuth Provider integration.
6
+ *
7
+ * Supports both personal Microsoft accounts and work/school (Azure AD) accounts
8
+ * via the "common" tenant endpoint. Uses the authorization code flow.
9
+ */
10
+ export function createMicrosoftProvider(config: {
11
+ clientId: string;
12
+ clientSecret: string;
13
+ /** Tenant ID. Defaults to "common" which allows both personal and organizational accounts. */
14
+ tenantId?: string;
15
+ }): OAuthProvider<{ code: string; redirectUri: string }> {
16
+ const tenantId = config.tenantId || "common";
17
+
18
+ return {
19
+ id: "microsoft",
20
+ schema: z.object({
21
+ code: z.string().min(1, "Auth code is required"),
22
+ redirectUri: z.string().url("Valid redirect URI is required")
23
+ }),
24
+ verify: async (payload: { code: string; redirectUri: string }): Promise<OAuthProviderProfile | null> => {
25
+ try {
26
+ // Exchange code for access token
27
+ const tokenResponse = await fetch(
28
+ `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
29
+ {
30
+ method: "POST",
31
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
32
+ body: new URLSearchParams({
33
+ client_id: config.clientId,
34
+ client_secret: config.clientSecret,
35
+ code: payload.code,
36
+ redirect_uri: payload.redirectUri,
37
+ grant_type: "authorization_code",
38
+ scope: "openid profile email User.Read"
39
+ })
40
+ }
41
+ );
42
+
43
+ if (!tokenResponse.ok) {
44
+ console.error("Failed to get Microsoft access token:", await tokenResponse.text());
45
+ return null;
46
+ }
47
+
48
+ const tokenData = await tokenResponse.json() as { access_token: string };
49
+ const accessToken = tokenData.access_token;
50
+
51
+ // Fetch user profile from Microsoft Graph
52
+ const profileResponse = await fetch("https://graph.microsoft.com/v1.0/me", {
53
+ headers: { "Authorization": `Bearer ${accessToken}` }
54
+ });
55
+
56
+ if (!profileResponse.ok) {
57
+ console.error("Failed to get Microsoft user info:", await profileResponse.text());
58
+ return null;
59
+ }
60
+
61
+ const profileData = await profileResponse.json() as {
62
+ id: string;
63
+ displayName?: string | null;
64
+ mail?: string | null;
65
+ userPrincipalName?: string | null;
66
+ };
67
+
68
+ const email = profileData.mail || profileData.userPrincipalName;
69
+ if (!email) {
70
+ console.error("Microsoft user has no email");
71
+ return null;
72
+ }
73
+
74
+ // Attempt to fetch profile photo URL (Graph returns binary, not a URL).
75
+ // We skip this and let the frontend use the Microsoft Graph photo endpoint.
76
+ return {
77
+ providerId: profileData.id,
78
+ email,
79
+ displayName: profileData.displayName || null,
80
+ photoUrl: null
81
+ };
82
+ } catch (error) {
83
+ console.error("Microsoft OAuth error:", error);
84
+ return null;
85
+ }
86
+ }
87
+ };
88
+ }