@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
package/src/auth/mfa.ts DELETED
@@ -1,161 +0,0 @@
1
- /**
2
- * TOTP (Time-based One-Time Password) implementation.
3
- *
4
- * Pure Node.js crypto implementation — no external dependencies required.
5
- * Implements RFC 6238 (TOTP) and RFC 4226 (HOTP).
6
- */
7
-
8
- import { createHmac, randomBytes, createHash } from "crypto";
9
-
10
- // ─── Base32 Encoding/Decoding ────────────────────────────────────────────────
11
-
12
- const BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
13
-
14
- /**
15
- * Encode a Buffer to Base32 string (RFC 4648)
16
- */
17
- export function base32Encode(buffer: Buffer): string {
18
- let bits = 0;
19
- let value = 0;
20
- let output = "";
21
-
22
- for (let i = 0; i < buffer.length; i++) {
23
- value = (value << 8) | buffer[i];
24
- bits += 8;
25
-
26
- while (bits >= 5) {
27
- output += BASE32_ALPHABET[(value >>> (bits - 5)) & 31];
28
- bits -= 5;
29
- }
30
- }
31
-
32
- if (bits > 0) {
33
- output += BASE32_ALPHABET[(value << (5 - bits)) & 31];
34
- }
35
-
36
- return output;
37
- }
38
-
39
- /**
40
- * Decode a Base32 string to Buffer
41
- */
42
- export function base32Decode(encoded: string): Buffer {
43
- const cleanInput = encoded.replace(/=+$/, "").toUpperCase();
44
- const bytes: number[] = [];
45
- let bits = 0;
46
- let value = 0;
47
-
48
- for (let i = 0; i < cleanInput.length; i++) {
49
- const index = BASE32_ALPHABET.indexOf(cleanInput[i]);
50
- if (index === -1) continue;
51
-
52
- value = (value << 5) | index;
53
- bits += 5;
54
-
55
- if (bits >= 8) {
56
- bytes.push((value >>> (bits - 8)) & 255);
57
- bits -= 8;
58
- }
59
- }
60
-
61
- return Buffer.from(bytes);
62
- }
63
-
64
- // ─── HOTP/TOTP ───────────────────────────────────────────────────────────────
65
-
66
- /**
67
- * Generate an HOTP value per RFC 4226
68
- */
69
- function generateHotp(secret: Buffer, counter: bigint): string {
70
- const hmac = createHmac("sha1", secret);
71
- const counterBuffer = Buffer.alloc(8);
72
- counterBuffer.writeBigInt64BE(counter);
73
- hmac.update(counterBuffer);
74
- const hash = hmac.digest();
75
-
76
- const offset = hash[hash.length - 1] & 0x0f;
77
- const code =
78
- (((hash[offset] & 0x7f) << 24) |
79
- (hash[offset + 1] << 16) |
80
- (hash[offset + 2] << 8) |
81
- hash[offset + 3]) %
82
- 1000000;
83
-
84
- return code.toString().padStart(6, "0");
85
- }
86
-
87
- /**
88
- * Generate a TOTP value for the current time step
89
- */
90
- export function generateTotp(secret: Buffer, timeStep = 30): string {
91
- const counter = BigInt(Math.floor(Date.now() / 1000 / timeStep));
92
- return generateHotp(secret, counter);
93
- }
94
-
95
- /**
96
- * Verify a TOTP token with a configurable time window
97
- *
98
- * @param secret - The shared secret as a Buffer
99
- * @param token - The 6-digit TOTP code to verify
100
- * @param window - Number of time steps to check on each side (default: 1)
101
- * @returns true if the token is valid within the window
102
- */
103
- export function verifyTotp(secret: Buffer, token: string, window = 1): boolean {
104
- const timeStep = 30;
105
- const counter = BigInt(Math.floor(Date.now() / 1000 / timeStep));
106
- for (let i = -window; i <= window; i++) {
107
- if (generateHotp(secret, counter + BigInt(i)) === token) {
108
- return true;
109
- }
110
- }
111
- return false;
112
- }
113
-
114
- // ─── Secret Generation ───────────────────────────────────────────────────────
115
-
116
- /**
117
- * Generate a new TOTP secret and return the setup information
118
- *
119
- * @param issuer - The issuer name (app name) for the QR code
120
- * @param accountName - The account identifier (usually email)
121
- * @returns Object with base32 secret and otpauth URI
122
- */
123
- export function generateTotpSecret(issuer: string, accountName: string): {
124
- secret: string;
125
- uri: string;
126
- } {
127
- // Generate a 20-byte random secret (160 bits, recommended by RFC 4226)
128
- const secretBuffer = randomBytes(20);
129
- const secret = base32Encode(secretBuffer);
130
-
131
- // Build otpauth:// URI for QR code generation
132
- const encodedIssuer = encodeURIComponent(issuer);
133
- const encodedAccount = encodeURIComponent(accountName);
134
- const uri = `otpauth://totp/${encodedIssuer}:${encodedAccount}?secret=${secret}&issuer=${encodedIssuer}&algorithm=SHA1&digits=6&period=30`;
135
-
136
- return { secret,
137
- uri };
138
- }
139
-
140
- // ─── Recovery Codes ──────────────────────────────────────────────────────────
141
-
142
- /**
143
- * Generate a set of one-time recovery codes
144
- *
145
- * @param count - Number of recovery codes to generate (default: 10)
146
- * @returns Array of formatted recovery code strings (e.g. "A1B2C-D3E4F")
147
- */
148
- export function generateRecoveryCodes(count = 10): string[] {
149
- return Array.from({ length: count }, () => {
150
- const raw = randomBytes(5).toString("hex").toUpperCase();
151
- const parts = raw.match(/.{1,5}/g);
152
- return parts ? parts.join("-") : raw;
153
- });
154
- }
155
-
156
- /**
157
- * Hash a recovery code for storage
158
- */
159
- export function hashRecoveryCode(code: string): string {
160
- return createHash("sha256").update(code.replace(/-/g, "").toUpperCase()).digest("hex");
161
- }
@@ -1,90 +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 Microsoft / Entra ID (Azure AD) OAuth Provider integration.
7
- *
8
- * Supports both personal Microsoft accounts and work/school (Azure AD) accounts
9
- * via the "common" tenant endpoint. Uses the authorization code flow.
10
- */
11
- export function createMicrosoftProvider(config: {
12
- clientId: string;
13
- clientSecret: string;
14
- /** Tenant ID. Defaults to "common" which allows both personal and organizational accounts. */
15
- tenantId?: string;
16
- }): OAuthProvider<{ code: string; redirectUri: string }> {
17
- const tenantId = config.tenantId || "common";
18
-
19
- return {
20
- id: "microsoft",
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
- }),
25
- verify: async (payload: { code: string; redirectUri: string }): Promise<OAuthProviderProfile | null> => {
26
- try {
27
- // Exchange code for access token
28
- const tokenResponse = await fetch(
29
- `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
30
- {
31
- method: "POST",
32
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
33
- body: new URLSearchParams({
34
- client_id: config.clientId,
35
- client_secret: config.clientSecret,
36
- code: payload.code,
37
- redirect_uri: payload.redirectUri,
38
- grant_type: "authorization_code",
39
- scope: "openid profile email User.Read"
40
- })
41
- }
42
- );
43
-
44
- if (!tokenResponse.ok) {
45
- logger.error("Failed to get Microsoft access token", { detail: await tokenResponse.text() });
46
- return null;
47
- }
48
-
49
- const tokenData = await tokenResponse.json() as { access_token: string };
50
- const accessToken = tokenData.access_token;
51
-
52
- // Fetch user profile from Microsoft Graph
53
- const profileResponse = await fetch("https://graph.microsoft.com/v1.0/me", {
54
- headers: { "Authorization": `Bearer ${accessToken}` }
55
- });
56
-
57
- if (!profileResponse.ok) {
58
- logger.error("Failed to get Microsoft user info", { detail: await profileResponse.text() });
59
- return null;
60
- }
61
-
62
- const profileData = await profileResponse.json() as {
63
- id: string;
64
- displayName?: string | null;
65
- mail?: string | null;
66
- userPrincipalName?: string | null;
67
- };
68
-
69
- const email = profileData.mail || profileData.userPrincipalName;
70
- if (!email) {
71
- logger.error("Microsoft user has no email");
72
- return null;
73
- }
74
-
75
- // Attempt to fetch profile photo URL (Graph returns binary, not a URL).
76
- // We skip this and let the frontend use the Microsoft Graph photo endpoint.
77
- return {
78
- providerId: profileData.id,
79
- email,
80
- displayName: profileData.displayName || null,
81
- photoUrl: null,
82
- emailVerified: true
83
- };
84
- } catch (error) {
85
- logger.error("Microsoft OAuth error", { error: error });
86
- return null;
87
- }
88
- }
89
- };
90
- }
@@ -1,369 +0,0 @@
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
- import { scopeDataDriver } from "./rls-scope";
6
- import { safeCompare } from "./crypto-utils";
7
- import { isApiKeyToken, validateApiKey } from "./api-keys/api-key-middleware";
8
- import type { ApiKeyStore } from "./api-keys/api-key-store";
9
- import { logger } from "../utils/logger";
10
-
11
- /**
12
- * Result from a custom auth validator.
13
- * - `false`/`null`/`undefined` = not authenticated
14
- * - `true` = authenticated as default user
15
- * - object with `userId` or `uid` = authenticated with user info
16
- */
17
- export type AuthResult = boolean | null | undefined | { userId?: string; uid?: string; roles?: string[]; [key: string]: unknown };
18
-
19
- /**
20
- * Options for creating an auth middleware via createAuthMiddleware()
21
- */
22
- export interface AuthMiddlewareOptions {
23
- /** DataDriver to scope via withAuth() for RLS */
24
- driver: DataDriver;
25
- /**
26
- * Optional per-request driver resolver for multi-data-source backends.
27
- * Given the request context, returns the unscoped delegate to use (e.g.
28
- * Postgres vs Mongo, picked by the request's collection data source).
29
- * When omitted, `driver` is used for every request.
30
- */
31
- resolveDriver?: (c: Context<HonoEnv>) => DataDriver;
32
- /**
33
- * If true, return 401 when no valid token is present.
34
- *
35
- * **Defaults to `true` (secure by default).** Set to `false` only for
36
- * intentionally public endpoints where access control is fully delegated
37
- * to Postgres Row-Level Security policies.
38
- */
39
- requireAuth?: boolean;
40
- /** Optional custom validator (for non-JWT auth, e.g. Firebase Auth) */
41
- validator?: (c: Context<HonoEnv>) => Promise<AuthResult>;
42
- /**
43
- * A static secret key for server-to-server / script authentication.
44
- *
45
- * When a request sends `Authorization: Bearer <key>` and the key matches
46
- * this value, the request is granted admin-level access (uid: `service`,
47
- * roles: `["admin"]`) **without** JWT verification. The driver is scoped
48
- * via `withAuth()` with the service identity.
49
- *
50
- * This is the Rebase equivalent of a Firebase Service Account key.
51
- * Set via `REBASE_SERVICE_KEY` in `.env` and pass through the backend config.
52
- *
53
- * **Security:** The comparison uses constant-time equality to prevent
54
- * timing attacks. The key must be at least 32 characters.
55
- */
56
- serviceKey?: string;
57
- /**
58
- * API key store for authenticating `rk_` prefixed tokens.
59
- * When set, tokens starting with `rk_` are validated against the
60
- * database instead of being treated as JWTs.
61
- */
62
- apiKeyStore?: ApiKeyStore;
63
- }
64
-
65
- /**
66
- * Hono middleware that requires a valid JWT token
67
- * Returns 401 if token is missing or invalid
68
- */
69
- export const requireAuth: MiddlewareHandler<HonoEnv> = async (
70
- c,
71
- next
72
- ) => {
73
- const authHeader = c.req.header("authorization");
74
- const queryToken = c.req.query("token");
75
- const hasBearer = authHeader && authHeader.startsWith("Bearer ");
76
-
77
- if (!hasBearer && !queryToken) {
78
- return c.json({
79
- error: {
80
- message: "Authorization header or token query parameter missing or invalid",
81
- code: "UNAUTHORIZED"
82
- }
83
- }, 401);
84
- }
85
-
86
- const token = hasBearer ? authHeader!.substring(7) : queryToken!;
87
- const payload = verifyAccessToken(token);
88
-
89
- if (!payload) {
90
- return c.json({
91
- error: {
92
- message: "Invalid or expired token",
93
- code: "UNAUTHORIZED"
94
- }
95
- }, 401);
96
- }
97
-
98
- c.set("user", payload);
99
- return next();
100
- };
101
-
102
- /**
103
- * Factory that creates a requireAuth middleware with optional service key support.
104
- *
105
- * When `serviceKey` is provided, the middleware will check if the Bearer token
106
- * matches the service key using constant-time comparison. If it matches, the
107
- * request is authenticated as a service user with admin privileges.
108
- *
109
- * This allows admin routes (which use standalone requireAuth + requireAdmin)
110
- * to be accessed via service keys for scripts and server-to-server calls.
111
- */
112
- export function createRequireAuth(options?: { serviceKey?: string }): MiddlewareHandler<HonoEnv> {
113
- if (!options?.serviceKey) return requireAuth;
114
-
115
- const key = options.serviceKey;
116
- return async (c, next) => {
117
- const authHeader = c.req.header("authorization");
118
- const queryToken = c.req.query("token");
119
- const hasBearer = authHeader && authHeader.startsWith("Bearer ");
120
-
121
- if (!hasBearer && !queryToken) {
122
- return c.json({
123
- error: {
124
- message: "Authorization header or token query parameter missing or invalid",
125
- code: "UNAUTHORIZED"
126
- }
127
- }, 401);
128
- }
129
-
130
- const token = hasBearer ? authHeader!.substring(7) : queryToken!;
131
-
132
- // Check service key first (constant-time comparison)
133
- if (safeCompare(token, key)) {
134
- c.set("user", { userId: "service",
135
- roles: ["admin"] } as AccessTokenPayload);
136
- return next();
137
- }
138
-
139
- // Fall back to JWT verification
140
- const payload = verifyAccessToken(token);
141
-
142
- if (!payload) {
143
- return c.json({
144
- error: {
145
- message: "Invalid or expired token",
146
- code: "UNAUTHORIZED"
147
- }
148
- }, 401);
149
- }
150
-
151
- c.set("user", payload);
152
- return next();
153
- };
154
- }
155
-
156
- /**
157
- * Middleware that requires the user to have an admin or schema-admin role.
158
- * Must be used AFTER requireAuth or on a route where user is guaranteed.
159
- */
160
- export const requireAdmin: MiddlewareHandler<HonoEnv> = async (
161
- c,
162
- next
163
- ) => {
164
- const user = c.get("user");
165
- if (!user) {
166
- return c.json({
167
- error: {
168
- message: "User not authenticated. requireAuth middleware is missing?",
169
- code: "UNAUTHORIZED"
170
- }
171
- }, 401);
172
- }
173
-
174
- const roles = (typeof user === "object" && user !== null && "roles" in user) ? (user.roles || []) : [];
175
- const isAdmin = roles.some((role: string) => {
176
- return role === "admin" || role === "schema-admin";
177
- });
178
-
179
- if (!isAdmin) {
180
- return c.json({
181
- error: {
182
- message: "Admin privileges required for this operation",
183
- code: "FORBIDDEN"
184
- }
185
- }, 403);
186
- }
187
-
188
- return next();
189
- };
190
-
191
-
192
- /**
193
- * Middleware that optionally extracts user from JWT
194
- * Does not return 401 if token is missing - allows anonymous access
195
- */
196
- export const optionalAuth: MiddlewareHandler<HonoEnv> = async (
197
- c,
198
- next
199
- ) => {
200
- const authHeader = c.req.header("authorization");
201
- const queryToken = c.req.query("token");
202
- const hasBearer = authHeader && authHeader.startsWith("Bearer ");
203
-
204
- if (hasBearer || queryToken) {
205
- const token = hasBearer ? authHeader!.substring(7) : queryToken!;
206
- const payload = verifyAccessToken(token);
207
- if (payload) {
208
- c.set("user", payload);
209
- }
210
- }
211
-
212
- return next();
213
- };
214
-
215
- /**
216
- * Extract user from token - for WebSocket authentication
217
- */
218
- export function extractUserFromToken(token: string): AccessTokenPayload | null {
219
- return verifyAccessToken(token);
220
- }
221
-
222
- /**
223
- * Create a configurable auth middleware that handles:
224
- * 1. Token extraction (via custom validator or JWT Bearer token)
225
- * 2. RLS-scoped DataDriver via withAuth()
226
- * 3. Enforcement (401 when requireAuth is true and no user)
227
- *
228
- * **Secure by default:** `requireAuth` defaults to `true`. Anonymous
229
- * access is only allowed when the developer explicitly opts out by
230
- * setting `requireAuth: false`, indicating that Postgres RLS policies
231
- * fully control access.
232
- *
233
- * **Fail-closed:** The raw unscoped driver is never placed in the
234
- * request context. Every code path either scopes via `withAuth()` or
235
- * rejects the request. This prevents silent RLS bypass.
236
- *
237
- * This is the single source of truth for HTTP auth in Rebase.
238
- * Use this instead of manually parsing tokens in route handlers.
239
- */
240
-
241
- export function createAuthMiddleware(options: AuthMiddlewareOptions): MiddlewareHandler<HonoEnv> {
242
- const { driver: baseDriver, resolveDriver, requireAuth: enforceAuth = true, validator, serviceKey, apiKeyStore } = options;
243
-
244
- return async (c, next) => {
245
- // Pick the per-request delegate (multi-data-source) before scoping.
246
- const driver = resolveDriver ? resolveDriver(c) : baseDriver;
247
- if (validator) {
248
- // Custom validator path (e.g., Firebase Auth, API keys)
249
- try {
250
- const authResult = await validator(c);
251
- if (authResult && typeof authResult === "object") {
252
- const id = ("userId" in authResult ? authResult.userId : undefined)
253
- || ("uid" in authResult ? authResult.uid : undefined);
254
- if (id) {
255
- const roles = authResult.roles || [];
256
- c.set("user", { userId: id,
257
- roles });
258
- const user = { uid: id,
259
- roles,
260
- ...authResult };
261
- c.set("driver", await scopeDataDriver(driver, user));
262
- } else {
263
- // Validator returned an object but without an ID — scope as anon
264
- c.set("driver", await scopeDataDriver(driver, { uid: "anon",
265
- roles: ["anon"] }));
266
- }
267
- } else if (authResult === true) {
268
- c.set("user", { userId: "default",
269
- roles: [] });
270
- c.set("driver", await scopeDataDriver(driver, { uid: "default",
271
- roles: [] }));
272
- } else {
273
- // Not authenticated — scope as anon so RLS can evaluate.
274
- // Fail closed: if anon scoping fails, reject instead of
275
- // falling back to the raw driver.
276
- c.set("driver", await scopeDataDriver(driver, { uid: "anon",
277
- roles: ["anon"] }));
278
- }
279
- } catch (error) {
280
- return c.json({ error: { message: "Unauthorized",
281
- code: "UNAUTHORIZED" } }, 401);
282
- }
283
- } else {
284
- // Default JWT path (with optional service key support)
285
- const authHeader = c.req.header("authorization");
286
- const queryToken = c.req.query("token");
287
- const hasBearer = authHeader && authHeader.startsWith("Bearer ");
288
-
289
- if (hasBearer || queryToken) {
290
- const token = hasBearer ? authHeader!.substring(7) : queryToken!;
291
-
292
- // ── Service Key check ──────────────────────────────────
293
- // Check BEFORE JWT verification. Service keys are static
294
- // secrets (like Firebase SA keys) that grant admin access
295
- // for scripts, cron jobs, and server-to-server calls.
296
- if (serviceKey && safeCompare(token, serviceKey)) {
297
- const serviceUser: AccessTokenPayload = {
298
- userId: "service",
299
- roles: ["admin"]
300
- };
301
- c.set("user", serviceUser);
302
- try {
303
- c.set("driver", await scopeDataDriver(driver, {
304
- uid: "service",
305
- roles: ["admin"]
306
- }));
307
- } catch (error) {
308
- logger.error("[AUTH] RLS scoping failed for service key", { error: error });
309
- return c.json({ error: { message: "Internal authentication error",
310
- code: "INTERNAL_ERROR" } }, 500);
311
- }
312
- } else if (apiKeyStore && isApiKeyToken(token)) {
313
- // ── API Key verification ──────────────────────────────
314
- // Tokens starting with `rk_` are validated against the
315
- // api_keys table instead of JWT verification.
316
- const result = await validateApiKey(c, token, {
317
- store: apiKeyStore,
318
- driver
319
- });
320
- if (result !== true) {
321
- return result;
322
- }
323
- } else {
324
- // ── JWT verification ───────────────────────────────────
325
- const payload = extractUserFromToken(token);
326
-
327
- if (payload) {
328
- c.set("user", payload);
329
- try {
330
- const user = { uid: payload.userId,
331
- roles: payload.roles };
332
- c.set("driver", await scopeDataDriver(driver, user));
333
- } catch (error) {
334
- // withAuth() failed for a valid token — reject (fail closed)
335
- logger.error("[AUTH] RLS scoping failed for authenticated user", { error: error });
336
- return c.json({ error: { message: "Internal authentication error",
337
- code: "INTERNAL_ERROR" } }, 500);
338
- }
339
- } else {
340
- // Token present but invalid — always reject.
341
- // Providing a malformed token should never grant access,
342
- // regardless of requireAuth setting.
343
- return c.json({ error: { message: "Invalid or expired token",
344
- code: "UNAUTHORIZED" } }, 401);
345
- }
346
- }
347
- } else {
348
- // No token provided — scope as anon for RLS evaluation.
349
- // Fail closed: if anon scoping fails, return 500 rather
350
- // than silently proceeding with an unscoped driver.
351
- try {
352
- c.set("driver", await scopeDataDriver(driver, { uid: "anon",
353
- roles: ["anon"] }));
354
- } catch (error) {
355
- logger.error("[AUTH] Failed to create anon-scoped driver", { error: error });
356
- return c.json({ error: { message: "Server configuration error",
357
- code: "INTERNAL_ERROR" } }, 500);
358
- }
359
- }
360
- }
361
-
362
- if (enforceAuth && !c.get("user")) {
363
- return c.json({ error: { message: "Unauthorized: Authentication required",
364
- code: "UNAUTHORIZED" } }, 401);
365
- }
366
-
367
- return next();
368
- };
369
- }
@@ -1,77 +0,0 @@
1
- import { scrypt, randomBytes, timingSafeEqual } from "crypto";
2
- import { promisify } from "util";
3
-
4
- const scryptAsync = promisify(scrypt);
5
-
6
- // Scrypt parameters (recommended values for 2024+)
7
- const SALT_LENGTH = 32;
8
- const KEY_LENGTH = 64;
9
- const SCRYPT_PARAMS = { N: 16384,
10
- r: 8,
11
- p: 1 };
12
-
13
- export interface PasswordValidationResult {
14
- valid: boolean;
15
- errors: string[];
16
- }
17
-
18
- /**
19
- * Password requirements:
20
- * - Minimum 8 characters
21
- * - At least 1 uppercase letter
22
- * - At least 1 lowercase letter
23
- * - At least 1 number
24
- */
25
- export function validatePasswordStrength(password: string): PasswordValidationResult {
26
- const errors: string[] = [];
27
-
28
- if (password.length < 8) {
29
- errors.push("Password must be at least 8 characters long");
30
- }
31
-
32
- if (!/[A-Z]/.test(password)) {
33
- errors.push("Password must contain at least one uppercase letter");
34
- }
35
-
36
- if (!/[a-z]/.test(password)) {
37
- errors.push("Password must contain at least one lowercase letter");
38
- }
39
-
40
- if (!/[0-9]/.test(password)) {
41
- errors.push("Password must contain at least one number");
42
- }
43
-
44
- return {
45
- valid: errors.length === 0,
46
- errors
47
- };
48
- }
49
-
50
- /**
51
- * Hash a password using Node's built-in scrypt
52
- * Returns format: salt:hash (both hex encoded)
53
- */
54
- export async function hashPassword(password: string): Promise<string> {
55
- const salt = randomBytes(SALT_LENGTH);
56
- const derivedKey = await scryptAsync(password, salt, KEY_LENGTH) as Buffer;
57
- return `${salt.toString("hex")}:${derivedKey.toString("hex")}`;
58
- }
59
-
60
- /**
61
- * Verify a password against a scrypt hash
62
- * Expects format: salt:hash (both hex encoded)
63
- */
64
- export async function verifyPassword(password: string, storedHash: string): Promise<boolean> {
65
- const [saltHex, hashHex] = storedHash.split(":");
66
- if (!saltHex || !hashHex) {
67
- return false;
68
- }
69
-
70
- const salt = Buffer.from(saltHex, "hex");
71
- const storedKey = Buffer.from(hashHex, "hex");
72
-
73
- const derivedKey = await scryptAsync(password, salt, KEY_LENGTH) as Buffer;
74
-
75
- // Use timing-safe comparison to prevent timing attacks
76
- return timingSafeEqual(derivedKey, storedKey);
77
- }