@sixthsummit/ship-api 0.0.0-stage → 4.1.1

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 (556) hide show
  1. package/CHANGELOG.md +605 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +4 -0
  4. package/README.md +58 -2
  5. package/cli/commands/add.js +537 -0
  6. package/cli/commands/contract.js +2 -0
  7. package/cli/commands/create.js +250 -0
  8. package/cli/commands/dev.js +227 -0
  9. package/cli/commands/doctor.js +286 -0
  10. package/cli/commands/generate.js +937 -0
  11. package/cli/commands/graph.js +2 -0
  12. package/cli/commands/migrate-orm.js +1171 -0
  13. package/cli/commands/update.js +37 -0
  14. package/cli/ship-api.js +285 -0
  15. package/lib/app/auth/AuthProvider.d.ts +91 -0
  16. package/lib/app/auth/AuthProvider.d.ts.map +1 -0
  17. package/lib/app/auth/AuthProvider.js +13 -0
  18. package/lib/app/auth/AuthProvider.js.map +1 -0
  19. package/lib/app/auth/adapters/firebase.d.ts +79 -0
  20. package/lib/app/auth/adapters/firebase.d.ts.map +1 -0
  21. package/lib/app/auth/adapters/firebase.js +122 -0
  22. package/lib/app/auth/adapters/firebase.js.map +1 -0
  23. package/lib/app/auth/adapters/jwtCookie.d.ts +55 -0
  24. package/lib/app/auth/adapters/jwtCookie.d.ts.map +1 -0
  25. package/lib/app/auth/adapters/jwtCookie.js +120 -0
  26. package/lib/app/auth/adapters/jwtCookie.js.map +1 -0
  27. package/lib/app/auth/index.d.ts +8 -0
  28. package/lib/app/auth/index.d.ts.map +1 -0
  29. package/lib/app/auth/index.js +7 -0
  30. package/lib/app/auth/index.js.map +1 -0
  31. package/lib/app/auth/jwt/AuthFlow.d.ts +94 -0
  32. package/lib/app/auth/jwt/AuthFlow.d.ts.map +1 -0
  33. package/lib/app/auth/jwt/AuthFlow.js +127 -0
  34. package/lib/app/auth/jwt/AuthFlow.js.map +1 -0
  35. package/lib/app/auth/jwt/DrizzleRefreshTokenStore.d.ts +80 -0
  36. package/lib/app/auth/jwt/DrizzleRefreshTokenStore.d.ts.map +1 -0
  37. package/lib/app/auth/jwt/DrizzleRefreshTokenStore.js +134 -0
  38. package/lib/app/auth/jwt/DrizzleRefreshTokenStore.js.map +1 -0
  39. package/lib/app/auth/jwt/JwtSigner.d.ts +123 -0
  40. package/lib/app/auth/jwt/JwtSigner.d.ts.map +1 -0
  41. package/lib/app/auth/jwt/JwtSigner.js +118 -0
  42. package/lib/app/auth/jwt/JwtSigner.js.map +1 -0
  43. package/lib/app/auth/jwt/RefreshTokenStore.d.ts +97 -0
  44. package/lib/app/auth/jwt/RefreshTokenStore.d.ts.map +1 -0
  45. package/lib/app/auth/jwt/RefreshTokenStore.js +122 -0
  46. package/lib/app/auth/jwt/RefreshTokenStore.js.map +1 -0
  47. package/lib/app/auth/permissionDefinition.d.ts +20 -0
  48. package/lib/app/auth/permissionDefinition.d.ts.map +1 -0
  49. package/lib/app/auth/permissionDefinition.js +2 -0
  50. package/lib/app/auth/permissionDefinition.js.map +1 -0
  51. package/lib/app/context/RequestContext.d.ts +59 -0
  52. package/lib/app/context/RequestContext.d.ts.map +1 -0
  53. package/lib/app/context/RequestContext.js +25 -0
  54. package/lib/app/context/RequestContext.js.map +1 -0
  55. package/lib/app/db/QueryOptions.d.ts +82 -0
  56. package/lib/app/db/QueryOptions.d.ts.map +1 -0
  57. package/lib/app/db/QueryOptions.js +37 -0
  58. package/lib/app/db/QueryOptions.js.map +1 -0
  59. package/lib/app/db/baseFields.d.ts +60 -0
  60. package/lib/app/db/baseFields.d.ts.map +1 -0
  61. package/lib/app/db/baseFields.js +54 -0
  62. package/lib/app/db/baseFields.js.map +1 -0
  63. package/lib/app/db/buildWhere.d.ts +22 -0
  64. package/lib/app/db/buildWhere.d.ts.map +1 -0
  65. package/lib/app/db/buildWhere.js +75 -0
  66. package/lib/app/db/buildWhere.js.map +1 -0
  67. package/lib/app/db/client.d.ts +111 -0
  68. package/lib/app/db/client.d.ts.map +1 -0
  69. package/lib/app/db/client.js +99 -0
  70. package/lib/app/db/client.js.map +1 -0
  71. package/lib/app/db/tenantScoped.d.ts +59 -0
  72. package/lib/app/db/tenantScoped.d.ts.map +1 -0
  73. package/lib/app/db/tenantScoped.js +54 -0
  74. package/lib/app/db/tenantScoped.js.map +1 -0
  75. package/lib/app/db/transaction.d.ts +58 -0
  76. package/lib/app/db/transaction.d.ts.map +1 -0
  77. package/lib/app/db/transaction.js +106 -0
  78. package/lib/app/db/transaction.js.map +1 -0
  79. package/lib/app/health/HealthCheck.d.ts +121 -0
  80. package/lib/app/health/HealthCheck.d.ts.map +1 -0
  81. package/lib/app/health/HealthCheck.js +151 -0
  82. package/lib/app/health/HealthCheck.js.map +1 -0
  83. package/lib/app/health/redisHealthCheck.d.ts +14 -0
  84. package/lib/app/health/redisHealthCheck.d.ts.map +1 -0
  85. package/lib/app/health/redisHealthCheck.js +21 -0
  86. package/lib/app/health/redisHealthCheck.js.map +1 -0
  87. package/lib/app/lifecycle/Bootstrapper.d.ts +92 -0
  88. package/lib/app/lifecycle/Bootstrapper.d.ts.map +1 -0
  89. package/lib/app/lifecycle/Bootstrapper.js +99 -0
  90. package/lib/app/lifecycle/Bootstrapper.js.map +1 -0
  91. package/lib/app/registry/routes.d.ts +89 -0
  92. package/lib/app/registry/routes.d.ts.map +1 -0
  93. package/lib/app/registry/routes.js +72 -0
  94. package/lib/app/registry/routes.js.map +1 -0
  95. package/lib/app/services/DrizzleEntityService.d.ts +123 -0
  96. package/lib/app/services/DrizzleEntityService.d.ts.map +1 -0
  97. package/lib/app/services/DrizzleEntityService.js +342 -0
  98. package/lib/app/services/DrizzleEntityService.js.map +1 -0
  99. package/lib/app/services/RedisProvider.d.ts +52 -0
  100. package/lib/app/services/RedisProvider.d.ts.map +1 -0
  101. package/lib/app/services/RedisProvider.js +75 -0
  102. package/lib/app/services/RedisProvider.js.map +1 -0
  103. package/lib/app/services/cache/Cache.d.ts +70 -0
  104. package/lib/app/services/cache/Cache.d.ts.map +1 -0
  105. package/lib/app/services/cache/Cache.js +18 -0
  106. package/lib/app/services/cache/Cache.js.map +1 -0
  107. package/lib/app/services/cache/InMemoryCache.d.ts +35 -0
  108. package/lib/app/services/cache/InMemoryCache.d.ts.map +1 -0
  109. package/lib/app/services/cache/InMemoryCache.js +76 -0
  110. package/lib/app/services/cache/InMemoryCache.js.map +1 -0
  111. package/lib/app/services/cache/RedisCache.d.ts +99 -0
  112. package/lib/app/services/cache/RedisCache.d.ts.map +1 -0
  113. package/lib/app/services/cache/RedisCache.js +150 -0
  114. package/lib/app/services/cache/RedisCache.js.map +1 -0
  115. package/lib/app/services/cache/cached.d.ts +30 -0
  116. package/lib/app/services/cache/cached.d.ts.map +1 -0
  117. package/lib/app/services/cache/cached.js +38 -0
  118. package/lib/app/services/cache/cached.js.map +1 -0
  119. package/lib/app/services/cache/index.d.ts +5 -0
  120. package/lib/app/services/cache/index.d.ts.map +1 -0
  121. package/lib/app/services/cache/index.js +4 -0
  122. package/lib/app/services/cache/index.js.map +1 -0
  123. package/lib/app/services/cron/defineCron.d.ts +57 -0
  124. package/lib/app/services/cron/defineCron.d.ts.map +1 -0
  125. package/lib/app/services/cron/defineCron.js +15 -0
  126. package/lib/app/services/cron/defineCron.js.map +1 -0
  127. package/lib/app/services/cron/index.d.ts +2 -0
  128. package/lib/app/services/cron/index.d.ts.map +1 -0
  129. package/lib/app/services/cron/index.js +2 -0
  130. package/lib/app/services/cron/index.js.map +1 -0
  131. package/lib/app/services/hooks/LifecycleHooks.d.ts +61 -0
  132. package/lib/app/services/hooks/LifecycleHooks.d.ts.map +1 -0
  133. package/lib/app/services/hooks/LifecycleHooks.js +85 -0
  134. package/lib/app/services/hooks/LifecycleHooks.js.map +1 -0
  135. package/lib/app/services/jobs/JobsManager.d.ts +67 -0
  136. package/lib/app/services/jobs/JobsManager.d.ts.map +1 -0
  137. package/lib/app/services/jobs/JobsManager.js +121 -0
  138. package/lib/app/services/jobs/JobsManager.js.map +1 -0
  139. package/lib/app/services/jobs/bullBoard.d.ts +72 -0
  140. package/lib/app/services/jobs/bullBoard.d.ts.map +1 -0
  141. package/lib/app/services/jobs/bullBoard.js +96 -0
  142. package/lib/app/services/jobs/bullBoard.js.map +1 -0
  143. package/lib/app/services/jobs/defineJob.d.ts +81 -0
  144. package/lib/app/services/jobs/defineJob.d.ts.map +1 -0
  145. package/lib/app/services/jobs/defineJob.js +49 -0
  146. package/lib/app/services/jobs/defineJob.js.map +1 -0
  147. package/lib/app/services/jobs/index.d.ts +4 -0
  148. package/lib/app/services/jobs/index.d.ts.map +1 -0
  149. package/lib/app/services/jobs/index.js +4 -0
  150. package/lib/app/services/jobs/index.js.map +1 -0
  151. package/lib/app/services/rateLimit/InMemorySlidingWindowBackend.d.ts +23 -0
  152. package/lib/app/services/rateLimit/InMemorySlidingWindowBackend.d.ts.map +1 -0
  153. package/lib/app/services/rateLimit/InMemorySlidingWindowBackend.js +51 -0
  154. package/lib/app/services/rateLimit/InMemorySlidingWindowBackend.js.map +1 -0
  155. package/lib/app/services/rateLimit/RateLimitBackend.d.ts +41 -0
  156. package/lib/app/services/rateLimit/RateLimitBackend.d.ts.map +1 -0
  157. package/lib/app/services/rateLimit/RateLimitBackend.js +19 -0
  158. package/lib/app/services/rateLimit/RateLimitBackend.js.map +1 -0
  159. package/lib/app/services/rateLimit/RedisSlidingWindowBackend.d.ts +16 -0
  160. package/lib/app/services/rateLimit/RedisSlidingWindowBackend.d.ts.map +1 -0
  161. package/lib/app/services/rateLimit/RedisSlidingWindowBackend.js +80 -0
  162. package/lib/app/services/rateLimit/RedisSlidingWindowBackend.js.map +1 -0
  163. package/lib/app/services/rateLimit/index.d.ts +4 -0
  164. package/lib/app/services/rateLimit/index.d.ts.map +1 -0
  165. package/lib/app/services/rateLimit/index.js +3 -0
  166. package/lib/app/services/rateLimit/index.js.map +1 -0
  167. package/lib/app/services/storage/DiskStorageAdapter.d.ts +62 -0
  168. package/lib/app/services/storage/DiskStorageAdapter.d.ts.map +1 -0
  169. package/lib/app/services/storage/DiskStorageAdapter.js +295 -0
  170. package/lib/app/services/storage/DiskStorageAdapter.js.map +1 -0
  171. package/lib/app/services/storage/GCSStorageAdapter.d.ts +44 -0
  172. package/lib/app/services/storage/GCSStorageAdapter.d.ts.map +1 -0
  173. package/lib/app/services/storage/GCSStorageAdapter.js +133 -0
  174. package/lib/app/services/storage/GCSStorageAdapter.js.map +1 -0
  175. package/lib/app/services/storage/S3StorageAdapter.d.ts +57 -0
  176. package/lib/app/services/storage/S3StorageAdapter.d.ts.map +1 -0
  177. package/lib/app/services/storage/S3StorageAdapter.js +162 -0
  178. package/lib/app/services/storage/S3StorageAdapter.js.map +1 -0
  179. package/lib/app/services/storage/StorageService.d.ts +88 -0
  180. package/lib/app/services/storage/StorageService.d.ts.map +1 -0
  181. package/lib/app/services/storage/StorageService.js +18 -0
  182. package/lib/app/services/storage/StorageService.js.map +1 -0
  183. package/lib/app/services/storage/index.d.ts +5 -0
  184. package/lib/app/services/storage/index.d.ts.map +1 -0
  185. package/lib/app/services/storage/index.js +4 -0
  186. package/lib/app/services/storage/index.js.map +1 -0
  187. package/lib/app/utils/ContextValidators.d.ts +46 -0
  188. package/lib/app/utils/ContextValidators.d.ts.map +1 -0
  189. package/lib/app/utils/ContextValidators.js +81 -0
  190. package/lib/app/utils/ContextValidators.js.map +1 -0
  191. package/lib/app/utils/defineEnv.d.ts +80 -0
  192. package/lib/app/utils/defineEnv.d.ts.map +1 -0
  193. package/lib/app/utils/defineEnv.js +95 -0
  194. package/lib/app/utils/defineEnv.js.map +1 -0
  195. package/lib/client.d.ts +139 -0
  196. package/lib/client.d.ts.map +1 -0
  197. package/lib/client.js +98 -0
  198. package/lib/client.js.map +1 -0
  199. package/lib/contract/application.d.ts +3 -0
  200. package/lib/contract/application.d.ts.map +1 -0
  201. package/lib/contract/application.js +2 -0
  202. package/lib/contract/application.js.map +1 -0
  203. package/lib/contract/client.d.ts +3 -0
  204. package/lib/contract/client.d.ts.map +1 -0
  205. package/lib/contract/client.js +3 -0
  206. package/lib/contract/client.js.map +1 -0
  207. package/lib/contract/index.d.ts +3 -0
  208. package/lib/contract/index.d.ts.map +1 -0
  209. package/lib/contract/index.js +3 -0
  210. package/lib/contract/index.js.map +1 -0
  211. package/lib/contract/manifest.d.ts +3 -0
  212. package/lib/contract/manifest.d.ts.map +1 -0
  213. package/lib/contract/manifest.js +2 -0
  214. package/lib/contract/manifest.js.map +1 -0
  215. package/lib/contract/openapi.d.ts +3 -0
  216. package/lib/contract/openapi.d.ts.map +1 -0
  217. package/lib/contract/openapi.js +3 -0
  218. package/lib/contract/openapi.js.map +1 -0
  219. package/lib/contract/project.d.ts +3 -0
  220. package/lib/contract/project.d.ts.map +1 -0
  221. package/lib/contract/project.js +3 -0
  222. package/lib/contract/project.js.map +1 -0
  223. package/lib/contract/schema.d.ts +3 -0
  224. package/lib/contract/schema.d.ts.map +1 -0
  225. package/lib/contract/schema.js +3 -0
  226. package/lib/contract/schema.js.map +1 -0
  227. package/lib/drizzle/index.d.ts +3 -0
  228. package/lib/drizzle/index.d.ts.map +1 -0
  229. package/lib/drizzle/index.js +3 -0
  230. package/lib/drizzle/index.js.map +1 -0
  231. package/lib/hono/auth.d.ts +6 -0
  232. package/lib/hono/auth.d.ts.map +1 -0
  233. package/lib/hono/auth.js +54 -0
  234. package/lib/hono/auth.js.map +1 -0
  235. package/lib/hono/index.d.ts +4 -0
  236. package/lib/hono/index.d.ts.map +1 -0
  237. package/lib/hono/index.js +4 -0
  238. package/lib/hono/index.js.map +1 -0
  239. package/lib/http/app/createApp.d.ts +132 -0
  240. package/lib/http/app/createApp.d.ts.map +1 -0
  241. package/lib/http/app/createApp.js +215 -0
  242. package/lib/http/app/createApp.js.map +1 -0
  243. package/lib/http/app/devRoutes.d.ts +31 -0
  244. package/lib/http/app/devRoutes.d.ts.map +1 -0
  245. package/lib/http/app/devRoutes.js +145 -0
  246. package/lib/http/app/devRoutes.js.map +1 -0
  247. package/lib/http/app/errors.d.ts +45 -0
  248. package/lib/http/app/errors.d.ts.map +1 -0
  249. package/lib/http/app/errors.js +93 -0
  250. package/lib/http/app/errors.js.map +1 -0
  251. package/lib/http/app/health.d.ts +66 -0
  252. package/lib/http/app/health.d.ts.map +1 -0
  253. package/lib/http/app/health.js +104 -0
  254. package/lib/http/app/health.js.map +1 -0
  255. package/lib/http/errors/AppError.d.ts +10 -0
  256. package/lib/http/errors/AppError.d.ts.map +1 -0
  257. package/lib/http/errors/AppError.js +19 -0
  258. package/lib/http/errors/AppError.js.map +1 -0
  259. package/lib/http/errors/AuthenticationError.d.ts +8 -0
  260. package/lib/http/errors/AuthenticationError.d.ts.map +1 -0
  261. package/lib/http/errors/AuthenticationError.js +10 -0
  262. package/lib/http/errors/AuthenticationError.js.map +1 -0
  263. package/lib/http/errors/AuthorizationError.d.ts +8 -0
  264. package/lib/http/errors/AuthorizationError.d.ts.map +1 -0
  265. package/lib/http/errors/AuthorizationError.js +10 -0
  266. package/lib/http/errors/AuthorizationError.js.map +1 -0
  267. package/lib/http/errors/BadRequestError.d.ts +8 -0
  268. package/lib/http/errors/BadRequestError.d.ts.map +1 -0
  269. package/lib/http/errors/BadRequestError.js +10 -0
  270. package/lib/http/errors/BadRequestError.js.map +1 -0
  271. package/lib/http/errors/ConflictError.d.ts +8 -0
  272. package/lib/http/errors/ConflictError.d.ts.map +1 -0
  273. package/lib/http/errors/ConflictError.js +10 -0
  274. package/lib/http/errors/ConflictError.js.map +1 -0
  275. package/lib/http/errors/InternalServerError.d.ts +8 -0
  276. package/lib/http/errors/InternalServerError.d.ts.map +1 -0
  277. package/lib/http/errors/InternalServerError.js +10 -0
  278. package/lib/http/errors/InternalServerError.js.map +1 -0
  279. package/lib/http/errors/NotFoundError.d.ts +8 -0
  280. package/lib/http/errors/NotFoundError.d.ts.map +1 -0
  281. package/lib/http/errors/NotFoundError.js +10 -0
  282. package/lib/http/errors/NotFoundError.js.map +1 -0
  283. package/lib/http/errors/RateLimitError.d.ts +13 -0
  284. package/lib/http/errors/RateLimitError.d.ts.map +1 -0
  285. package/lib/http/errors/RateLimitError.js +16 -0
  286. package/lib/http/errors/RateLimitError.js.map +1 -0
  287. package/lib/http/errors/ServiceUnavailableError.d.ts +8 -0
  288. package/lib/http/errors/ServiceUnavailableError.d.ts.map +1 -0
  289. package/lib/http/errors/ServiceUnavailableError.js +10 -0
  290. package/lib/http/errors/ServiceUnavailableError.js.map +1 -0
  291. package/lib/http/errors/ValidationError.d.ts +9 -0
  292. package/lib/http/errors/ValidationError.d.ts.map +1 -0
  293. package/lib/http/errors/ValidationError.js +12 -0
  294. package/lib/http/errors/ValidationError.js.map +1 -0
  295. package/lib/http/errors/index.d.ts +11 -0
  296. package/lib/http/errors/index.d.ts.map +1 -0
  297. package/lib/http/errors/index.js +12 -0
  298. package/lib/http/errors/index.js.map +1 -0
  299. package/lib/http/middleware/apiKey.d.ts +9 -0
  300. package/lib/http/middleware/apiKey.d.ts.map +1 -0
  301. package/lib/http/middleware/apiKey.js +71 -0
  302. package/lib/http/middleware/apiKey.js.map +1 -0
  303. package/lib/http/middleware/csrf.d.ts +63 -0
  304. package/lib/http/middleware/csrf.d.ts.map +1 -0
  305. package/lib/http/middleware/csrf.js +169 -0
  306. package/lib/http/middleware/csrf.js.map +1 -0
  307. package/lib/http/middleware/httpLogger.d.ts +39 -0
  308. package/lib/http/middleware/httpLogger.d.ts.map +1 -0
  309. package/lib/http/middleware/httpLogger.js +118 -0
  310. package/lib/http/middleware/httpLogger.js.map +1 -0
  311. package/lib/http/middleware/idempotency.d.ts +48 -0
  312. package/lib/http/middleware/idempotency.d.ts.map +1 -0
  313. package/lib/http/middleware/idempotency.js +180 -0
  314. package/lib/http/middleware/idempotency.js.map +1 -0
  315. package/lib/http/middleware/rateLimit.d.ts +105 -0
  316. package/lib/http/middleware/rateLimit.d.ts.map +1 -0
  317. package/lib/http/middleware/rateLimit.js +84 -0
  318. package/lib/http/middleware/rateLimit.js.map +1 -0
  319. package/lib/http/middleware/requireAuth.d.ts +58 -0
  320. package/lib/http/middleware/requireAuth.d.ts.map +1 -0
  321. package/lib/http/middleware/requireAuth.js +63 -0
  322. package/lib/http/middleware/requireAuth.js.map +1 -0
  323. package/lib/http/middleware/requirePermission.d.ts +23 -0
  324. package/lib/http/middleware/requirePermission.d.ts.map +1 -0
  325. package/lib/http/middleware/requirePermission.js +50 -0
  326. package/lib/http/middleware/requirePermission.js.map +1 -0
  327. package/lib/http/middleware/resolveTenant.d.ts +41 -0
  328. package/lib/http/middleware/resolveTenant.d.ts.map +1 -0
  329. package/lib/http/middleware/resolveTenant.js +17 -0
  330. package/lib/http/middleware/resolveTenant.js.map +1 -0
  331. package/lib/http/streaming/sse.d.ts +31 -0
  332. package/lib/http/streaming/sse.d.ts.map +1 -0
  333. package/lib/http/streaming/sse.js +11 -0
  334. package/lib/http/streaming/sse.js.map +1 -0
  335. package/lib/http/streaming/streamLLM.d.ts +94 -0
  336. package/lib/http/streaming/streamLLM.d.ts.map +1 -0
  337. package/lib/http/streaming/streamLLM.js +94 -0
  338. package/lib/http/streaming/streamLLM.js.map +1 -0
  339. package/lib/http/utils/authCookies.d.ts +74 -0
  340. package/lib/http/utils/authCookies.d.ts.map +1 -0
  341. package/lib/http/utils/authCookies.js +102 -0
  342. package/lib/http/utils/authCookies.js.map +1 -0
  343. package/lib/http/utils/clientIp.d.ts +85 -0
  344. package/lib/http/utils/clientIp.d.ts.map +1 -0
  345. package/lib/http/utils/clientIp.js +249 -0
  346. package/lib/http/utils/clientIp.js.map +1 -0
  347. package/lib/http/utils/index.d.ts +4 -0
  348. package/lib/http/utils/index.d.ts.map +1 -0
  349. package/lib/http/utils/index.js +5 -0
  350. package/lib/http/utils/index.js.map +1 -0
  351. package/lib/http/utils/metaTags.d.ts +49 -0
  352. package/lib/http/utils/metaTags.d.ts.map +1 -0
  353. package/lib/http/utils/metaTags.js +32 -0
  354. package/lib/http/utils/metaTags.js.map +1 -0
  355. package/lib/http/utils/publicSuffix.d.ts +34 -0
  356. package/lib/http/utils/publicSuffix.d.ts.map +1 -0
  357. package/lib/http/utils/publicSuffix.js +276 -0
  358. package/lib/http/utils/publicSuffix.js.map +1 -0
  359. package/lib/http/utils/uploads.d.ts +92 -0
  360. package/lib/http/utils/uploads.d.ts.map +1 -0
  361. package/lib/http/utils/uploads.js +126 -0
  362. package/lib/http/utils/uploads.js.map +1 -0
  363. package/lib/http/utils/validate.d.ts +41 -0
  364. package/lib/http/utils/validate.d.ts.map +1 -0
  365. package/lib/http/utils/validate.js +55 -0
  366. package/lib/http/utils/validate.js.map +1 -0
  367. package/lib/http/utils/webhooks.d.ts +63 -0
  368. package/lib/http/utils/webhooks.d.ts.map +1 -0
  369. package/lib/http/utils/webhooks.js +133 -0
  370. package/lib/http/utils/webhooks.js.map +1 -0
  371. package/lib/http/ws/auth.d.ts +46 -0
  372. package/lib/http/ws/auth.d.ts.map +1 -0
  373. package/lib/http/ws/auth.js +123 -0
  374. package/lib/http/ws/auth.js.map +1 -0
  375. package/lib/index.d.ts +80 -0
  376. package/lib/index.d.ts.map +1 -0
  377. package/lib/index.js +61 -0
  378. package/lib/index.js.map +1 -0
  379. package/lib/kernel/index.d.ts +3 -0
  380. package/lib/kernel/index.d.ts.map +1 -0
  381. package/lib/kernel/index.js +3 -0
  382. package/lib/kernel/index.js.map +1 -0
  383. package/lib/libraries/.gitkeep +0 -0
  384. package/lib/libraries/ai/CHANGELOG.md +17 -0
  385. package/lib/libraries/ai/README.md +164 -0
  386. package/lib/libraries/ai/adapters/AnthropicAdapter.ts +382 -0
  387. package/lib/libraries/ai/adapters/LLMProvider.ts +76 -0
  388. package/lib/libraries/ai/adapters/OpenAIAdapter.ts +466 -0
  389. package/lib/libraries/ai/experimental/index.ts +20 -0
  390. package/lib/libraries/ai/index.ts +128 -0
  391. package/lib/libraries/ai/library.json +19 -0
  392. package/lib/libraries/ai/loop/agentLoop.ts +377 -0
  393. package/lib/libraries/ai/prompts/definePrompt.ts +0 -0
  394. package/lib/libraries/ai/rag/retrieveAndAnswer.ts +177 -0
  395. package/lib/libraries/ai/streaming/streamLLM.ts +94 -0
  396. package/lib/libraries/ai/structured/completeStructured.ts +143 -0
  397. package/lib/libraries/ai/tokens/Tokenizer.ts +216 -0
  398. package/lib/libraries/ai/tools/defineTool.ts +208 -0
  399. package/lib/libraries/ai/types.ts +260 -0
  400. package/lib/libraries/chunking/ChunkingService.ts +103 -0
  401. package/lib/libraries/chunking/README.md +104 -0
  402. package/lib/libraries/chunking/docs/api-reference.md +549 -0
  403. package/lib/libraries/chunking/docs/best-practices.md +321 -0
  404. package/lib/libraries/chunking/docs/code-organization.md +538 -0
  405. package/lib/libraries/chunking/docs/configuration.md +418 -0
  406. package/lib/libraries/chunking/docs/index.md +236 -0
  407. package/lib/libraries/chunking/docs/quick-start.md +298 -0
  408. package/lib/libraries/chunking/docs/strategies.md +407 -0
  409. package/lib/libraries/chunking/docs/summary.md +258 -0
  410. package/lib/libraries/chunking/docs/troubleshooting.md +527 -0
  411. package/lib/libraries/chunking/index.ts +17 -0
  412. package/lib/libraries/chunking/library.json +16 -0
  413. package/lib/libraries/chunking/strategies/ChunkingStrategy.ts +13 -0
  414. package/lib/libraries/chunking/strategies/FixedLengthChunkingStrategy.ts +88 -0
  415. package/lib/libraries/chunking/strategies/SemanticChunkingStrategy.ts +389 -0
  416. package/lib/libraries/chunking/strategies/SlidingWindowChunkingStrategy.ts +258 -0
  417. package/lib/libraries/chunking/types/index.ts +63 -0
  418. package/lib/libraries/email/EmailService.ts +591 -0
  419. package/lib/libraries/email/README.md +777 -0
  420. package/lib/libraries/email/TemplateRegistry.ts +148 -0
  421. package/lib/libraries/email/TemplateRenderer.ts +273 -0
  422. package/lib/libraries/email/index.ts +4 -0
  423. package/lib/libraries/email/initializeEmailService.ts +60 -0
  424. package/lib/libraries/email/library.json +12 -0
  425. package/lib/libraries/email/types/EmailAddress.ts +4 -0
  426. package/lib/libraries/email/types/EmailAttachment.ts +11 -0
  427. package/lib/libraries/email/types/EmailTag.ts +9 -0
  428. package/lib/libraries/email/types/EmailTemplate.ts +124 -0
  429. package/lib/libraries/email/types/SendBatchEmailsOptions.ts +9 -0
  430. package/lib/libraries/email/types/SendBatchEmailsResult.ts +9 -0
  431. package/lib/libraries/email/types/SendEmailOptions.ts +33 -0
  432. package/lib/libraries/email/types/SendEmailResult.ts +9 -0
  433. package/lib/libraries/email/types/index.ts +8 -0
  434. package/lib/libraries/weaviate/QueryBuilder.ts +292 -0
  435. package/lib/libraries/weaviate/README.md +284 -0
  436. package/lib/libraries/weaviate/Weaviate.ts +1419 -0
  437. package/lib/libraries/weaviate/WeaviateConfig.ts +268 -0
  438. package/lib/libraries/weaviate/docs/.gitkeep +1 -0
  439. package/lib/libraries/weaviate/docs/API_REFERENCE.md +1067 -0
  440. package/lib/libraries/weaviate/docs/CONFIGURATION.md +320 -0
  441. package/lib/libraries/weaviate/docs/ERROR_HANDLING.md +271 -0
  442. package/lib/libraries/weaviate/docs/MULTI_TENANCY.md +348 -0
  443. package/lib/libraries/weaviate/docs/PERFORMANCE.md +602 -0
  444. package/lib/libraries/weaviate/docs/PLUGIN_DEVELOPMENT.md +547 -0
  445. package/lib/libraries/weaviate/docs/QUICK_START.md +443 -0
  446. package/lib/libraries/weaviate/docs/README.md +269 -0
  447. package/lib/libraries/weaviate/docs/SCHEMA_GUIDE.md +630 -0
  448. package/lib/libraries/weaviate/docs/WEAVIATE_README.md +520 -0
  449. package/lib/libraries/weaviate/errors/WeaviateError.ts +92 -0
  450. package/lib/libraries/weaviate/errors/index.ts +1 -0
  451. package/lib/libraries/weaviate/index.ts +35 -0
  452. package/lib/libraries/weaviate/library.json +26 -0
  453. package/lib/libraries/weaviate/plugins/index.ts +2 -0
  454. package/lib/libraries/weaviate/schemas/SchemaRegistry.ts +284 -0
  455. package/lib/libraries/weaviate/schemas/index.ts +2 -0
  456. package/lib/libraries/weaviate/types/BM25SearchOptions.ts +9 -0
  457. package/lib/libraries/weaviate/types/ExtendedWeaviateClient.ts +7 -0
  458. package/lib/libraries/weaviate/types/HybridSearchOptions.ts +11 -0
  459. package/lib/libraries/weaviate/types/MultiTenancy.ts +81 -0
  460. package/lib/libraries/weaviate/types/Plugin.ts +17 -0
  461. package/lib/libraries/weaviate/types/SchemaConfig.ts +25 -0
  462. package/lib/libraries/weaviate/types/SearchMethod.ts +8 -0
  463. package/lib/libraries/weaviate/types/SearchOptions.ts +16 -0
  464. package/lib/libraries/weaviate/types/SearchRequest.ts +19 -0
  465. package/lib/libraries/weaviate/types/SearchResult.ts +16 -0
  466. package/lib/libraries/weaviate/types/SearchableField.ts +87 -0
  467. package/lib/libraries/weaviate/types/VectorIndexConfig.ts +19 -0
  468. package/lib/libraries/weaviate/types/VectorSearchOptions.ts +18 -0
  469. package/lib/libraries/weaviate/types/VectorizerConfig.ts +8 -0
  470. package/lib/libraries/weaviate/types/index.ts +29 -0
  471. package/lib/libraries/weaviate/utils/TenantContextManager.ts +94 -0
  472. package/lib/modules/accounts/index.ts +47 -0
  473. package/lib/modules/accounts/module.json +15 -0
  474. package/lib/modules/accounts/permissions.ts +33 -0
  475. package/lib/modules/accounts/routes.ts +200 -0
  476. package/lib/modules/accounts/schema.ts +219 -0
  477. package/lib/modules/accounts/services/AccountService.ts +44 -0
  478. package/lib/modules/accounts/validation/schemas.ts +70 -0
  479. package/lib/modules/oauth/adapters/GmailOAuthAdapter.ts +35 -0
  480. package/lib/modules/oauth/adapters/GoogleOAuthAdapter.ts +278 -0
  481. package/lib/modules/oauth/adapters/MicrosoftOAuthAdapter.ts +269 -0
  482. package/lib/modules/oauth/adapters/MockOAuthAdapter.ts +138 -0
  483. package/lib/modules/oauth/adapters/OAuthAdapter.ts +211 -0
  484. package/lib/modules/oauth/adapters/OAuthAdapterRegistry.ts +77 -0
  485. package/lib/modules/oauth/adapters/OutlookOAuthAdapter.ts +38 -0
  486. package/lib/modules/oauth/index.ts +79 -0
  487. package/lib/modules/oauth/module.json +23 -0
  488. package/lib/modules/oauth/permissions.ts +39 -0
  489. package/lib/modules/oauth/routes.ts +497 -0
  490. package/lib/modules/oauth/schema.ts +127 -0
  491. package/lib/modules/oauth/services/OAuthConnectionService.ts +147 -0
  492. package/lib/modules/oauth/state.ts +234 -0
  493. package/lib/modules/oauth/stateJtiCache.ts +85 -0
  494. package/lib/modules/oauth/verifyIdToken.ts +178 -0
  495. package/lib/modules/outbox/EventEmitter.ts +106 -0
  496. package/lib/modules/outbox/OutboxDrainer.ts +163 -0
  497. package/lib/modules/outbox/handlers/EventHandler.ts +115 -0
  498. package/lib/modules/outbox/index.ts +43 -0
  499. package/lib/modules/outbox/module.json +15 -0
  500. package/lib/modules/outbox/permissions.ts +36 -0
  501. package/lib/modules/outbox/routes.ts +219 -0
  502. package/lib/modules/outbox/schema.ts +207 -0
  503. package/lib/modules/outbox/services/OutboxEventService.ts +223 -0
  504. package/lib/observability/logger.d.ts +30 -0
  505. package/lib/observability/logger.d.ts.map +1 -0
  506. package/lib/observability/logger.js +110 -0
  507. package/lib/observability/logger.js.map +1 -0
  508. package/lib/observability/metrics.d.ts +3 -0
  509. package/lib/observability/metrics.d.ts.map +1 -0
  510. package/lib/observability/metrics.js +3 -0
  511. package/lib/observability/metrics.js.map +1 -0
  512. package/lib/observability/otel.d.ts +3 -0
  513. package/lib/observability/otel.d.ts.map +1 -0
  514. package/lib/observability/otel.js +3 -0
  515. package/lib/observability/otel.js.map +1 -0
  516. package/lib/observability/safeErrorFields.d.ts +14 -0
  517. package/lib/observability/safeErrorFields.d.ts.map +1 -0
  518. package/lib/observability/safeErrorFields.js +14 -0
  519. package/lib/observability/safeErrorFields.js.map +1 -0
  520. package/lib/observability/tracing.d.ts +3 -0
  521. package/lib/observability/tracing.d.ts.map +1 -0
  522. package/lib/observability/tracing.js +3 -0
  523. package/lib/observability/tracing.js.map +1 -0
  524. package/lib/package.json +4 -0
  525. package/lib/testing.d.ts +3 -0
  526. package/lib/testing.d.ts.map +1 -0
  527. package/lib/testing.js +3 -0
  528. package/lib/testing.js.map +1 -0
  529. package/package.json +193 -3
  530. package/templates/default/.claude/agents/qa-brief.md +106 -0
  531. package/templates/default/.claude/commands/sherpa-brief.md +23 -0
  532. package/templates/default/.cursor/rules/api.mdc +80 -0
  533. package/templates/default/.cursor/rules/commits.mdc +88 -0
  534. package/templates/default/.cursor/rules/security.mdc +92 -0
  535. package/templates/default/.cursor/rules/testing.mdc +123 -0
  536. package/templates/default/.cursor/rules/typescript.mdc +86 -0
  537. package/templates/default/.env.example +79 -0
  538. package/templates/default/.github/workflows/sherpa-brief.yml +180 -0
  539. package/templates/default/AGENTS.md +166 -0
  540. package/templates/default/CLAUDE.md +17 -0
  541. package/templates/default/README.md +97 -0
  542. package/templates/default/_gitignore +27 -0
  543. package/templates/default/docker-compose.yml +83 -0
  544. package/templates/default/drizzle.config.ts +34 -0
  545. package/templates/default/eslint.config.mjs +106 -0
  546. package/templates/default/package.json +57 -0
  547. package/templates/default/ship-api.config.ts +21 -0
  548. package/templates/default/src/client.ts +34 -0
  549. package/templates/default/src/config/modules.ts +23 -0
  550. package/templates/default/src/db/schema.ts +26 -0
  551. package/templates/default/src/environment.ts +81 -0
  552. package/templates/default/src/index.test.ts +53 -0
  553. package/templates/default/src/index.ts +11 -0
  554. package/templates/default/src/instrumentation.ts +13 -0
  555. package/templates/default/src/server.ts +90 -0
  556. package/templates/default/tsconfig.json +24 -0
