@rebasepro/server-core 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/README.md +2 -2
  2. package/dist/api/ast-schema-editor.d.ts +21 -0
  3. package/dist/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
  4. package/dist/api/errors.d.ts +53 -0
  5. package/dist/api/index.d.ts +7 -0
  6. package/dist/api/logs-routes.d.ts +37 -0
  7. package/dist/api/openapi-generator.d.ts +16 -0
  8. package/dist/api/rest/api-generator.d.ts +68 -0
  9. package/{src/api/rest/index.ts → dist/api/rest/index.d.ts} +0 -1
  10. package/dist/api/rest/query-parser.d.ts +6 -0
  11. package/dist/api/schema-editor-routes.d.ts +3 -0
  12. package/dist/api/types.d.ts +56 -0
  13. package/dist/auth/adapter-middleware.d.ts +43 -0
  14. package/dist/auth/admin-roles-route.d.ts +18 -0
  15. package/dist/auth/admin-user-ops.d.ts +79 -0
  16. package/dist/auth/admin-users-route.d.ts +28 -0
  17. package/dist/auth/api-keys/api-key-middleware.d.ts +39 -0
  18. package/{src/auth/api-keys/api-key-permission-guard.ts → dist/auth/api-keys/api-key-permission-guard.d.ts} +2 -34
  19. package/dist/auth/api-keys/api-key-routes.d.ts +20 -0
  20. package/dist/auth/api-keys/api-key-store.d.ts +35 -0
  21. package/{src/auth/api-keys/api-key-types.ts → dist/auth/api-keys/api-key-types.d.ts} +0 -6
  22. package/{src/auth/api-keys/index.ts → dist/auth/api-keys/index.d.ts} +2 -22
  23. package/dist/auth/apple-oauth.d.ts +30 -0
  24. package/{src/auth/auth-hooks.ts → dist/auth/auth-hooks.d.ts} +15 -73
  25. package/dist/auth/bitbucket-oauth.d.ts +11 -0
  26. package/dist/auth/builtin-auth-adapter.d.ts +55 -0
  27. package/dist/auth/cookie-utils.d.ts +32 -0
  28. package/{src/auth/crypto-utils.ts → dist/auth/crypto-utils.d.ts} +1 -16
  29. package/dist/auth/custom-auth-adapter.d.ts +39 -0
  30. package/dist/auth/discord-oauth.d.ts +14 -0
  31. package/dist/auth/facebook-oauth.d.ts +14 -0
  32. package/dist/auth/github-oauth.d.ts +15 -0
  33. package/dist/auth/gitlab-oauth.d.ts +13 -0
  34. package/dist/auth/google-oauth.d.ts +47 -0
  35. package/{src/auth/index.ts → dist/auth/index.d.ts} +4 -22
  36. package/{src/auth/interfaces.ts → dist/auth/interfaces.d.ts} +20 -89
  37. package/dist/auth/jwt.d.ts +67 -0
  38. package/dist/auth/linkedin-oauth.d.ts +18 -0
  39. package/dist/auth/magic-link-routes.d.ts +32 -0
  40. package/dist/auth/mfa-crypto.d.ts +23 -0
  41. package/dist/auth/mfa-routes.d.ts +7 -0
  42. package/dist/auth/mfa.d.ts +49 -0
  43. package/dist/auth/microsoft-oauth.d.ts +16 -0
  44. package/dist/auth/middleware.d.ts +158 -0
  45. package/dist/auth/password.d.ts +22 -0
  46. package/dist/auth/rate-limiter.d.ts +50 -0
  47. package/dist/auth/reset-password-admin.d.ts +29 -0
  48. package/dist/auth/rls-scope.d.ts +31 -0
  49. package/dist/auth/routes.d.ts +66 -0
  50. package/dist/auth/session-routes.d.ts +29 -0
  51. package/dist/auth/slack-oauth.d.ts +12 -0
  52. package/dist/auth/spotify-oauth.d.ts +12 -0
  53. package/dist/auth/twitter-oauth.d.ts +18 -0
  54. package/dist/collections/BackendCollectionRegistry.d.ts +13 -0
  55. package/dist/collections/loader.d.ts +5 -0
  56. package/dist/cron/cron-loader.d.ts +18 -0
  57. package/dist/cron/cron-routes.d.ts +14 -0
  58. package/dist/cron/cron-scheduler.d.ts +106 -0
  59. package/dist/cron/cron-store.d.ts +32 -0
  60. package/dist/cron/define-cron.d.ts +28 -0
  61. package/{src/cron/index.ts → dist/cron/index.d.ts} +1 -0
  62. package/dist/db/interfaces.d.ts +18 -0
  63. package/dist/dynamic-import-Dvh-K5fl.js +22 -0
  64. package/dist/dynamic-import-Dvh-K5fl.js.map +1 -0
  65. package/dist/email/index.d.ts +6 -0
  66. package/dist/email/smtp-email-service.d.ts +30 -0
  67. package/dist/email/templates.d.ts +50 -0
  68. package/{src/email/types.ts → dist/email/types.d.ts} +40 -36
  69. package/dist/env.d.ts +102 -0
  70. package/dist/functions/define-function.d.ts +55 -0
  71. package/dist/functions/function-loader.d.ts +18 -0
  72. package/dist/functions/function-routes.d.ts +10 -0
  73. package/{src/functions/index.ts → dist/functions/index.d.ts} +2 -0
  74. package/dist/history/history-routes.d.ts +23 -0
  75. package/{src/history/index.ts → dist/history/index.d.ts} +0 -1
  76. package/dist/index.d.ts +35 -0
  77. package/dist/index.es.js +18184 -0
  78. package/dist/index.es.js.map +1 -0
  79. package/dist/index.umd.js +48629 -0
  80. package/dist/index.umd.js.map +1 -0
  81. package/dist/init/docs.d.ts +4 -0
  82. package/dist/init/health.d.ts +2 -0
  83. package/dist/init/middlewares.d.ts +10 -0
  84. package/dist/init/shutdown.d.ts +65 -0
  85. package/dist/init/storage.d.ts +5 -0
  86. package/dist/init.d.ts +344 -0
  87. package/dist/jwt-BwIn8xmk.js +4200 -0
  88. package/dist/jwt-BwIn8xmk.js.map +1 -0
  89. package/dist/logger-BYU66ENZ.js +94 -0
  90. package/dist/logger-BYU66ENZ.js.map +1 -0
  91. package/dist/ms-DnYXB-Wd.js +162 -0
  92. package/dist/ms-DnYXB-Wd.js.map +1 -0
  93. package/dist/openapi-generator-Z9oYWLf_.js +586 -0
  94. package/dist/openapi-generator-Z9oYWLf_.js.map +1 -0
  95. package/dist/schema-editor-routes-D3ef8zu1.js +219 -0
  96. package/dist/schema-editor-routes-D3ef8zu1.js.map +1 -0
  97. package/dist/serve-spa.d.ts +36 -0
  98. package/dist/services/driver-registry.d.ts +78 -0
  99. package/dist/services/routed-realtime-service.d.ts +43 -0
  100. package/dist/services/webhook-service.d.ts +29 -0
  101. package/dist/singleton.d.ts +51 -0
  102. package/dist/src-B4OLmNVa.js +437 -0
  103. package/dist/src-B4OLmNVa.js.map +1 -0
  104. package/dist/src-B5WkP1Rv.js +24778 -0
  105. package/dist/src-B5WkP1Rv.js.map +1 -0
  106. package/dist/storage/GCSStorageController.d.ts +43 -0
  107. package/dist/storage/LocalStorageController.d.ts +46 -0
  108. package/dist/storage/S3StorageController.d.ts +40 -0
  109. package/dist/storage/image-transform.d.ts +50 -0
  110. package/{src/storage/index.ts → dist/storage/index.d.ts} +6 -21
  111. package/dist/storage/routes.d.ts +70 -0
  112. package/dist/storage/storage-registry.d.ts +78 -0
  113. package/dist/storage/tus-handler.d.ts +53 -0
  114. package/{src/storage/types.ts → dist/storage/types.d.ts} +32 -38
  115. package/dist/types/index.d.ts +11 -0
  116. package/dist/utils/dev-port.d.ts +45 -0
  117. package/dist/utils/dynamic-import.d.ts +25 -0
  118. package/dist/utils/logger.d.ts +31 -0
  119. package/dist/utils/logging.d.ts +9 -0
  120. package/dist/utils/request-id.d.ts +4 -0
  121. package/dist/utils/request-logger.d.ts +19 -0
  122. package/{src/utils/sql.ts → dist/utils/sql.d.ts} +5 -16
  123. package/package.json +49 -22
  124. package/coverage/clover.xml +0 -3739
  125. package/coverage/coverage-final.json +0 -31
  126. package/coverage/lcov-report/base.css +0 -224
  127. package/coverage/lcov-report/block-navigation.js +0 -87
  128. package/coverage/lcov-report/favicon.png +0 -0
  129. package/coverage/lcov-report/index.html +0 -266
  130. package/coverage/lcov-report/prettify.css +0 -1
  131. package/coverage/lcov-report/prettify.js +0 -2
  132. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  133. package/coverage/lcov-report/sorter.js +0 -210
  134. package/coverage/lcov-report/src/api/ast-schema-editor.ts.html +0 -952
  135. package/coverage/lcov-report/src/api/errors.ts.html +0 -472
  136. package/coverage/lcov-report/src/api/graphql/graphql-schema-generator.ts.html +0 -1069
  137. package/coverage/lcov-report/src/api/graphql/index.html +0 -116
  138. package/coverage/lcov-report/src/api/index.html +0 -176
  139. package/coverage/lcov-report/src/api/openapi-generator.ts.html +0 -565
  140. package/coverage/lcov-report/src/api/rest/api-generator.ts.html +0 -994
  141. package/coverage/lcov-report/src/api/rest/index.html +0 -131
  142. package/coverage/lcov-report/src/api/rest/query-parser.ts.html +0 -550
  143. package/coverage/lcov-report/src/api/schema-editor-routes.ts.html +0 -202
  144. package/coverage/lcov-report/src/api/server.ts.html +0 -823
  145. package/coverage/lcov-report/src/auth/admin-routes.ts.html +0 -973
  146. package/coverage/lcov-report/src/auth/index.html +0 -176
  147. package/coverage/lcov-report/src/auth/jwt.ts.html +0 -574
  148. package/coverage/lcov-report/src/auth/middleware.ts.html +0 -745
  149. package/coverage/lcov-report/src/auth/password.ts.html +0 -310
  150. package/coverage/lcov-report/src/auth/services.ts.html +0 -2074
  151. package/coverage/lcov-report/src/collections/index.html +0 -116
  152. package/coverage/lcov-report/src/collections/loader.ts.html +0 -232
  153. package/coverage/lcov-report/src/db/auth-schema.ts.html +0 -523
  154. package/coverage/lcov-report/src/db/data-transformer.ts.html +0 -1753
  155. package/coverage/lcov-report/src/db/entityService.ts.html +0 -700
  156. package/coverage/lcov-report/src/db/index.html +0 -146
  157. package/coverage/lcov-report/src/db/services/EntityFetchService.ts.html +0 -4048
  158. package/coverage/lcov-report/src/db/services/EntityPersistService.ts.html +0 -883
  159. package/coverage/lcov-report/src/db/services/RelationService.ts.html +0 -3121
  160. package/coverage/lcov-report/src/db/services/entity-helpers.ts.html +0 -442
  161. package/coverage/lcov-report/src/db/services/index.html +0 -176
  162. package/coverage/lcov-report/src/db/services/index.ts.html +0 -124
  163. package/coverage/lcov-report/src/generate-drizzle-schema-logic.ts.html +0 -1960
  164. package/coverage/lcov-report/src/index.html +0 -116
  165. package/coverage/lcov-report/src/services/driver-registry.ts.html +0 -631
  166. package/coverage/lcov-report/src/services/index.html +0 -131
  167. package/coverage/lcov-report/src/services/postgresDataDriver.ts.html +0 -3025
  168. package/coverage/lcov-report/src/storage/LocalStorageController.ts.html +0 -1189
  169. package/coverage/lcov-report/src/storage/S3StorageController.ts.html +0 -970
  170. package/coverage/lcov-report/src/storage/index.html +0 -161
  171. package/coverage/lcov-report/src/storage/storage-registry.ts.html +0 -646
  172. package/coverage/lcov-report/src/storage/types.ts.html +0 -451
  173. package/coverage/lcov-report/src/utils/drizzle-conditions.ts.html +0 -3082
  174. package/coverage/lcov-report/src/utils/index.html +0 -116
  175. package/coverage/lcov.info +0 -7179
  176. package/jest.config.cjs +0 -19
  177. package/src/api/ast-schema-editor.ts +0 -289
  178. package/src/api/collections_for_test/callbacks_test_collection.ts +0 -61
  179. package/src/api/errors.ts +0 -278
  180. package/src/api/graphql/graphql-schema-generator.ts +0 -383
  181. package/src/api/graphql/index.ts +0 -2
  182. package/src/api/index.ts +0 -11
  183. package/src/api/logs-routes.ts +0 -132
  184. package/src/api/openapi-generator.ts +0 -727
  185. package/src/api/rest/api-generator-count.test.ts +0 -126
  186. package/src/api/rest/api-generator.ts +0 -747
  187. package/src/api/rest/query-parser.ts +0 -300
  188. package/src/api/schema-editor-routes.ts +0 -41
  189. package/src/api/server.ts +0 -271
  190. package/src/api/types.ts +0 -104
  191. package/src/auth/adapter-middleware.ts +0 -114
  192. package/src/auth/admin-roles-route.ts +0 -36
  193. package/src/auth/admin-user-ops.ts +0 -236
  194. package/src/auth/admin-users-route.ts +0 -302
  195. package/src/auth/api-keys/api-key-middleware.ts +0 -133
  196. package/src/auth/api-keys/api-key-routes.ts +0 -193
  197. package/src/auth/api-keys/api-key-store.ts +0 -340
  198. package/src/auth/apple-oauth.ts +0 -122
  199. package/src/auth/bitbucket-oauth.ts +0 -84
  200. package/src/auth/builtin-auth-adapter.ts +0 -380
  201. package/src/auth/custom-auth-adapter.ts +0 -86
  202. package/src/auth/discord-oauth.ts +0 -85
  203. package/src/auth/facebook-oauth.ts +0 -74
  204. package/src/auth/github-oauth.ts +0 -112
  205. package/src/auth/gitlab-oauth.ts +0 -72
  206. package/src/auth/google-oauth.ts +0 -223
  207. package/src/auth/jwt.ts +0 -208
  208. package/src/auth/linkedin-oauth.ts +0 -83
  209. package/src/auth/magic-link-routes.ts +0 -167
  210. package/src/auth/mfa-crypto.ts +0 -91
  211. package/src/auth/mfa-routes.ts +0 -323
  212. package/src/auth/mfa.ts +0 -161
  213. package/src/auth/microsoft-oauth.ts +0 -90
  214. package/src/auth/middleware.ts +0 -369
  215. package/src/auth/password.ts +0 -77
  216. package/src/auth/rate-limiter.ts +0 -225
  217. package/src/auth/reset-password-admin.ts +0 -160
  218. package/src/auth/rls-scope.ts +0 -58
  219. package/src/auth/routes.ts +0 -802
  220. package/src/auth/session-routes.ts +0 -353
  221. package/src/auth/slack-oauth.ts +0 -73
  222. package/src/auth/spotify-oauth.ts +0 -69
  223. package/src/auth/twitter-oauth.ts +0 -128
  224. package/src/collections/BackendCollectionRegistry.ts +0 -20
  225. package/src/collections/loader.ts +0 -50
  226. package/src/cron/cron-loader.ts +0 -84
  227. package/src/cron/cron-routes.test.ts +0 -265
  228. package/src/cron/cron-routes.ts +0 -85
  229. package/src/cron/cron-scheduler.test.ts +0 -559
  230. package/src/cron/cron-scheduler.ts +0 -581
  231. package/src/cron/cron-store.ts +0 -166
  232. package/src/db/interfaces.ts +0 -60
  233. package/src/email/index.ts +0 -19
  234. package/src/email/smtp-email-service.ts +0 -123
  235. package/src/email/templates.ts +0 -470
  236. package/src/env.ts +0 -227
  237. package/src/functions/function-loader.ts +0 -116
  238. package/src/functions/function-routes.ts +0 -31
  239. package/src/history/history-routes.ts +0 -129
  240. package/src/index.ts +0 -70
  241. package/src/init/docs.ts +0 -47
  242. package/src/init/health.ts +0 -37
  243. package/src/init/middlewares.ts +0 -61
  244. package/src/init/shutdown.ts +0 -56
  245. package/src/init/storage.ts +0 -57
  246. package/src/init.ts +0 -1041
  247. package/src/serve-spa.ts +0 -89
  248. package/src/services/driver-registry.ts +0 -183
  249. package/src/services/routed-realtime-service.ts +0 -113
  250. package/src/services/webhook-service.ts +0 -155
  251. package/src/singleton.ts +0 -70
  252. package/src/storage/LocalStorageController.ts +0 -389
  253. package/src/storage/S3StorageController.ts +0 -300
  254. package/src/storage/image-transform.ts +0 -218
  255. package/src/storage/routes.ts +0 -368
  256. package/src/storage/storage-registry.ts +0 -188
  257. package/src/storage/tus-handler.ts +0 -315
  258. package/src/types/index.ts +0 -27
  259. package/src/utils/dev-port.ts +0 -196
  260. package/src/utils/logger.ts +0 -143
  261. package/src/utils/logging.ts +0 -40
  262. package/src/utils/request-id.ts +0 -40
  263. package/src/utils/request-logger.ts +0 -72
  264. package/test/api-generator.test.ts +0 -588
  265. package/test/api-key-permission-guard.test.ts +0 -144
  266. package/test/ast-schema-editor.test.ts +0 -89
  267. package/test/auth-middleware-hono.test.ts +0 -556
  268. package/test/auth-routes.test.ts +0 -1098
  269. package/test/backend-hooks-data.test.ts +0 -477
  270. package/test/custom-auth-adapter.test.ts +0 -189
  271. package/test/driver-registry.test.ts +0 -282
  272. package/test/email-templates.test.ts +0 -174
  273. package/test/env.test.ts +0 -128
  274. package/test/error-propagation.test.ts +0 -226
  275. package/test/errors-hono.test.ts +0 -133
  276. package/test/errors.test.ts +0 -155
  277. package/test/function-loader.test.ts +0 -127
  278. package/test/graphql-schema-generator.test.ts +0 -554
  279. package/test/jwt-security.test.ts +0 -182
  280. package/test/jwt.test.ts +0 -326
  281. package/test/mfa.test.ts +0 -197
  282. package/test/middleware.test.ts +0 -305
  283. package/test/multi-datasource-routing.test.ts +0 -113
  284. package/test/password.test.ts +0 -165
  285. package/test/query-parser.test.ts +0 -233
  286. package/test/rate-limiter.test.ts +0 -102
  287. package/test/reset-password-admin.test.ts +0 -113
  288. package/test/routed-realtime-service.test.ts +0 -86
  289. package/test/safe-compare.test.ts +0 -66
  290. package/test/singleton.test.ts +0 -59
  291. package/test/smtp-email-service.test.ts +0 -169
  292. package/test/storage-local.test.ts +0 -271
  293. package/test/storage-registry.test.ts +0 -282
  294. package/test/storage-routes.test.ts +0 -222
  295. package/test/storage-s3.test.ts +0 -304
  296. package/test/transform-auth-response.test.ts +0 -305
  297. package/test/webhook-service.test.ts +0 -260
  298. package/tsconfig.json +0 -54
  299. package/tsconfig.prod.json +0 -23
  300. package/vite.config.ts +0 -86
@@ -1,353 +0,0 @@
1
- import { Hono } from "hono";
2
- import { z } from "zod";
3
- import { randomBytes } from "crypto";
4
- import { ApiError } from "../api/errors";
5
- import { HonoEnv } from "../api/types";
6
- import { requireAuth } from "./middleware";
7
- import { logger } from "../utils/logger";
8
- import { strictAuthLimiter, defaultAuthLimiter } from "./rate-limiter";
9
- import { hashRefreshToken } from "./jwt";
10
- import type { AuthModuleConfig } from "./routes";
11
- import { resolveAuthHooks } from "./auth-hooks";
12
- import type { CreateUserData } from "./interfaces";
13
- import type { AuthResponsePayload, TransformAuthResponseContext } from "@rebasepro/types";
14
-
15
- interface SessionRoutesConfig {
16
- router: Hono<HonoEnv>;
17
- config: AuthModuleConfig;
18
- ops: ReturnType<typeof resolveAuthHooks>;
19
- parseBody: <T>(schema: z.ZodSchema<T>, body: unknown) => T;
20
- buildAuthResponse: (
21
- user: { id: string; email: string; displayName?: string | null; photoUrl?: string | null; metadata?: Record<string, unknown> | null },
22
- roleIds: string[],
23
- accessToken: string,
24
- refreshToken: string
25
- ) => unknown;
26
- createSessionAndTokens: (userId: string, userAgent: string, ipAddress: string) => Promise<{
27
- roleIds: string[];
28
- accessToken: string;
29
- refreshToken: string;
30
- }>;
31
- applyTransformHook: (
32
- response: AuthResponsePayload,
33
- method: TransformAuthResponseContext["method"],
34
- request: Request,
35
- userId: string
36
- ) => Promise<AuthResponsePayload>;
37
- }
38
-
39
- export function mountSessionRoutes(opts: SessionRoutesConfig): void {
40
- const { router, config, ops, parseBody, buildAuthResponse, createSessionAndTokens, applyTransformHook } = opts;
41
- const authRepo = config.authRepo;
42
-
43
- const logoutSchema = z.object({
44
- refreshToken: z.string().optional()
45
- });
46
-
47
- const linkSchema = z.object({
48
- email: z.string().email("Invalid email address").max(255),
49
- password: z.string().min(1, "Password is required").max(128)
50
- });
51
-
52
- const updateProfileSchema = z.object({
53
- displayName: z.string().max(255).optional(),
54
- photoURL: z.string().url().max(2048).optional()
55
- });
56
-
57
- const isEmailConfigured = () => !!(config.emailService && config.emailService.isConfigured());
58
-
59
- /**
60
- * POST /auth/logout
61
- */
62
- router.post("/logout", async (c) => {
63
- const { refreshToken } = parseBody(logoutSchema, await c.req.json());
64
-
65
- if (refreshToken) {
66
- const tokenHash = hashRefreshToken(refreshToken);
67
- await authRepo.deleteRefreshToken(tokenHash);
68
- }
69
-
70
- // Call afterLogout hook (fire-and-forget)
71
- // Extract userId from the access token if present
72
- const authHeader = c.req.header("authorization");
73
- if (ops.afterLogout && authHeader?.startsWith("Bearer ")) {
74
- const { verifyAccessToken } = await import("./jwt");
75
- const payload = verifyAccessToken(authHeader.substring(7));
76
- if (payload) {
77
- ops.afterLogout(payload.userId).catch((err) => {
78
- logger.error("[AuthHooks] afterLogout error", {
79
- error: err instanceof Error ? err.message : err
80
- });
81
- });
82
- }
83
- }
84
-
85
- return c.json({ success: true });
86
- });
87
-
88
- /**
89
- * GET /auth/sessions
90
- * Get active refresh tokens (sessions) for the current user
91
- */
92
- router.get("/sessions", requireAuth, async (c) => {
93
- const userCtx = c.get("user") as { userId: string; roles?: string[] } | undefined;
94
- if (!userCtx) {
95
- throw ApiError.unauthorized("Not authenticated");
96
- }
97
-
98
- const currentRefreshToken = c.req.header("x-refresh-token") as string;
99
- const currentTokenHash = currentRefreshToken ? hashRefreshToken(currentRefreshToken) : null;
100
-
101
- const sessions = await authRepo.listRefreshTokensForUser(userCtx.userId);
102
-
103
- const mappedSessions = sessions.map((s) => ({
104
- id: s.id,
105
- userAgent: s.userAgent,
106
- ipAddress: s.ipAddress,
107
- createdAt: s.createdAt,
108
- isCurrentSession: currentTokenHash ? s.tokenHash === currentTokenHash : false
109
- }));
110
-
111
- return c.json({ sessions: mappedSessions });
112
- });
113
-
114
- /**
115
- * DELETE /auth/sessions
116
- * Delete all refresh tokens for the current user (remote logout every device)
117
- */
118
- router.delete("/sessions", requireAuth, async (c) => {
119
- const userCtx = c.get("user") as { userId: string; roles?: string[] } | undefined;
120
- if (!userCtx) {
121
- throw ApiError.unauthorized("Not authenticated");
122
- }
123
-
124
- await authRepo.deleteAllRefreshTokensForUser(userCtx.userId);
125
- return c.json({
126
- success: true,
127
- message: "All sessions revoked successfully"
128
- });
129
- });
130
-
131
- /**
132
- * DELETE /auth/sessions/:id
133
- * Delete a specific refresh token (remote logout)
134
- */
135
- router.delete("/sessions/:id", requireAuth, async (c) => {
136
- const userCtx = c.get("user") as { userId: string; roles?: string[] } | undefined;
137
- if (!userCtx) {
138
- throw ApiError.unauthorized("Not authenticated");
139
- }
140
-
141
- const id = c.req.param("id");
142
- if (!id) {
143
- throw ApiError.badRequest("Session ID is required", "INVALID_INPUT");
144
- }
145
-
146
- await authRepo.deleteRefreshTokenById(id, userCtx.userId);
147
- return c.json({
148
- success: true,
149
- message: "Session revoked successfully"
150
- });
151
- });
152
-
153
- /**
154
- * GET /auth/me
155
- * Get current authenticated user
156
- */
157
- router.get("/me", requireAuth, async (c) => {
158
- const userCtx = c.get("user") as { userId: string; roles?: string[] } | undefined;
159
- if (!userCtx) {
160
- throw ApiError.unauthorized("Not authenticated");
161
- }
162
-
163
- const result = await authRepo.getUserWithRoles(userCtx.userId);
164
- if (!result) {
165
- throw ApiError.notFound("User not found");
166
- }
167
-
168
- return c.json({
169
- user: {
170
- uid: result.user.id,
171
- email: result.user.email,
172
- displayName: result.user.displayName,
173
- photoURL: result.user.photoUrl,
174
- emailVerified: result.user.emailVerified,
175
- roles: result.roles.map((r) => r.id),
176
- metadata: result.user.metadata ?? {}
177
- }
178
- });
179
- });
180
-
181
- /**
182
- * PATCH /auth/me
183
- * Update current authenticated user profile
184
- */
185
- router.patch("/me", requireAuth, async (c) => {
186
- const userCtx = c.get("user") as { userId: string; roles?: string[] } | undefined;
187
- if (!userCtx) {
188
- throw ApiError.unauthorized("Not authenticated");
189
- }
190
-
191
- const { displayName, photoURL } = parseBody(updateProfileSchema, await c.req.json());
192
-
193
- const updatedUser = await authRepo.updateUser(userCtx.userId, {
194
- displayName: displayName !== undefined ? displayName : undefined,
195
- photoUrl: photoURL !== undefined ? photoURL : undefined
196
- });
197
-
198
- if (!updatedUser) {
199
- throw ApiError.notFound("User not found");
200
- }
201
-
202
- const result = await authRepo.getUserWithRoles(userCtx.userId);
203
- if (!result) {
204
- throw ApiError.notFound("User not found");
205
- }
206
-
207
- return c.json({
208
- user: {
209
- uid: result.user.id,
210
- email: result.user.email,
211
- displayName: result.user.displayName,
212
- photoURL: result.user.photoUrl,
213
- emailVerified: result.user.emailVerified,
214
- roles: result.roles.map((r) => r.id),
215
- metadata: result.user.metadata ?? {}
216
- }
217
- });
218
- });
219
-
220
- /**
221
- * GET /auth/config
222
- * Get public auth configuration
223
- */
224
- router.get("/config", defaultAuthLimiter, async (c) => {
225
- let needsSetup: boolean;
226
- if (config.isBootstrapCompleted) {
227
- needsSetup = !(await config.isBootstrapCompleted());
228
- } else {
229
- const allUsers = await authRepo.listUsers();
230
- needsSetup = allUsers.length === 0;
231
- }
232
-
233
- const registrationAllowed = needsSetup || !!config.allowRegistration;
234
- const enabledProviders = (config.oauthProviders || []).map((p) => p.id);
235
-
236
- return c.json({
237
- needsSetup,
238
- registrationEnabled: registrationAllowed,
239
- emailServiceEnabled: isEmailConfigured(),
240
- magicLinkEnabled: !!config.enableMagicLink && isEmailConfigured(),
241
- enabledProviders
242
- });
243
- });
244
-
245
- /**
246
- * POST /auth/anonymous
247
- * Create an anonymous user with temporary credentials
248
- */
249
- router.post("/anonymous", strictAuthLimiter, async (c) => {
250
- const anonId = randomBytes(16).toString("hex");
251
- const anonEmail = `anon_${anonId.slice(0, 8)}@anonymous.local`;
252
-
253
- let createData: CreateUserData = {
254
- email: anonEmail,
255
- emailVerified: false,
256
- isAnonymous: true
257
- };
258
-
259
- if (ops.beforeUserCreate) {
260
- createData = await ops.beforeUserCreate(createData);
261
- }
262
-
263
- const user = await authRepo.createUser(createData);
264
-
265
- // Assign default role (follow register route pattern, but never auto-admin)
266
- if (config.defaultRole) {
267
- await authRepo.assignDefaultRole(user.id, config.defaultRole);
268
- }
269
-
270
- const { roleIds, accessToken, refreshToken } = await createSessionAndTokens(
271
- user.id,
272
- c.req.header("user-agent") || "unknown",
273
- c.req.header("x-forwarded-for") || "unknown"
274
- );
275
-
276
- // Fire afterUserCreate hook
277
- if (ops.afterUserCreate) {
278
- ops.afterUserCreate(user).catch((err) => {
279
- logger.error("[AuthHooks] afterUserCreate error", {
280
- error: err instanceof Error ? err.message : err
281
- });
282
- });
283
- }
284
-
285
- // Fire onAuthenticated hook
286
- if (ops.onAuthenticated) {
287
- ops.onAuthenticated(user, "anonymous").catch((err) => {
288
- logger.error("[AuthHooks] onAuthenticated error", {
289
- error: err instanceof Error ? err.message : err
290
- });
291
- });
292
- }
293
-
294
- const authResponse = buildAuthResponse(user, roleIds, accessToken, refreshToken) as AuthResponsePayload;
295
- const finalResponse = await applyTransformHook(authResponse, "anonymous", c.req.raw, user.id);
296
- return c.json(finalResponse, 201);
297
- });
298
-
299
- /**
300
- * POST /auth/anonymous/link
301
- * Upgrade an anonymous user to a permanent account with email/password
302
- */
303
- router.post("/anonymous/link", requireAuth, async (c) => {
304
- const userCtx = c.get("user") as { userId: string; roles?: string[] } | undefined;
305
- if (!userCtx) {
306
- throw ApiError.unauthorized("Not authenticated");
307
- }
308
-
309
- const user = await authRepo.getUserById(userCtx.userId);
310
- if (!user?.isAnonymous) {
311
- throw ApiError.badRequest("User is not anonymous", "NOT_ANONYMOUS");
312
- }
313
-
314
- const { email, password } = parseBody(linkSchema, await c.req.json());
315
-
316
- // Validate password strength
317
- const passwordValidation = ops.validatePasswordStrength(password);
318
- if (!passwordValidation.valid) {
319
- throw ApiError.badRequest(passwordValidation.errors.join(". "), "WEAK_PASSWORD");
320
- }
321
-
322
- // Check if email is already taken
323
- const existingUser = await authRepo.getUserByEmail(email.toLowerCase());
324
- if (existingUser) {
325
- throw ApiError.conflict("Email already registered", "EMAIL_EXISTS");
326
- }
327
-
328
- // Hash password
329
- const passwordHash = await ops.hashPassword(password);
330
-
331
- // Update user: set email, password, remove anonymous flag
332
- const updatedUser = await authRepo.updateUser(user.id, {
333
- email: email.toLowerCase(),
334
- passwordHash,
335
- isAnonymous: false
336
- });
337
-
338
- if (!updatedUser) {
339
- throw ApiError.notFound("User not found");
340
- }
341
-
342
- // Generate new tokens with updated identity
343
- const { roleIds, accessToken, refreshToken } = await createSessionAndTokens(
344
- user.id,
345
- c.req.header("user-agent") || "unknown",
346
- c.req.header("x-forwarded-for") || "unknown"
347
- );
348
-
349
- const authResponse = buildAuthResponse(updatedUser, roleIds, accessToken, refreshToken) as AuthResponsePayload;
350
- const finalResponse = await applyTransformHook(authResponse, "anonymous", c.req.raw, user.id);
351
- return c.json(finalResponse);
352
- });
353
- }
@@ -1,73 +0,0 @@
1
- import type { OAuthProvider, OAuthProviderProfile } from "./interfaces";
2
- import { z } from "zod";
3
- import { logger } from "../utils/logger";
4
-
5
- /**
6
- * Creates a Slack OAuth Provider integration (OAuth 2.0 / "Sign in with Slack").
7
- * Uses the OpenID Connect flow with the "openid,email,profile" scopes.
8
- */
9
- export function createSlackProvider(config: { clientId: string; clientSecret: string }): OAuthProvider<{ code: string; redirectUri: string }> {
10
- return {
11
- id: "slack",
12
- schema: z.object({
13
- code: z.string().min(1, "Auth code is required"),
14
- redirectUri: z.string().url("Valid redirect URI is required")
15
- }),
16
- verify: async (payload: { code: string; redirectUri: string }): Promise<OAuthProviderProfile | null> => {
17
- try {
18
- const tokenResponse = await fetch("https://slack.com/api/openid.connect.token", {
19
- method: "POST",
20
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
21
- body: new URLSearchParams({
22
- client_id: config.clientId,
23
- client_secret: config.clientSecret,
24
- code: payload.code,
25
- redirect_uri: payload.redirectUri,
26
- grant_type: "authorization_code"
27
- })
28
- });
29
-
30
- if (!tokenResponse.ok) {
31
- logger.error("Failed to get Slack access token", { detail: await tokenResponse.text() });
32
- return null;
33
- }
34
-
35
- const tokenData = await tokenResponse.json() as { ok: boolean; access_token?: string; error?: string };
36
- if (!tokenData.ok || !tokenData.access_token) {
37
- logger.error("Slack token exchange failed", { detail: tokenData.error });
38
- return null;
39
- }
40
-
41
- const profileResponse = await fetch("https://slack.com/api/openid.connect.userInfo", {
42
- headers: { "Authorization": `Bearer ${tokenData.access_token}` }
43
- });
44
-
45
- if (!profileResponse.ok) {
46
- logger.error("Failed to get Slack user info", { detail: await profileResponse.text() });
47
- return null;
48
- }
49
-
50
- const p = await profileResponse.json() as {
51
- ok: boolean; sub: string; name?: string;
52
- email?: string; picture?: string; email_verified?: boolean;
53
- };
54
-
55
- if (!p.ok || !p.email) {
56
- logger.error("Slack user has no email");
57
- return null;
58
- }
59
-
60
- return {
61
- providerId: p.sub,
62
- email: p.email,
63
- displayName: p.name || null,
64
- photoUrl: p.picture || null,
65
- emailVerified: p.email_verified === true
66
- };
67
- } catch (error) {
68
- logger.error("Slack OAuth error", { error: error });
69
- return null;
70
- }
71
- }
72
- };
73
- }
@@ -1,69 +0,0 @@
1
- import type { OAuthProvider, OAuthProviderProfile } from "./interfaces";
2
- import { z } from "zod";
3
- import { logger } from "../utils/logger";
4
-
5
- /**
6
- * Creates a Spotify OAuth Provider integration.
7
- * Uses the authorization code flow with the "user-read-email" scope.
8
- */
9
- export function createSpotifyProvider(config: { clientId: string; clientSecret: string }): OAuthProvider<{ code: string; redirectUri: string }> {
10
- return {
11
- id: "spotify",
12
- schema: z.object({
13
- code: z.string().min(1, "Auth code is required"),
14
- redirectUri: z.string().url("Valid redirect URI is required")
15
- }),
16
- verify: async (payload: { code: string; redirectUri: string }): Promise<OAuthProviderProfile | null> => {
17
- try {
18
- const basicAuth = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64");
19
-
20
- const tokenResponse = await fetch("https://accounts.spotify.com/api/token", {
21
- method: "POST",
22
- headers: {
23
- "Content-Type": "application/x-www-form-urlencoded",
24
- "Authorization": `Basic ${basicAuth}`
25
- },
26
- body: new URLSearchParams({
27
- grant_type: "authorization_code",
28
- code: payload.code,
29
- redirect_uri: payload.redirectUri
30
- })
31
- });
32
-
33
- if (!tokenResponse.ok) {
34
- logger.error("Failed to get Spotify access token", { detail: await tokenResponse.text() });
35
- return null;
36
- }
37
-
38
- const tokenData = await tokenResponse.json() as { access_token: string };
39
-
40
- const profileResponse = await fetch("https://api.spotify.com/v1/me", {
41
- headers: { "Authorization": `Bearer ${tokenData.access_token}` }
42
- });
43
-
44
- if (!profileResponse.ok) {
45
- logger.error("Failed to get Spotify user info", { detail: await profileResponse.text() });
46
- return null;
47
- }
48
-
49
- const p = await profileResponse.json() as {
50
- id: string; display_name?: string | null;
51
- email?: string; images?: Array<{ url: string }>;
52
- };
53
-
54
- if (!p.email) { logger.error("Spotify user has no email"); return null; }
55
-
56
- return {
57
- providerId: p.id,
58
- email: p.email,
59
- displayName: p.display_name || null,
60
- photoUrl: p.images?.[0]?.url || null,
61
- emailVerified: true
62
- };
63
- } catch (error) {
64
- logger.error("Spotify OAuth error", { error: error });
65
- return null;
66
- }
67
- }
68
- };
69
- }
@@ -1,128 +0,0 @@
1
- import type { OAuthProvider, OAuthProviderProfile } from "./interfaces";
2
- import { z } from "zod";
3
- import { logger } from "../utils/logger";
4
-
5
- /**
6
- * Creates a Twitter/X OAuth 2.0 Provider integration.
7
- *
8
- * Uses OAuth 2.0 with PKCE (authorization code flow). The frontend must include
9
- * the PKCE `code_verifier` when sending the authorization code.
10
- *
11
- * Twitter API v2 requires the "tweet.read" and "users.read" scopes at minimum,
12
- * plus "offline.access" if refresh tokens are needed on Twitter's side.
13
- */
14
- export function createTwitterProvider(config: { clientId: string; clientSecret: string }): OAuthProvider<{
15
- code: string;
16
- redirectUri: string;
17
- codeVerifier: string;
18
- }> {
19
- return {
20
- id: "twitter",
21
- schema: z.object({
22
- code: z.string().min(1, "Auth code is required"),
23
- redirectUri: z.string().url("Valid redirect URI is required"),
24
- codeVerifier: z.string().min(1, "PKCE code verifier is required")
25
- }),
26
- verify: async (payload: {
27
- code: string;
28
- redirectUri: string;
29
- codeVerifier: string;
30
- }): Promise<OAuthProviderProfile | null> => {
31
- try {
32
- // Twitter OAuth 2.0 uses Basic auth for the token endpoint
33
- const basicAuth = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64");
34
-
35
- // Exchange code for access token
36
- const tokenResponse = await fetch("https://api.twitter.com/2/oauth2/token", {
37
- method: "POST",
38
- headers: {
39
- "Content-Type": "application/x-www-form-urlencoded",
40
- "Authorization": `Basic ${basicAuth}`
41
- },
42
- body: new URLSearchParams({
43
- code: payload.code,
44
- grant_type: "authorization_code",
45
- redirect_uri: payload.redirectUri,
46
- code_verifier: payload.codeVerifier
47
- })
48
- });
49
-
50
- if (!tokenResponse.ok) {
51
- logger.error("Failed to get Twitter access token", { detail: await tokenResponse.text() });
52
- return null;
53
- }
54
-
55
- const tokenData = await tokenResponse.json() as { access_token: string };
56
- const accessToken = tokenData.access_token;
57
-
58
- // Fetch user profile from Twitter API v2
59
- const profileResponse = await fetch(
60
- "https://api.twitter.com/2/users/me?user.fields=id,name,username,profile_image_url",
61
- {
62
- headers: { "Authorization": `Bearer ${accessToken}` }
63
- }
64
- );
65
-
66
- if (!profileResponse.ok) {
67
- logger.error("Failed to get Twitter user info", { detail: await profileResponse.text() });
68
- return null;
69
- }
70
-
71
- const profileResult = await profileResponse.json() as {
72
- data: {
73
- id: string;
74
- name: string;
75
- username: string;
76
- profile_image_url?: string;
77
- };
78
- };
79
-
80
- const profileData = profileResult.data;
81
-
82
- // Twitter does NOT expose email via API v2 by default.
83
- // It requires elevated access. If we can't get an email, we
84
- // generate a placeholder email — the user can update it later.
85
- // For apps with elevated access, fetch from v1.1 endpoint.
86
- let email: string | null = null;
87
- let emailVerified = false;
88
- try {
89
- const emailResponse = await fetch(
90
- "https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true",
91
- {
92
- headers: { "Authorization": `Bearer ${accessToken}` }
93
- }
94
- );
95
- if (emailResponse.ok) {
96
- const emailData = await emailResponse.json() as { email?: string };
97
- email = emailData.email || null;
98
- if (email) {
99
- emailVerified = true;
100
- }
101
- }
102
- } catch {
103
- // Elevated access not available — fall through
104
- }
105
-
106
- if (!email) {
107
- // Construct a deterministic placeholder email from the Twitter user ID.
108
- // This allows the account to be created and linked; the user should
109
- // update their email through the profile settings.
110
- email = `${profileData.id}@twitter.placeholder.rebase`;
111
- // Placeholder emails are NOT verified — prevents auto-linking to existing accounts
112
- emailVerified = false;
113
- }
114
-
115
- return {
116
- providerId: profileData.id,
117
- email,
118
- displayName: profileData.name || profileData.username || null,
119
- photoUrl: profileData.profile_image_url?.replace("_normal", "_400x400") || null,
120
- emailVerified
121
- };
122
- } catch (error) {
123
- logger.error("Twitter OAuth error", { error: error });
124
- return null;
125
- }
126
- }
127
- };
128
- }
@@ -1,20 +0,0 @@
1
- import { CollectionRegistry } from "@rebasepro/common";
2
- import { CollectionRegistryInterface } from "../db/interfaces";
3
- import { EntityCollection } from "@rebasepro/types";
4
-
5
- /**
6
- * Backend-agnostic collection registry.
7
- * Satisfies CollectionRegistryInterface through inheritance from CollectionRegistry.
8
- */
9
- export class BackendCollectionRegistry extends CollectionRegistry implements CollectionRegistryInterface {
10
-
11
- /**
12
- * Get the available relation keys for a given collection path.
13
- * Maps from the collection's relation property names to the relation names.
14
- */
15
- getRelationKeysForCollection(collectionPath: string): string[] {
16
- const collection = this.getCollectionByPath(collectionPath) as (EntityCollection & { relations?: { relationName?: string, localKey?: string }[] }) | undefined;
17
- if (!collection?.relations) return [];
18
- return collection.relations.map(r => (r.relationName || r.localKey || "") as string).filter(Boolean);
19
- }
20
- }