@@ -0,0 +1,497 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import type {
3
+ AuthenticatedAccount,
4
+ AuthProvider,
5
+ } from "@sixthsummit/ship-api/app/auth/AuthProvider";
6
+ import type { AppEnv } from "@sixthsummit/ship-api/http/app/createApp";
7
+ import { createModuleApp } from "@sixthsummit/ship-api/http/app/createApp";
8
+ import { BadRequestError } from "@sixthsummit/ship-api/http/errors/BadRequestError";
9
+ import { NotFoundError } from "@sixthsummit/ship-api/http/errors/NotFoundError";
10
+ import { requireAuth } from "@sixthsummit/ship-api/http/middleware/requireAuth";
11
+ import { requirePermission } from "@sixthsummit/ship-api/http/middleware/requirePermission";
12
+ import { validate } from "@sixthsummit/ship-api/http/utils/validate";
13
+ import type { Hono, Schema } from "hono";
14
+ import type { BlankSchema } from "hono/types";
15
+ import { z } from "zod";
16
+
17
+ import type { OAuthAdapter, OAuthClientCredentials } from "./adapters/OAuthAdapter.js";
18
+ import type { OAuthAdapterRegistry } from "./adapters/OAuthAdapterRegistry.js";
19
+ import { OAUTH_PERMISSIONS } from "./permissions.js";
20
+ import { isActive, type OAuthConnection } from "./schema.js";
21
+ import type { OAuthConnectionService } from "./services/OAuthConnectionService.js";
22
+ import { InvalidStateError, type StateSecret, signState, verifyState } from "./state.js";
23
+ import { InMemoryStateJtiCache, type StateJtiCache } from "./stateJtiCache.js";
24
+
25
+ /**
26
+ * v4 oauth routes — `defineOAuthRoutes(app, deps)`.
27
+ *
28
+ * Wires:
29
+ * - GET /oauth list current user's connections
30
+ * - POST /oauth/:providerCode/authorize build authorize URL (returns { url, state })
31
+ * - GET /oauth/:providerCode/callback receive code+state, exchange, upsert
32
+ * - DELETE /oauth/:providerCode disconnect (clear tokens; optionally revoke)
33
+ *
34
+ * The callback is GET because OAuth providers redirect via HTTP 302
35
+ * after the user grants access — the browser does the request, not
36
+ * application code.
37
+ *
38
+ * The authorize is POST and returns JSON `{ url, state }` rather than
39
+ * doing a 302 redirect itself. This is API-first: SPAs and mobile
40
+ * clients want the URL to load in a controlled way (new tab, in-app
41
+ * browser, etc.) rather than have the API server pick the navigation
42
+ * mode. The `Location`-redirect pattern is one `Response.redirect(url)`
43
+ * away if the consumer prefers — they can layer their own GET route
44
+ * that calls into the same flow.
45
+ *
46
+ * State management: framework-owned end-to-end. The authorize handler
47
+ * signs a `StatePayload` (accountId + providerCode + optional returnTo
48
+ * + optional extras) and embeds it in the URL the adapter builds.
49
+ * The callback handler verifies the state against the same secret,
50
+ * recovers the payload, and proceeds. Adapters never inspect state.
51
+ */
52
+
53
+ export interface OAuthCredentialsResolver {
54
+ /**
55
+ * Look up the OAuth client credentials for a given provider. Returns
56
+ * null when the provider isn't configured (caller env var unset).
57
+ * Routes layer surfaces `null` as a 400 BadRequestError.
58
+ */
59
+ (providerCode: string): OAuthClientCredentials | null;
60
+ }
61
+
62
+ export interface OAuthRoutesDeps<TAccount extends AuthenticatedAccount = AuthenticatedAccount> {
63
+ service: OAuthConnectionService;
64
+ provider: AuthProvider<TAccount>;
65
+ adapters: OAuthAdapterRegistry;
66
+ /**
67
+ * Resolve `OAuthClientCredentials` per `providerCode`. Typically a
68
+ * closure over the consumer's `defineEnv`-validated env config.
69
+ */
70
+ credentials: OAuthCredentialsResolver;
71
+ /**
72
+ * HMAC-SHA256 secret(s) for state signing. Source from the consumer's
73
+ * env (e.g. `OAUTH_STATE_SECRET`). MUST be at least 32 bytes; this
74
+ * function trusts the caller — `defineEnv` is the boundary.
75
+ *
76
+ * Pass an array to enable zero-downtime rotation: the head signs new
77
+ * state, every entry is accepted on verify. See `StateSecret` for
78
+ * the rotation pattern.
79
+ */
80
+ stateSecret: StateSecret;
81
+ /**
82
+ * Where the callback handler redirects on success when the
83
+ * authorize call didn't supply a `returnTo`. Defaults to "/".
84
+ */
85
+ defaultRedirectUrl?: string;
86
+ /**
87
+ * Resolve the framework's account-id from `c.get('user')`. The
88
+ * framework's `AuthProvider` returns whatever shape the consumer
89
+ * configured for `Account`; the routes layer doesn't know the
90
+ * shape. Defaults to reading `.id` (most common case).
91
+ */
92
+ resolveAccountId?: (user: TAccount) => string;
93
+ /**
94
+ * Origins permitted as absolute-URL `returnTo` targets at the
95
+ * authorize endpoint. Each entry is an absolute URL whose `origin`
96
+ * (scheme + host + port) is the comparison key.
97
+ *
98
+ * Same-origin relative paths (`/dashboard`, `/`) are always
99
+ * accepted regardless of this list. Absolute URLs are rejected
100
+ * entirely when the list is omitted or empty — the framework
101
+ * defends against open-redirect chains originating on the
102
+ * consumer's trusted login domain.
103
+ *
104
+ * Schemes outside `http:`/`https:` (e.g. `javascript:`, `data:`)
105
+ * are rejected unconditionally. Protocol-relative `//host`,
106
+ * backslash-host (`/\host`), and CRLF/null-byte payloads are
107
+ * rejected unconditionally.
108
+ */
109
+ returnToAllowlist?: readonly string[];
110
+ /**
111
+ * Single-use cache for OAuth state nonces (F16). Every successful
112
+ * callback records the verified state's nonce; a re-submit of the
113
+ * same state within the cache's TTL (default 60 s, well above any
114
+ * legitimate round-trip) is rejected as a replay.
115
+ *
116
+ * Defaults to an `InMemoryStateJtiCache` per `defineOAuthRoutes`
117
+ * call. Multi-instance deployments (behind a load balancer) need
118
+ * a shared backing to enforce single-use across nodes — pass a
119
+ * Redis-backed `StateJtiCache` implementation.
120
+ */
121
+ stateJtiCache?: StateJtiCache;
122
+ }
123
+
124
+ const authorizeSchema = z
125
+ .object({
126
+ /** OAuth scopes to request. Provider-defined values. */
127
+ scopes: z.array(z.string()).default([]),
128
+ /** Where to redirect the user after the callback succeeds. */
129
+ returnTo: z.string().optional(),
130
+ /** Provider-specific options the adapter consults (`access_type`, etc.). */
131
+ providerOptions: z.record(z.string(), z.unknown()).optional(),
132
+ /**
133
+ * Free-form extras the framework round-trips through state — e.g.
134
+ * a wizard step ID or a feature flag the callback page should see.
135
+ * Not seen by the adapter; recovered intact on the callback.
136
+ */
137
+ extras: z.record(z.string(), z.unknown()).optional(),
138
+ })
139
+ .strict();
140
+
141
+ const providerCodeParam = z.object({
142
+ providerCode: z.string().min(1).max(50),
143
+ });
144
+
145
+ const callbackQuerySchema = z
146
+ .object({
147
+ code: z.string().min(1).optional(),
148
+ state: z.string().min(1).optional(),
149
+ error: z.string().optional(),
150
+ error_description: z.string().optional(),
151
+ })
152
+ .passthrough();
153
+
154
+ /**
155
+ * ⚠️ FOOT-GUN: do NOT thread one shared `app` through several modules'
156
+ * `defineXxxRoutes` calls — it accumulates one giant Hono route-type and
157
+ * detonates `tsc` (TS2589) at scale. Prefer {@link buildOAuthApp} mounted via
158
+ * `root.route("/", buildOAuthApp(deps))`. This raw form is kept for advanced
159
+ * composition.
160
+ */
161
+ // Generic over Hono's schema param `S` so an already-chained app keeps its
162
+ // previously registered route types — `Hono<AppEnv>` here would widen them
163
+ // away. See defineAccountsRoutes for the full rationale.
164
+ export function defineOAuthRoutes<
165
+ TAccount extends AuthenticatedAccount = AuthenticatedAccount,
166
+ S extends Schema = BlankSchema,
167
+ >(app: Hono<AppEnv, S>, deps: OAuthRoutesDeps<TAccount>) {
168
+ const auth = requireAuth({ provider: deps.provider });
169
+ const resolveAccountId = deps.resolveAccountId ?? ((u: TAccount) => (u as { id: string }).id);
170
+ const defaultRedirectUrl = deps.defaultRedirectUrl ?? "/";
171
+ const returnToAllowlist = deps.returnToAllowlist ?? [];
172
+ const stateJtiCache = deps.stateJtiCache ?? new InMemoryStateJtiCache();
173
+
174
+ return app
175
+ .get("/oauth", auth, requirePermission(OAUTH_PERMISSIONS.readSelf), async (c) => {
176
+ const user = c.get("user" as never) as TAccount;
177
+ const accountId = resolveAccountId(user);
178
+ const rows = (await deps.service.getAll({
179
+ where: { accountId },
180
+ })) as OAuthConnection[];
181
+ const summary = rows.map(summarizeConnection);
182
+ return c.json(summary, 200);
183
+ })
184
+ .post(
185
+ "/oauth/:providerCode/authorize",
186
+ auth,
187
+ requirePermission(OAUTH_PERMISSIONS.connect),
188
+ validate("param", providerCodeParam),
189
+ validate("json", authorizeSchema),
190
+ async (c) => {
191
+ const { providerCode } = c.req.valid("param");
192
+ const body = c.req.valid("json");
193
+ const adapter = requireAdapter(deps.adapters, providerCode);
194
+ const credentials = requireCredentials(deps.credentials, providerCode);
195
+ const user = c.get("user" as never) as TAccount;
196
+ const accountId = resolveAccountId(user);
197
+ const safeReturnTo = validateReturnTo(body.returnTo, returnToAllowlist);
198
+
199
+ // PKCE (F12): generate a 256-bit verifier and the SHA-256
200
+ // challenge. Verifier round-trips through the framework-
201
+ // signed state; challenge goes to the provider via the
202
+ // authorize URL. Defense-in-depth even for confidential
203
+ // clients (OAuth 2.1 mandates PKCE).
204
+ const codeVerifier = randomBytes(32).toString("base64url");
205
+ const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");
206
+
207
+ const state = signState(
208
+ {
209
+ accountId,
210
+ providerCode,
211
+ returnTo: safeReturnTo,
212
+ extras: body.extras as Record<string, unknown> | undefined,
213
+ codeVerifier,
214
+ },
215
+ deps.stateSecret,
216
+ );
217
+
218
+ const url = adapter.buildAuthorizationUrl(credentials, {
219
+ state,
220
+ scopes: body.scopes,
221
+ providerOptions: body.providerOptions,
222
+ codeChallenge,
223
+ codeChallengeMethod: "S256",
224
+ });
225
+
226
+ return c.json({ url, state }, 200);
227
+ },
228
+ )
229
+ .get(
230
+ "/oauth/:providerCode/callback",
231
+ validate("param", providerCodeParam),
232
+ validate("query", callbackQuerySchema),
233
+ async (c) => {
234
+ const { providerCode } = c.req.valid("param");
235
+ const q = c.req.valid("query");
236
+
237
+ // Provider-error short-circuit: redirect with the error tag in
238
+ // the URL so the consumer's UI surfaces something actionable.
239
+ // F23: defensively truncate provider-supplied error strings to
240
+ // 256 chars before round-tripping through the URL. Pathological
241
+ // providers / malicious upstream proxies could otherwise emit
242
+ // multi-KB error blobs that overflow Location-header limits or
243
+ // bloat the consumer's UI.
244
+ if (q.error) {
245
+ return c.redirect(
246
+ withQuery(defaultRedirectUrl, {
247
+ oauth_error: q.error.slice(0, 256),
248
+ oauth_error_description: (q.error_description ?? "").slice(0, 256),
249
+ }),
250
+ 302,
251
+ );
252
+ }
253
+ if (!q.code || !q.state) {
254
+ throw new BadRequestError(
255
+ "OAuth callback missing required `code` or `state` query param",
256
+ );
257
+ }
258
+
259
+ let payload;
260
+ try {
261
+ payload = verifyState(q.state, deps.stateSecret);
262
+ } catch (err) {
263
+ if (err instanceof InvalidStateError) {
264
+ throw new BadRequestError(err.message);
265
+ }
266
+ throw err;
267
+ }
268
+ if (payload.providerCode !== providerCode) {
269
+ throw new BadRequestError(
270
+ "OAuth callback `providerCode` does not match the state's bound provider",
271
+ );
272
+ }
273
+
274
+ // F16: defend against state-replay within the signature
275
+ // window. `verifyState` already enforces the 10-minute
276
+ // expiry; this guards the (theoretical) window inside that
277
+ // expiry where a leaked + replayed state could be exchanged
278
+ // a second time if the upstream provider's auth-code
279
+ // invalidation lagged.
280
+ if (payload.nonce && stateJtiCache.seenAndRecord(payload.nonce)) {
281
+ throw new BadRequestError("OAuth state already used");
282
+ }
283
+
284
+ const adapter = requireAdapter(deps.adapters, providerCode);
285
+ const credentials = requireCredentials(deps.credentials, providerCode);
286
+
287
+ const tokens = await adapter.exchangeCodeForTokens(q.code, credentials, {
288
+ codeVerifier: payload.codeVerifier,
289
+ });
290
+ const providerAccountId = await adapter.getProviderAccountId(tokens, credentials);
291
+
292
+ // Defense in depth: re-validate the state's `returnTo` against
293
+ // the current allowlist before redirecting. A state signed under
294
+ // an older (permissive) configuration must fail closed once the
295
+ // operator tightens the allowlist. The validator throws
296
+ // `BadRequestError` on any disallowed value.
297
+ const safeTarget =
298
+ validateReturnTo(payload.returnTo, returnToAllowlist) ?? defaultRedirectUrl;
299
+
300
+ await deps.service.upsertTokens({
301
+ accountId: payload.accountId,
302
+ providerCode,
303
+ accessToken: tokens.accessToken,
304
+ refreshToken: tokens.refreshToken,
305
+ expiresAt: tokens.expiresAt,
306
+ scope: tokens.scope,
307
+ tokenType: tokens.tokenType,
308
+ providerAccountId,
309
+ metadata: tokens.metadata,
310
+ });
311
+
312
+ return c.redirect(safeTarget, 302);
313
+ },
314
+ )
315
+ .delete(
316
+ "/oauth/:providerCode",
317
+ auth,
318
+ requirePermission(OAUTH_PERMISSIONS.disconnect),
319
+ validate("param", providerCodeParam),
320
+ async (c) => {
321
+ const { providerCode } = c.req.valid("param");
322
+ const user = c.get("user" as never) as TAccount;
323
+ const accountId = resolveAccountId(user);
324
+
325
+ const existing = await deps.service.findByAccountAndProvider(accountId, providerCode);
326
+ if (!existing) {
327
+ throw new NotFoundError(`No OAuth connection for provider "${providerCode}"`);
328
+ }
329
+
330
+ // Best-effort upstream revoke — failure doesn't abort the
331
+ // local clear (see adapter contract: revoke is "best-effort").
332
+ if (
333
+ existing.accessToken &&
334
+ deps.adapters.has(providerCode) &&
335
+ deps.adapters.get(providerCode)!.revokeAccess
336
+ ) {
337
+ const credentials = deps.credentials(providerCode);
338
+ if (credentials) {
339
+ try {
340
+ await deps.adapters.require(providerCode).revokeAccess!(
341
+ existing.accessToken,
342
+ credentials,
343
+ );
344
+ } catch {
345
+ // swallow — local clear still happens.
346
+ }
347
+ }
348
+ }
349
+
350
+ await deps.service.clearTokens(existing.id);
351
+ return c.body(null, 204);
352
+ },
353
+ );
354
+ }
355
+
356
+ /**
357
+ * Build the oauth module as its own sub-app, ready to mount on the root with
358
+ * `root.route("/", buildOAuthApp(deps))`. Canonical wiring — see
359
+ * {@link buildAccountsApp} for the rationale. Routes are tagged under the
360
+ * `oauth` module in `/dev/routes` / `list_routes`.
361
+ */
362
+ export function buildOAuthApp<TAccount extends AuthenticatedAccount = AuthenticatedAccount>(
363
+ deps: OAuthRoutesDeps<TAccount>,
364
+ ) {
365
+ return defineOAuthRoutes(createModuleApp("oauth"), deps);
366
+ }
367
+
368
+ function requireAdapter(registry: OAuthAdapterRegistry, providerCode: string): OAuthAdapter {
369
+ const adapter = registry.get(providerCode);
370
+ if (!adapter) {
371
+ throw new BadRequestError(`No OAuth adapter registered for provider "${providerCode}"`);
372
+ }
373
+ return adapter;
374
+ }
375
+
376
+ function requireCredentials(
377
+ resolver: OAuthCredentialsResolver,
378
+ providerCode: string,
379
+ ): OAuthClientCredentials {
380
+ const credentials = resolver(providerCode);
381
+ if (!credentials) {
382
+ throw new BadRequestError(
383
+ `OAuth credentials for provider "${providerCode}" are not configured`,
384
+ );
385
+ }
386
+ return credentials;
387
+ }
388
+
389
+ /**
390
+ * Strip secrets from a row before returning to the client. Tokens
391
+ * never leave the server; status booleans + metadata are safe.
392
+ */
393
+ function summarizeConnection(row: OAuthConnection) {
394
+ return {
395
+ id: row.id,
396
+ providerCode: row.providerCode,
397
+ providerAccountId: row.providerAccountId,
398
+ scope: row.scope,
399
+ expiresAt: row.expiresAt,
400
+ isActive: isActive(row),
401
+ metadata: row.metadata,
402
+ createdAt: row.createdAt,
403
+ updatedAt: row.updatedAt,
404
+ };
405
+ }
406
+
407
+ /**
408
+ * Validate a user-supplied `returnTo` value before it is signed into
409
+ * state or used as a `Location` redirect. Closes the open-redirect
410
+ * chain that lets an anonymous attacker craft a phishing link pointing
411
+ * at the consumer's trusted authorize endpoint with a 3rd-party return
412
+ * URL. The framework-signed state defeats *tampering*, but the value
413
+ * itself is user input — it must be validated up front.
414
+ *
415
+ * Accepted shapes:
416
+ * - `undefined` / empty → returns `undefined` (caller falls back to
417
+ * `defaultRedirectUrl`).
418
+ * - Same-origin relative path: must start with a single `/` (not
419
+ * `//`, not `/\\`) and not contain CR / LF / null bytes (Location-
420
+ * header injection vectors). The full string is returned as-is.
421
+ * - Absolute URL whose `origin` matches an entry in `allowlist`. The
422
+ * allowlist entries are normalized by `new URL(entry).origin`.
423
+ *
424
+ * Everything else throws `BadRequestError`:
425
+ * - Non-http(s) schemes (`javascript:`, `data:`, `vbscript:`,
426
+ * `file:`, `ms-…:`, etc.).
427
+ * - Protocol-relative `//host` and backslash-host `/\\host`.
428
+ * - Absolute URLs whose origin is not in the allowlist (including
429
+ * all absolute URLs when the allowlist is empty).
430
+ * - Malformed URL strings.
431
+ *
432
+ * Exported (alongside the route factory) so consumers building their
433
+ * own redirect-bearing endpoints can apply the same defense.
434
+ */
435
+ export function validateReturnTo(
436
+ returnTo: string | undefined,
437
+ allowlist: readonly string[],
438
+ ): string | undefined {
439
+ if (returnTo == null || returnTo === "") return undefined;
440
+
441
+ // Reject header-injection shapes outright. Hono's `c.redirect()`
442
+ // sets the `Location` header — CR / LF / NUL would let an attacker
443
+ // forge response headers.
444
+ if (/[\r\n\0]/.test(returnTo)) {
445
+ throw new BadRequestError("Invalid returnTo: contains illegal characters");
446
+ }
447
+
448
+ // Same-origin relative paths. Reject "//host" (protocol-relative)
449
+ // and "/\\host" (some user-agents resolve the backslash as a host
450
+ // separator, escaping the origin).
451
+ if (returnTo.startsWith("/") && !returnTo.startsWith("//") && !returnTo.startsWith("/\\")) {
452
+ return returnTo;
453
+ }
454
+
455
+ // Anything not starting with "/" must be an absolute URL parseable
456
+ // by `URL`, whose origin matches an allowlist entry.
457
+ let parsed: URL;
458
+ try {
459
+ parsed = new URL(returnTo);
460
+ } catch {
461
+ throw new BadRequestError("Invalid returnTo: not a valid URL");
462
+ }
463
+
464
+ // Disallow non-http(s) schemes even if listed somehow.
465
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
466
+ throw new BadRequestError(`Invalid returnTo: scheme "${parsed.protocol}" not permitted`);
467
+ }
468
+
469
+ if (allowlist.length === 0) {
470
+ throw new BadRequestError("Invalid returnTo: absolute URLs require returnToAllowlist");
471
+ }
472
+
473
+ for (const entry of allowlist) {
474
+ let entryOrigin: string;
475
+ try {
476
+ entryOrigin = new URL(entry).origin;
477
+ } catch {
478
+ // Malformed allowlist entry — skip it. (Operator misconfig; the
479
+ // request is still rejected if no other entry matches.)
480
+ continue;
481
+ }
482
+ if (parsed.origin === entryOrigin) {
483
+ return returnTo;
484
+ }
485
+ }
486
+
487
+ throw new BadRequestError("Invalid returnTo: origin not in returnToAllowlist");
488
+ }
489
+
490
+ function withQuery(url: string, params: Record<string, string>): string {
491
+ // Preserves an existing query string by appending. Simple
492
+ // string-level concat — intentionally avoids the `URL` constructor
493
+ // since callers may pass relative paths (e.g. "/onboarding").
494
+ const sep = url.includes("?") ? "&" : "?";
495
+ const qs = new URLSearchParams(Object.entries(params).filter(([, v]) => v !== "")).toString();
496
+ return qs ? `${url}${sep}${qs}` : url;
497
+ }
@@ -0,0 +1,127 @@
1
+ import { baseFields } from "@sixthsummit/ship-api/app/db/baseFields";
2
+ import { relations } from "drizzle-orm";
3
+ import {
4
+ index,
5
+ jsonb,
6
+ pgTable,
7
+ text,
8
+ timestamp,
9
+ uniqueIndex,
10
+ uuid,
11
+ varchar,
12
+ } from "drizzle-orm/pg-core";
13
+ import { accounts } from "../accounts/schema.js";
14
+
15
+ /**
16
+ * v4 OAuth connection table — ports v3's `OAuthConnection` TypeORM entity.
17
+ *
18
+ * One row per (account, provider) pair: an account that has OAuth-linked
19
+ * Google + Microsoft has two rows. The unique composite on
20
+ * `(account_id, provider_code)` enforces this; the codemod surfaced the
21
+ * @Unique decorator as a TODO and we restore it here.
22
+ *
23
+ * Token storage is intentionally plain text columns (Postgres at-rest
24
+ * encryption is the deployment-level concern). Refresh tokens live in
25
+ * the same row as the access token; rotation overwrites in place
26
+ * (no token-history trail — when the upstream provider rotates the
27
+ * refresh token, the old one is gone).
28
+ *
29
+ * The `metadata` jsonb column carries provider-specific extras
30
+ * (Google `id_token`, Microsoft `tenant_id`, …) that don't fit a
31
+ * cross-provider schema. Adapters own the contract.
32
+ */
33
+ export const oauthConnections = pgTable(
34
+ "oauth_connections",
35
+ {
36
+ id: uuid("id").primaryKey().defaultRandom(),
37
+
38
+ accountId: uuid("account_id")
39
+ .notNull()
40
+ .references(() => accounts.id, { onDelete: "cascade" }),
41
+
42
+ /** Stable provider identifier (e.g. "google", "microsoft", "gmail"). */
43
+ providerCode: varchar("provider_code", { length: 50 }).notNull(),
44
+
45
+ /** Bearer-style access token. Plaintext in DB; rely on at-rest encryption. */
46
+ accessToken: text("access_token"),
47
+
48
+ /** Long-lived refresh token. Rotated in place on refresh. */
49
+ refreshToken: text("refresh_token"),
50
+
51
+ /** Access-token expiry. Null when the provider doesn't issue one. */
52
+ expiresAt: timestamp("expires_at", { withTimezone: true }),
53
+
54
+ /** Granted OAuth scopes (space-separated, as the provider returned). */
55
+ scope: text("scope"),
56
+
57
+ /** Token type — typically "Bearer". */
58
+ tokenType: varchar("token_type", { length: 50 }),
59
+
60
+ /** Stable identifier for the user *at the upstream provider*. */
61
+ providerAccountId: varchar("provider_account_id", { length: 255 }),
62
+
63
+ /** Provider-specific extras the cross-provider schema doesn't model. */
64
+ metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
65
+
66
+ ...baseFields,
67
+ },
68
+ (table) => [
69
+ uniqueIndex("oauth_conn_account_provider_unique").on(table.accountId, table.providerCode),
70
+ index("oauth_conn_provider_ix").on(table.providerCode),
71
+ index("oauth_conn_expires_ix").on(table.expiresAt),
72
+ index("oauth_conn_provider_account_ix").on(table.providerCode, table.providerAccountId),
73
+ ],
74
+ );
75
+
76
+ export const oauthConnectionsRelations = relations(oauthConnections, ({ one }) => ({
77
+ account: one(accounts, {
78
+ fields: [oauthConnections.accountId],
79
+ references: [accounts.id],
80
+ }),
81
+ }));
82
+
83
+ export type OAuthConnection = typeof oauthConnections.$inferSelect;
84
+ export type NewOAuthConnection = typeof oauthConnections.$inferInsert;
85
+
86
+ // ─── Token-status helpers ──────────────────────────────────────────────
87
+ //
88
+ // v3 had these as instance methods on the TypeORM entity class. v4 rows
89
+ // are plain objects ($inferSelect produces a structural type, not a
90
+ // class), so the helpers move to module-level functions. Same semantics,
91
+ // no `this` — the service / adapter layer calls them on the row.
92
+
93
+ const FIVE_MINUTES_MS = 5 * 60 * 1000;
94
+
95
+ /** True when the access token has expired (or expires-at is in the past). */
96
+ export function isExpired(conn: Pick<OAuthConnection, "expiresAt">): boolean {
97
+ if (!conn.expiresAt) return false;
98
+ return Date.now() >= conn.expiresAt.getTime();
99
+ }
100
+
101
+ /** True when the access token expires within the next 5 minutes. */
102
+ export function isExpiringSoon(conn: Pick<OAuthConnection, "expiresAt">): boolean {
103
+ if (!conn.expiresAt) return false;
104
+ return conn.expiresAt.getTime() - Date.now() <= FIVE_MINUTES_MS;
105
+ }
106
+
107
+ /** True when the connection has both an access token and isn't expired. */
108
+ export function isActive(conn: Pick<OAuthConnection, "accessToken" | "expiresAt">): boolean {
109
+ return Boolean(conn.accessToken) && !isExpired(conn);
110
+ }
111
+
112
+ /** True when refreshing the token is needed (expired or expiring soon). */
113
+ export function needsRefresh(conn: Pick<OAuthConnection, "expiresAt">): boolean {
114
+ return isExpired(conn) || isExpiringSoon(conn);
115
+ }
116
+
117
+ /**
118
+ * Build the `Authorization: <type> <token>` header value for upstream
119
+ * provider API calls. Returns null when there's no access token to send.
120
+ */
121
+ export function authorizationHeader(
122
+ conn: Pick<OAuthConnection, "accessToken" | "tokenType">,
123
+ ): string | null {
124
+ if (!conn.accessToken) return null;
125
+ const type = conn.tokenType ?? "Bearer";
126
+ return `${type} ${conn.accessToken}`;
127
+ }