openship 0.1.0 → 0.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 (531) hide show
  1. package/.env.example +19 -3
  2. package/CONTRIBUTING.md +69 -19
  3. package/LICENSE +661 -0
  4. package/README.md +304 -108
  5. package/apps/api/Dockerfile +6 -8
  6. package/apps/api/package.json +25 -21
  7. package/apps/api/src/app.ts +46 -3
  8. package/apps/api/src/config/env.ts +81 -7
  9. package/apps/api/src/lib/auth-mode.ts +42 -0
  10. package/apps/api/src/lib/auth.ts +105 -0
  11. package/apps/api/src/lib/cache.ts +128 -0
  12. package/apps/api/src/lib/cloud-auth-proxy.ts +310 -0
  13. package/apps/api/src/lib/cloud-client.ts +103 -0
  14. package/apps/api/src/lib/compose-parser.ts +138 -0
  15. package/apps/api/src/lib/controller-helpers.ts +90 -0
  16. package/apps/api/src/lib/email-templates.ts +94 -0
  17. package/apps/api/src/lib/encryption.ts +102 -0
  18. package/apps/api/src/lib/local-user.ts +65 -0
  19. package/apps/api/src/lib/mail.ts +48 -0
  20. package/apps/api/src/lib/notifications.ts +195 -0
  21. package/apps/api/src/lib/openship-cloud.ts +101 -0
  22. package/apps/api/src/lib/resources.ts +96 -0
  23. package/apps/api/src/lib/screenshots.ts +189 -0
  24. package/apps/api/src/lib/ssh-manager.ts +369 -0
  25. package/apps/api/src/lib/ssl-scheduler.ts +109 -0
  26. package/apps/api/src/lib/stack-detector.ts +708 -0
  27. package/apps/api/src/lib/system-debug.ts +10 -0
  28. package/apps/api/src/middleware/auth.ts +49 -13
  29. package/apps/api/src/middleware/error-handler.ts +23 -3
  30. package/apps/api/src/middleware/index.ts +2 -0
  31. package/apps/api/src/middleware/internal-auth.ts +40 -0
  32. package/apps/api/src/middleware/local-only.ts +20 -0
  33. package/apps/api/src/middleware/rate-limiter.ts +9 -0
  34. package/apps/api/src/modules/analytics/analytics.controller.ts +111 -0
  35. package/apps/api/src/modules/analytics/analytics.routes.ts +29 -0
  36. package/apps/api/src/modules/analytics/analytics.schema.ts +37 -0
  37. package/apps/api/src/modules/analytics/analytics.service.ts +264 -0
  38. package/apps/api/src/modules/auth/auth.routes.ts +233 -6
  39. package/apps/api/src/modules/billing/billing.routes.ts +8 -0
  40. package/apps/api/src/modules/cloud/cloud.controller.ts +225 -0
  41. package/apps/api/src/modules/cloud/cloud.routes.ts +40 -0
  42. package/apps/api/src/modules/deployments/build.service.ts +791 -0
  43. package/apps/api/src/modules/deployments/deployment-lifecycle.ts +138 -0
  44. package/apps/api/src/modules/deployments/deployment.controller.ts +324 -16
  45. package/apps/api/src/modules/deployments/deployment.routes.ts +33 -7
  46. package/apps/api/src/modules/deployments/deployment.schema.ts +37 -5
  47. package/apps/api/src/modules/deployments/deployment.service.ts +178 -11
  48. package/apps/api/src/modules/deployments/preflight.ts +280 -0
  49. package/apps/api/src/modules/deployments/prepare.service.ts +252 -0
  50. package/apps/api/src/modules/deployments/session-manager.ts +283 -0
  51. package/apps/api/src/modules/deployments/ssl.service.ts +73 -0
  52. package/apps/api/src/modules/domains/domain.controller.ts +59 -7
  53. package/apps/api/src/modules/domains/domain.routes.ts +20 -5
  54. package/apps/api/src/modules/domains/domain.schema.ts +35 -0
  55. package/apps/api/src/modules/domains/domain.service.ts +375 -9
  56. package/apps/api/src/modules/github/github.auth.ts +397 -0
  57. package/apps/api/src/modules/github/github.controller.ts +387 -0
  58. package/apps/api/src/modules/github/github.local-auth.ts +251 -0
  59. package/apps/api/src/modules/github/github.routes.ts +50 -0
  60. package/apps/api/src/modules/github/github.schema.ts +52 -0
  61. package/apps/api/src/modules/github/github.service.ts +592 -0
  62. package/apps/api/src/modules/github/github.types.ts +158 -0
  63. package/apps/api/src/modules/github/github.webhook.ts +186 -0
  64. package/apps/api/src/modules/github/index.ts +16 -0
  65. package/apps/api/src/modules/health/health.routes.ts +35 -0
  66. package/apps/api/src/modules/projects/project.controller.ts +481 -12
  67. package/apps/api/src/modules/projects/project.routes.ts +63 -6
  68. package/apps/api/src/modules/projects/project.schema.ts +104 -6
  69. package/apps/api/src/modules/projects/project.service.ts +502 -11
  70. package/apps/api/src/modules/settings/settings.controller.ts +55 -0
  71. package/apps/api/src/modules/settings/settings.routes.ts +25 -0
  72. package/apps/api/src/modules/settings/settings.service.ts +49 -0
  73. package/apps/api/src/modules/system/filesystem.controller.ts +66 -0
  74. package/apps/api/src/modules/system/index.ts +1 -0
  75. package/apps/api/src/modules/system/server-check.controller.ts +475 -0
  76. package/apps/api/src/modules/system/setup-session.ts +255 -0
  77. package/apps/api/src/modules/system/setup.controller.ts +128 -0
  78. package/apps/api/src/modules/system/system.routes.ts +42 -0
  79. package/apps/api/src/modules/webhooks/webhook.controller.ts +57 -15
  80. package/apps/api/src/modules/webhooks/webhook.routes.ts +11 -8
  81. package/apps/api/src/modules/webhooks/webhook.service.ts +46 -5
  82. package/apps/api/src/modules/webhooks/webhook.types.ts +35 -0
  83. package/apps/api/tsconfig.json +3 -1
  84. package/apps/cli/package.json +8 -10
  85. package/apps/cli/tsconfig.json +2 -1
  86. package/apps/dashboard/Dockerfile +4 -6
  87. package/apps/dashboard/components.json +20 -0
  88. package/apps/dashboard/next-env.d.ts +6 -0
  89. package/apps/dashboard/next.config.mjs +11 -1
  90. package/apps/dashboard/package.json +25 -13
  91. package/apps/dashboard/postcss.config.mjs +5 -0
  92. package/apps/dashboard/public/android-chrome-192x192.png +0 -0
  93. package/apps/dashboard/public/android-chrome-512x512.png +0 -0
  94. package/apps/dashboard/public/apple-touch-icon.png +0 -0
  95. package/apps/dashboard/public/favicon-16x16.png +0 -0
  96. package/apps/dashboard/public/favicon-32x32.png +0 -0
  97. package/apps/dashboard/public/favicon.ico +0 -0
  98. package/apps/dashboard/public/site.webmanifest +19 -0
  99. package/apps/dashboard/src/app/(auth)/forgot-password/page.tsx +114 -0
  100. package/apps/dashboard/src/app/(auth)/layout.tsx +18 -3
  101. package/apps/dashboard/src/app/(auth)/login/page.tsx +215 -6
  102. package/apps/dashboard/src/app/(auth)/providers.tsx +34 -0
  103. package/apps/dashboard/src/app/(auth)/register/page.tsx +171 -5
  104. package/apps/dashboard/src/app/(auth)/reset-password/page.tsx +172 -0
  105. package/apps/dashboard/src/app/(auth)/verify-email/page.tsx +190 -0
  106. package/apps/dashboard/src/app/(dashboard)/(deployment)/build/[id]/page.tsx +162 -0
  107. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/BuildSummary.tsx +166 -0
  108. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/DnsConfiguration.tsx +119 -0
  109. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/DomainSettings.tsx +291 -0
  110. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/Header.tsx +71 -0
  111. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/Sidebar.tsx +270 -0
  112. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/SkeletonLoader.tsx +87 -0
  113. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/page.tsx +169 -0
  114. package/apps/dashboard/src/app/(dashboard)/(deployment)/layout.tsx +7 -0
  115. package/apps/dashboard/src/app/(dashboard)/billing/page.tsx +1 -8
  116. package/apps/dashboard/src/app/(dashboard)/deployments/components/CommitDetailsModal.tsx +240 -0
  117. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentCard.tsx +121 -0
  118. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentHeader.tsx +89 -0
  119. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentMenu.tsx +182 -0
  120. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentsContent.tsx +324 -0
  121. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentsFilters.tsx +99 -0
  122. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentsList.tsx +31 -0
  123. package/apps/dashboard/src/app/(dashboard)/deployments/components/EmptyState.tsx +101 -0
  124. package/apps/dashboard/src/app/(dashboard)/deployments/components/LoadingSkeleton.tsx +57 -0
  125. package/apps/dashboard/src/app/(dashboard)/deployments/components/ProjectFilter.tsx +109 -0
  126. package/apps/dashboard/src/app/(dashboard)/deployments/components/index.ts +9 -0
  127. package/apps/dashboard/src/app/(dashboard)/deployments/page.tsx +9 -3
  128. package/apps/dashboard/src/app/(dashboard)/deployments/types.ts +44 -0
  129. package/apps/dashboard/src/app/(dashboard)/deployments/utils.ts +176 -0
  130. package/apps/dashboard/src/app/(dashboard)/domains/page.tsx +1 -6
  131. package/apps/dashboard/src/app/(dashboard)/integrations/page.tsx +321 -0
  132. package/apps/dashboard/src/app/(dashboard)/layout.tsx +22 -32
  133. package/apps/dashboard/src/app/(dashboard)/library/components/ConnectPrompt.tsx +187 -0
  134. package/apps/dashboard/src/app/(dashboard)/library/components/LibrarySidebar.tsx +147 -0
  135. package/apps/dashboard/src/app/(dashboard)/library/components/LoadingSkeleton.tsx +39 -0
  136. package/apps/dashboard/src/app/(dashboard)/library/components/LocalProjects.tsx +548 -0
  137. package/apps/dashboard/src/app/(dashboard)/library/components/RepositoryList.tsx +292 -0
  138. package/apps/dashboard/src/app/(dashboard)/library/components/TemplateGrid.tsx +36 -0
  139. package/apps/dashboard/src/app/(dashboard)/library/components/UrlImport.tsx +74 -0
  140. package/apps/dashboard/src/app/(dashboard)/library/page.tsx +121 -0
  141. package/apps/dashboard/src/app/(dashboard)/library/types.ts +26 -0
  142. package/apps/dashboard/src/app/(dashboard)/monitoring/page.tsx +1 -6
  143. package/apps/dashboard/src/app/(dashboard)/page.tsx +485 -0
  144. package/apps/dashboard/src/app/(dashboard)/projects/[id]/[[...slug]]/layout.tsx +22 -0
  145. package/apps/dashboard/src/app/(dashboard)/projects/[id]/[[...slug]]/page.tsx +286 -0
  146. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/AdvancedSettings.tsx +235 -0
  147. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/BuildSettings.tsx +174 -0
  148. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/DeleteConfirmationDialog.tsx +45 -0
  149. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/DeletionModal.tsx +95 -0
  150. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/Deployments.tsx +11 -0
  151. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/DomainSettings.tsx +523 -0
  152. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/EnvironmentSettings.tsx +246 -0
  153. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/GeneralSettings.tsx +93 -0
  154. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/GitInfo.tsx +261 -0
  155. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/GitSettings.tsx +391 -0
  156. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/LogsSettings.tsx +133 -0
  157. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/ProjectNotFound.tsx +95 -0
  158. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/ProjectsBottomNavigation.tsx +62 -0
  159. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/ResourceSettings.tsx +333 -0
  160. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/SettingSection.tsx +66 -0
  161. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/SettingsNavigation.tsx +27 -0
  162. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/SleepModeSettings.tsx +134 -0
  163. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/ProductionUrl.tsx +25 -0
  164. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/ProjectIdentity.tsx +159 -0
  165. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/ProjectInfo.tsx +58 -0
  166. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/SkeletonLoader.tsx +170 -0
  167. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/StatsCards.tsx +33 -0
  168. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/TopPaths.tsx +56 -0
  169. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/TrafficChart.tsx +157 -0
  170. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/index.ts +14 -0
  171. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/LogsActions.tsx +52 -0
  172. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/ServerLogs.tsx +249 -0
  173. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/TerminalLogs.tsx +682 -0
  174. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/logs.css +127 -0
  175. package/apps/dashboard/src/app/(dashboard)/projects/components/DeploymentCard.tsx +73 -0
  176. package/apps/dashboard/src/app/(dashboard)/projects/components/ProjectCard.tsx +330 -0
  177. package/apps/dashboard/src/app/(dashboard)/projects/page.tsx +326 -4
  178. package/apps/dashboard/src/app/(dashboard)/providers.tsx +23 -0
  179. package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/_components/components-tab.tsx +329 -0
  180. package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/_components/overview-tab.tsx +229 -0
  181. package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/_components/terminal-tab.tsx +38 -0
  182. package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/page.tsx +437 -0
  183. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/checking-state.tsx +15 -0
  184. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/choose-mode.tsx +54 -0
  185. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/component-row.tsx +65 -0
  186. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/error-banner.tsx +13 -0
  187. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/installing-panel.tsx +227 -0
  188. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/results-panel.tsx +90 -0
  189. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/setup-header.tsx +56 -0
  190. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/types.ts +13 -0
  191. package/apps/dashboard/src/app/(dashboard)/servers/new/page.tsx +666 -0
  192. package/apps/dashboard/src/app/(dashboard)/servers/page.tsx +283 -0
  193. package/apps/dashboard/src/app/(dashboard)/settings/_components/BuildPreferences.tsx +110 -0
  194. package/apps/dashboard/src/app/(dashboard)/settings/_components/CloudConnection.tsx +147 -0
  195. package/apps/dashboard/src/app/(dashboard)/settings/_components/InstanceInfo.tsx +54 -0
  196. package/apps/dashboard/src/app/(dashboard)/settings/_components/ServerConnection.tsx +407 -0
  197. package/apps/dashboard/src/app/(dashboard)/settings/_components/SettingsSection.tsx +32 -0
  198. package/apps/dashboard/src/app/(dashboard)/settings/page.tsx +62 -3
  199. package/apps/dashboard/src/app/auth/callback/close/page.tsx +23 -0
  200. package/apps/dashboard/src/app/auth/callback/install/page.tsx +40 -0
  201. package/apps/dashboard/src/app/authorize/page.tsx +137 -0
  202. package/apps/dashboard/src/app/globals.css +57 -3
  203. package/apps/dashboard/src/app/layout.tsx +43 -3
  204. package/apps/dashboard/src/components/auth-shell.tsx +66 -0
  205. package/apps/dashboard/src/components/deployments/types.ts +44 -0
  206. package/apps/dashboard/src/components/global-deployments/index.ts +17 -0
  207. package/apps/dashboard/src/components/i18n-provider.tsx +100 -0
  208. package/apps/dashboard/src/components/import-project/BuildSettings.tsx +376 -0
  209. package/apps/dashboard/src/components/import-project/BuildSkeleton.tsx +161 -0
  210. package/apps/dashboard/src/components/import-project/BuildTerminal.tsx +358 -0
  211. package/apps/dashboard/src/components/import-project/ComposeServices.tsx +455 -0
  212. package/apps/dashboard/src/components/import-project/DeploymentProcessing.tsx +510 -0
  213. package/apps/dashboard/src/components/import-project/DockerSettings.tsx +64 -0
  214. package/apps/dashboard/src/components/import-project/EnvironmentVariables.tsx +481 -0
  215. package/apps/dashboard/src/components/import-project/Frameworks.tsx +90 -0
  216. package/apps/dashboard/src/components/import-project/ProjectSettings.tsx +160 -0
  217. package/apps/dashboard/src/components/import-project/types.ts +82 -0
  218. package/apps/dashboard/src/components/integrations/GithubPermissionModal.tsx +173 -0
  219. package/apps/dashboard/src/components/language-switcher.tsx +36 -0
  220. package/apps/dashboard/src/components/logo.tsx +13 -0
  221. package/apps/dashboard/src/components/network-error-handler.tsx +27 -0
  222. package/apps/dashboard/src/components/oauth-buttons.tsx +81 -0
  223. package/apps/dashboard/src/components/overview/ActivityChart.tsx +247 -0
  224. package/apps/dashboard/src/components/overview/ApiRequestsCard.tsx +111 -0
  225. package/apps/dashboard/src/components/overview/CreditsCard.tsx +137 -0
  226. package/apps/dashboard/src/components/overview/DeploymentsChart.tsx +195 -0
  227. package/apps/dashboard/src/components/overview/EmptyState.tsx +126 -0
  228. package/apps/dashboard/src/components/overview/LoadingSkeleton.tsx +92 -0
  229. package/apps/dashboard/src/components/overview/OverviewHeader.tsx +74 -0
  230. package/apps/dashboard/src/components/overview/OverviewHeroChart.tsx +344 -0
  231. package/apps/dashboard/src/components/overview/ProjectsCard.tsx +134 -0
  232. package/apps/dashboard/src/components/overview/QuickLinks.tsx +119 -0
  233. package/apps/dashboard/src/components/overview/SandboxesCard.tsx +150 -0
  234. package/apps/dashboard/src/components/overview/StatsGrid.tsx +166 -0
  235. package/apps/dashboard/src/components/overview/TokenUsageChart.tsx +206 -0
  236. package/apps/dashboard/src/components/overview/UsageStats.tsx +79 -0
  237. package/apps/dashboard/src/components/overview/index.ts +30 -0
  238. package/apps/dashboard/src/components/overview/types.ts +116 -0
  239. package/apps/dashboard/src/components/page-header.tsx +25 -0
  240. package/apps/dashboard/src/components/project-settings/ServerSideSwitch.jsx +60 -0
  241. package/apps/dashboard/src/components/shared/AlertBox.tsx +39 -0
  242. package/apps/dashboard/src/components/shared/CTASection.tsx +45 -0
  243. package/apps/dashboard/src/components/shared/ErrorState.tsx +176 -0
  244. package/apps/dashboard/src/components/shared/FeatureCard.tsx +20 -0
  245. package/apps/dashboard/src/components/shared/MDXComponents.tsx +24 -0
  246. package/apps/dashboard/src/components/shared/MachineSettingsModal.tsx +286 -0
  247. package/apps/dashboard/src/components/shared/Modal.tsx +39 -0
  248. package/apps/dashboard/src/components/shared/OTPInput.tsx +122 -0
  249. package/apps/dashboard/src/components/shared/PageHero.tsx +21 -0
  250. package/apps/dashboard/src/components/shared/PlatformFeatureCard.tsx +27 -0
  251. package/apps/dashboard/src/components/shared/StatCard.tsx +14 -0
  252. package/apps/dashboard/src/components/shared/ValueCard.tsx +20 -0
  253. package/apps/dashboard/src/components/shared/index.ts +8 -0
  254. package/apps/dashboard/src/components/sidebar.tsx +282 -0
  255. package/apps/dashboard/src/components/theme-provider.tsx +96 -0
  256. package/apps/dashboard/src/components/toast.tsx +106 -0
  257. package/apps/dashboard/src/components/ui/CustomCursor.tsx +107 -0
  258. package/apps/dashboard/src/components/ui/CustomSelect.tsx +126 -0
  259. package/apps/dashboard/src/components/ui/DropdownMenu.tsx +174 -0
  260. package/apps/dashboard/src/components/ui/FileIcon.jsx +59 -0
  261. package/apps/dashboard/src/components/ui/IconPickerModal.tsx +273 -0
  262. package/apps/dashboard/src/components/ui/InfoBanner.jsx +39 -0
  263. package/apps/dashboard/src/components/ui/Logo.tsx +14 -0
  264. package/apps/dashboard/src/components/ui/Modal.tsx +117 -0
  265. package/apps/dashboard/src/components/ui/PageContainer.tsx +12 -0
  266. package/apps/dashboard/src/components/ui/SectionContainer.jsx +7 -0
  267. package/apps/dashboard/src/components/ui/SlidingToggle.tsx +128 -0
  268. package/apps/dashboard/src/components/ui/Toast.tsx +29 -0
  269. package/apps/dashboard/src/components/ui/button.tsx +58 -0
  270. package/apps/dashboard/src/components/ui/card.tsx +60 -0
  271. package/apps/dashboard/src/components/ui/input.tsx +20 -0
  272. package/apps/dashboard/src/components/ui/label.tsx +19 -0
  273. package/apps/dashboard/src/components/ui/select.tsx +10 -0
  274. package/apps/dashboard/src/components/ui/textarea.tsx +9 -0
  275. package/apps/dashboard/src/constants/lang-colors.ts +25 -0
  276. package/apps/dashboard/src/constants/mock.ts +38 -0
  277. package/apps/dashboard/src/context/AuthContext.tsx +91 -0
  278. package/apps/dashboard/src/context/CloudContext.tsx +275 -0
  279. package/apps/dashboard/src/context/DeploymentContext.tsx +71 -0
  280. package/apps/dashboard/src/context/GitHubContext.tsx +288 -0
  281. package/apps/dashboard/src/context/ModalContext.tsx +300 -0
  282. package/apps/dashboard/src/context/NetworkContext.tsx +32 -0
  283. package/apps/dashboard/src/context/ProjectSettingsContext.tsx +727 -0
  284. package/apps/dashboard/src/context/ToastContext.tsx +129 -0
  285. package/apps/dashboard/src/context/deployment/index.ts +3 -0
  286. package/apps/dashboard/src/context/deployment/types.ts +183 -0
  287. package/apps/dashboard/src/context/deployment/useDeploymentBuild.ts +547 -0
  288. package/apps/dashboard/src/context/deployment/useDeploymentConfig.ts +176 -0
  289. package/apps/dashboard/src/hooks/useBuildConnection.ts +334 -0
  290. package/apps/dashboard/src/hooks/useMonitorStream.ts +146 -0
  291. package/apps/dashboard/src/hooks/useProjectData.ts +110 -0
  292. package/apps/dashboard/src/hooks/useSSEConnection.ts +465 -0
  293. package/apps/dashboard/src/hooks/useSSEStream.ts +309 -0
  294. package/apps/dashboard/src/hooks/useSessions.ts +104 -0
  295. package/apps/dashboard/src/hooks/useSetupStream.ts +223 -0
  296. package/apps/dashboard/src/i18n/dictionaries/ar.json +218 -0
  297. package/apps/dashboard/src/i18n/dictionaries/en.json +218 -0
  298. package/apps/dashboard/src/i18n/index.ts +16 -0
  299. package/apps/dashboard/src/lib/api/ai.ts +22 -0
  300. package/apps/dashboard/src/lib/api/auth.ts +13 -0
  301. package/apps/dashboard/src/lib/api/client.ts +199 -0
  302. package/apps/dashboard/src/lib/api/cloud.ts +15 -0
  303. package/apps/dashboard/src/lib/api/deploy.ts +58 -0
  304. package/apps/dashboard/src/lib/api/domains.ts +13 -0
  305. package/apps/dashboard/src/lib/api/endpoints.ts +129 -0
  306. package/apps/dashboard/src/lib/api/github.ts +31 -0
  307. package/apps/dashboard/src/lib/api/icons.ts +12 -0
  308. package/apps/dashboard/src/lib/api/index.ts +41 -0
  309. package/apps/dashboard/src/lib/api/projects.ts +153 -0
  310. package/apps/dashboard/src/lib/api/sandbox.ts +14 -0
  311. package/apps/dashboard/src/lib/api/settings.ts +21 -0
  312. package/apps/dashboard/src/lib/api/system.ts +159 -0
  313. package/apps/dashboard/src/lib/auth-client.ts +54 -0
  314. package/apps/dashboard/src/lib/country.ts +25 -0
  315. package/apps/dashboard/src/lib/server/api.ts +140 -0
  316. package/apps/dashboard/src/lib/server/session.ts +103 -0
  317. package/apps/dashboard/src/lib/sseClient.ts +241 -0
  318. package/apps/dashboard/src/lib/sseMessageProcessors.ts +323 -0
  319. package/apps/dashboard/src/lib/utils.ts +6 -0
  320. package/apps/dashboard/src/proxy.ts +46 -0
  321. package/apps/dashboard/src/styles/Toast.module.css +92 -0
  322. package/apps/dashboard/src/styles/fonts.css +93 -0
  323. package/apps/dashboard/src/styles/theme.css +336 -0
  324. package/apps/dashboard/src/types/research.ts +42 -0
  325. package/apps/dashboard/src/utils/SSEParser.js +150 -0
  326. package/apps/dashboard/src/utils/authWindow.ts +143 -0
  327. package/apps/dashboard/src/utils/codeTheme.ts +127 -0
  328. package/apps/dashboard/src/utils/colors.json +2810 -0
  329. package/apps/dashboard/src/utils/date.ts +121 -0
  330. package/apps/dashboard/src/utils/deployment.ts +128 -0
  331. package/apps/dashboard/src/utils/deploymentPhaseDetector.ts +232 -0
  332. package/apps/dashboard/src/utils/extToLang.js +103 -0
  333. package/apps/dashboard/src/utils/github.ts +62 -0
  334. package/apps/dashboard/src/utils/icons.js +46 -0
  335. package/apps/dashboard/src/utils/repoSlug.ts +110 -0
  336. package/apps/dashboard/src/utils/research-api.ts +163 -0
  337. package/apps/dashboard/src/utils/suggestions.ts +84 -0
  338. package/apps/dashboard/src/utils/theme.js +1 -0
  339. package/apps/dashboard/src/utils/ui-helpers.tsx +30 -0
  340. package/apps/dashboard/src/utils/upload.js +12 -0
  341. package/apps/dashboard/tsconfig.json +27 -5
  342. package/apps/desktop/forge.config.json +36 -0
  343. package/apps/desktop/package.json +26 -0
  344. package/apps/desktop/scripts/dev.mjs +58 -0
  345. package/apps/desktop/src/main/index.ts +675 -0
  346. package/apps/desktop/src/main/types/desktop.d.ts +33 -0
  347. package/apps/desktop/src/preload/index.ts +88 -0
  348. package/apps/desktop/src/renderer/index.html +462 -0
  349. package/apps/desktop/src/renderer/onboarding.js +511 -0
  350. package/apps/desktop/src/renderer/styles.css +1139 -0
  351. package/apps/desktop/tsconfig.json +17 -0
  352. package/apps/web/.source/browser.ts +13 -0
  353. package/apps/web/.source/dynamic.ts +8 -0
  354. package/apps/web/.source/server.ts +22 -0
  355. package/apps/web/.source/source.config.mjs +20 -0
  356. package/apps/web/Dockerfile +4 -6
  357. package/apps/web/content/blog/how-ai-builds-work.mdx +55 -0
  358. package/apps/web/content/blog/introducing-openship.mdx +46 -0
  359. package/apps/web/content/blog/self-hosting-cost-breakdown.mdx +59 -0
  360. package/apps/web/content/docs/_meta.json +14 -0
  361. package/apps/web/content/docs/api.mdx +98 -0
  362. package/apps/web/content/docs/cli.mdx +80 -0
  363. package/apps/web/content/docs/first-deployment.mdx +56 -0
  364. package/apps/web/content/docs/index.mdx +27 -0
  365. package/apps/web/content/docs/installation.mdx +65 -0
  366. package/apps/web/content/docs/quickstart.mdx +45 -0
  367. package/apps/web/next-env.d.ts +6 -0
  368. package/apps/web/next.config.mjs +16 -2
  369. package/apps/web/package.json +19 -12
  370. package/apps/web/postcss.config.mjs +5 -0
  371. package/apps/web/public/android-chrome-192x192.png +0 -0
  372. package/apps/web/public/android-chrome-512x512.png +0 -0
  373. package/apps/web/public/apple-touch-icon.png +0 -0
  374. package/apps/web/public/favicon-16x16.png +0 -0
  375. package/apps/web/public/favicon-32x32.png +0 -0
  376. package/apps/web/public/favicon.ico +0 -0
  377. package/apps/web/public/site.webmanifest +1 -0
  378. package/apps/web/source.config.ts +17 -0
  379. package/apps/web/src/app/(marketing)/layout.tsx +7 -22
  380. package/apps/web/src/app/blog/[...slug]/page.tsx +247 -0
  381. package/apps/web/src/app/blog/blog.css +575 -0
  382. package/apps/web/src/app/blog/layout.tsx +50 -0
  383. package/apps/web/src/app/blog/page.tsx +235 -0
  384. package/apps/web/src/app/docs/[[...slug]]/page.tsx +67 -0
  385. package/apps/web/src/app/docs/layout.tsx +62 -0
  386. package/apps/web/src/app/docs/toc-theme.css +71 -0
  387. package/apps/web/src/app/download/page.tsx +396 -0
  388. package/apps/web/src/app/globals.css +20 -3
  389. package/apps/web/src/app/layout.tsx +57 -3
  390. package/apps/web/src/app/page.tsx +47 -6
  391. package/apps/web/src/app/robots.ts +15 -0
  392. package/apps/web/src/app/sitemap.ts +35 -0
  393. package/apps/web/src/components/blog/share-sidebar.tsx +81 -0
  394. package/apps/web/src/components/blog/writer-sidebar.tsx +81 -0
  395. package/apps/web/src/components/landing/ai-native.tsx +169 -0
  396. package/apps/web/src/components/landing/backups.tsx +139 -0
  397. package/apps/web/src/components/landing/cdn.tsx +109 -0
  398. package/apps/web/src/components/landing/coming-soon.tsx +180 -0
  399. package/apps/web/src/components/landing/dark-section.tsx +166 -0
  400. package/apps/web/src/components/landing/dashboard.tsx +407 -0
  401. package/apps/web/src/components/landing/developer-experience.tsx +224 -0
  402. package/apps/web/src/components/landing/dns-domains.tsx +146 -0
  403. package/apps/web/src/components/landing/download-button.tsx +151 -0
  404. package/apps/web/src/components/landing/features/ai-visual.tsx +48 -0
  405. package/apps/web/src/components/landing/features/cloud-visual.tsx +44 -0
  406. package/apps/web/src/components/landing/features/git-visual.tsx +61 -0
  407. package/apps/web/src/components/landing/features/index.tsx +642 -0
  408. package/apps/web/src/components/landing/features/infra-visual.tsx +69 -0
  409. package/apps/web/src/components/landing/features/rollback-visual.tsx +42 -0
  410. package/apps/web/src/components/landing/features/ssl-visual.tsx +41 -0
  411. package/apps/web/src/components/landing/features/stacks-visual.tsx +54 -0
  412. package/apps/web/src/components/landing/final-cta.tsx +116 -0
  413. package/apps/web/src/components/landing/footer.tsx +145 -0
  414. package/apps/web/src/components/landing/hero.tsx +110 -0
  415. package/apps/web/src/components/landing/how-it-works.tsx +210 -0
  416. package/apps/web/src/components/landing/index.ts +21 -0
  417. package/apps/web/src/components/landing/mail-server.tsx +122 -0
  418. package/apps/web/src/components/landing/navbar.tsx +199 -0
  419. package/apps/web/src/components/landing/no-lockin.tsx +185 -0
  420. package/apps/web/src/components/landing/open-source-cta.tsx +151 -0
  421. package/apps/web/src/components/landing/portability.tsx +190 -0
  422. package/apps/web/src/components/landing/pricing.tsx +263 -0
  423. package/apps/web/src/components/landing/scaling.tsx +209 -0
  424. package/apps/web/src/components/landing/why-openship.tsx +291 -0
  425. package/apps/web/src/hooks/use-gsap-scroll.ts +158 -0
  426. package/apps/web/src/hooks/use-platform.ts +86 -0
  427. package/apps/web/src/lib/source.ts +12 -0
  428. package/apps/web/src/styles/fonts.css +84 -0
  429. package/apps/web/src/styles/landing.css +951 -0
  430. package/apps/web/src/styles/theme.css +227 -0
  431. package/apps/web/tsconfig.json +8 -3
  432. package/bun.lock +3260 -0
  433. package/docker-compose.yml +3 -0
  434. package/docs/screenshots/.gitkeep +0 -0
  435. package/docs/screenshots/screen.png +0 -0
  436. package/openship.code-workspace +7 -0
  437. package/package.json +23 -9
  438. package/packages/adapters/docs/ARCHITECTURE.md +160 -0
  439. package/packages/adapters/docs/BUILD-PIPELINE.md +171 -0
  440. package/packages/adapters/docs/CLOUD.md +239 -0
  441. package/packages/adapters/docs/EXECUTOR.md +174 -0
  442. package/packages/adapters/docs/LOG-STREAMING.md +175 -0
  443. package/packages/adapters/docs/SYSTEM.md +191 -0
  444. package/packages/adapters/package.json +14 -9
  445. package/packages/adapters/src/index.ts +144 -4
  446. package/packages/adapters/src/infra/cloud.ts +42 -0
  447. package/packages/adapters/src/infra/index.ts +9 -0
  448. package/packages/adapters/src/infra/noop.ts +27 -0
  449. package/packages/adapters/src/infra/traefik.ts +297 -0
  450. package/packages/adapters/src/infra/types.ts +36 -0
  451. package/packages/adapters/src/platform.ts +255 -0
  452. package/packages/adapters/src/runtime/bare.ts +567 -0
  453. package/packages/adapters/src/runtime/build-pipeline.ts +237 -0
  454. package/packages/adapters/src/runtime/cloud.ts +791 -0
  455. package/packages/adapters/src/runtime/deploy-pipeline.ts +146 -0
  456. package/packages/adapters/src/runtime/docker.ts +443 -0
  457. package/packages/adapters/src/runtime/index.ts +69 -0
  458. package/packages/adapters/src/runtime/local-build.ts +112 -0
  459. package/packages/adapters/src/runtime/transfer.ts +82 -0
  460. package/packages/adapters/src/runtime/types.ts +138 -0
  461. package/packages/adapters/src/system/catalog.ts +188 -0
  462. package/packages/adapters/src/system/checks.ts +228 -0
  463. package/packages/adapters/src/system/components.ts +45 -0
  464. package/packages/adapters/src/system/debug.ts +18 -0
  465. package/packages/adapters/src/system/environment.ts +168 -0
  466. package/packages/adapters/src/system/errors.ts +34 -0
  467. package/packages/adapters/src/system/executor.ts +600 -0
  468. package/packages/adapters/src/system/index.ts +58 -0
  469. package/packages/adapters/src/system/installer.ts +310 -0
  470. package/packages/adapters/src/system/setup.ts +442 -0
  471. package/packages/adapters/src/system/state.ts +96 -0
  472. package/packages/adapters/src/system/types.ts +116 -0
  473. package/packages/adapters/src/toolchain/catalog.ts +439 -0
  474. package/packages/adapters/src/toolchain/checks.ts +181 -0
  475. package/packages/adapters/src/toolchain/index.ts +38 -0
  476. package/packages/adapters/src/toolchain/installer.ts +189 -0
  477. package/packages/adapters/src/toolchain/types.ts +84 -0
  478. package/packages/adapters/src/types.ts +277 -0
  479. package/packages/adapters/tsconfig.json +3 -1
  480. package/packages/core/package.json +9 -9
  481. package/packages/core/src/constants.ts +44 -1
  482. package/packages/core/src/index.ts +2 -0
  483. package/packages/core/src/stacks.ts +778 -0
  484. package/packages/core/src/system.ts +111 -0
  485. package/packages/core/src/types.ts +22 -1
  486. package/packages/core/src/utils.ts +8 -0
  487. package/packages/core/tsconfig.json +3 -1
  488. package/packages/db/drizzle/0000_init.sql +231 -0
  489. package/packages/db/drizzle/0001_add_server_name.sql +1 -0
  490. package/packages/db/drizzle/meta/_journal.json +20 -0
  491. package/packages/db/drizzle.config.ts +10 -0
  492. package/packages/db/package.json +26 -14
  493. package/packages/db/src/client.ts +119 -0
  494. package/packages/db/src/index.ts +57 -8
  495. package/packages/db/src/repos/account.repo.ts +92 -0
  496. package/packages/db/src/repos/deployment.repo.ts +168 -0
  497. package/packages/db/src/repos/domain.repo.ts +102 -0
  498. package/packages/db/src/repos/git-installation.repo.ts +95 -0
  499. package/packages/db/src/repos/index.ts +43 -0
  500. package/packages/db/src/repos/instance-settings.repo.ts +43 -0
  501. package/packages/db/src/repos/project.repo.ts +159 -0
  502. package/packages/db/src/repos/session.repo.ts +82 -0
  503. package/packages/db/src/repos/settings.repo.ts +51 -0
  504. package/packages/db/src/repos/user.repo.ts +94 -0
  505. package/packages/db/src/schema/auth.ts +56 -0
  506. package/packages/db/src/schema/deployment.ts +87 -0
  507. package/packages/db/src/schema/domain.ts +46 -0
  508. package/packages/db/src/schema/github.ts +24 -0
  509. package/packages/db/src/schema/index.ts +6 -0
  510. package/packages/db/src/schema/project.ts +117 -0
  511. package/packages/db/src/schema/settings.ts +103 -0
  512. package/packages/db/tsconfig.json +3 -1
  513. package/packages/ui/package.json +17 -17
  514. package/packages/ui/src/components/status-dot.tsx +1 -1
  515. package/packages/ui/tsconfig.json +7 -2
  516. package/pnpm-workspace.yaml +0 -1
  517. package/{tooling/tsconfig/base.json → tsconfig.base.json} +0 -1
  518. package/turbo.json +4 -0
  519. package/apps/api/src/modules/auth/auth.controller.ts +0 -26
  520. package/apps/api/src/modules/auth/auth.schema.ts +0 -12
  521. package/apps/api/src/modules/auth/auth.service.ts +0 -23
  522. package/apps/dashboard/src/app/page.tsx +0 -6
  523. package/apps/web/src/app/(marketing)/docs/page.tsx +0 -10
  524. package/packages/adapters/src/base-adapter.ts +0 -49
  525. package/packages/adapters/src/docker-adapter.ts +0 -43
  526. package/packages/adapters/src/oblien-adapter.ts +0 -46
  527. package/packages/adapters/src/registry.ts +0 -22
  528. package/packages/db/prisma/schema.prisma +0 -160
  529. package/tooling/tsconfig/nextjs.json +0 -10
  530. package/tooling/tsconfig/node.json +0 -10
  531. package/tooling/tsconfig/package.json +0 -7
@@ -1,10 +1,237 @@
1
1
  import { Hono } from "hono";
2
- import * as authController from "./auth.controller";
2
+ import { auth } from "../../lib/auth";
3
+ import { env } from "../../config/env";
4
+ import { setSignedCookie } from "hono/cookie";
5
+ import { internalAuth } from "../../middleware/internal-auth";
3
6
 
4
7
  export const authRoutes = new Hono();
5
8
 
6
- authRoutes.post("/register", authController.register);
7
- authRoutes.post("/login", authController.login);
8
- authRoutes.post("/logout", authController.logout);
9
- authRoutes.post("/refresh", authController.refresh);
10
- authRoutes.get("/me", authController.me);
9
+ /** HTML page shown in the system browser after cloud auth for desktop. */
10
+ function desktopResultPage(title: string, message: string, success = false): string {
11
+ return `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Openship</title></head>
12
+ <body style="display:flex;align-items:center;justify-content:center;height:100vh;margin:0;font-family:system-ui,-apple-system,sans-serif;background:#0a0a0a;color:#fafafa">
13
+ <div style="text-align:center;max-width:420px">
14
+ <div style="font-size:48px;margin-bottom:16px">${success ? "\u2713" : "\u26A0"}</div>
15
+ <h2 style="margin:0 0 8px">${title}</h2>
16
+ <p style="color:#888;margin:0 0 24px">${message}</p>
17
+ ${success ? '<p style="color:#555;font-size:14px">This tab can be safely closed.</p>' : ''}
18
+ </div>
19
+ </body></html>`;
20
+ }
21
+
22
+ /**
23
+ * Desktop-mode auth endpoints.
24
+ *
25
+ * Two authentication flows are supported:
26
+ *
27
+ * 1. Zero-auth (/desktop-login)
28
+ * User chose self-hosted during onboarding → auto-provision a local
29
+ * admin user and create a real Better Auth session with a cookie.
30
+ * No password, the session cookie is the only credential.
31
+ *
32
+ * 2. Cloud auth (/cloud-callback)
33
+ * User chose "Continue with Cloud" during onboarding → authenticate
34
+ * on app.openship.io, exchange a one-time code for a local session.
35
+ *
36
+ * Both flows end with a `better-auth.session_token` cookie and a
37
+ * redirect to the dashboard. The rest of the app works the same way
38
+ * regardless of which path was taken.
39
+ */
40
+ if (env.DEPLOY_MODE === "desktop") {
41
+ /**
42
+ * GET /get-session — try real Better Auth session first, fall back
43
+ * to auto-provisioned zero-auth session for backwards compat.
44
+ */
45
+ authRoutes.get("/get-session", async (c) => {
46
+ // Try real session first (cloud-authenticated or previously created)
47
+ try {
48
+ const realSession = await auth.api.getSession({
49
+ headers: c.req.raw.headers,
50
+ });
51
+ if (realSession) {
52
+ return c.json(realSession);
53
+ }
54
+ } catch {
55
+ // session lookup failed — fall through to zero-auth fallback
56
+ }
57
+
58
+ // Zero-auth fallback — only when authMode is "none" (self-hosted desktop).
59
+ // Cloud-auth users must re-authenticate via Openship Cloud.
60
+ const { getAuthMode } = await import("../../lib/auth-mode");
61
+ const authMode = await getAuthMode();
62
+ if (authMode !== "none") {
63
+ return c.json({ error: "Unauthorized" }, 401);
64
+ }
65
+
66
+ const { ensureLocalUser } = await import("../../lib/local-user");
67
+ const user = await ensureLocalUser();
68
+ const now = new Date().toISOString();
69
+ return c.json({
70
+ session: {
71
+ id: "desktop-session",
72
+ userId: user.id,
73
+ token: "desktop",
74
+ expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(),
75
+ createdAt: now,
76
+ updatedAt: now,
77
+ },
78
+ user: {
79
+ ...user,
80
+ image: null,
81
+ createdAt: now,
82
+ updatedAt: now,
83
+ },
84
+ });
85
+ });
86
+
87
+ /**
88
+ * GET /desktop-login — create a real Better Auth session for the
89
+ * zero-auth local user and redirect to the dashboard.
90
+ *
91
+ * Called ONCE after onboarding completes (self-hosted path).
92
+ * The BrowserWindow navigates here → gets a cookie → reaches the dashboard.
93
+ */
94
+ authRoutes.get("/desktop-login", async (c) => {
95
+ // Only zero-auth mode can mint sessions without credentials
96
+ const { getAuthMode } = await import("../../lib/auth-mode");
97
+ const authMode = await getAuthMode();
98
+ if (authMode !== "none") {
99
+ return c.redirect(`${env.DASHBOARD_URL}/login`);
100
+ }
101
+
102
+ const { ensureLocalUser } = await import("../../lib/local-user");
103
+ const { createLocalSession } = await import("../../lib/cloud-auth-proxy");
104
+
105
+ const user = await ensureLocalUser();
106
+ const session = await createLocalSession(user.id, "127.0.0.1", "desktop");
107
+
108
+ await setSignedCookie(c, "better-auth.session_token", session.token, env.BETTER_AUTH_SECRET, {
109
+ httpOnly: true,
110
+ secure: false,
111
+ sameSite: "Lax",
112
+ path: "/",
113
+ expires: session.expiresAt,
114
+ });
115
+
116
+ return c.redirect(env.DASHBOARD_URL);
117
+ });
118
+
119
+ /**
120
+ * GET /cloud-callback — exchange cloud auth code for a local session.
121
+ *
122
+ * Flow:
123
+ * 1. User authenticates on app.openship.io (in system browser)
124
+ * 2. Cloud generates a one-time code and redirects here with ?code=xxx&state=yyy
125
+ * 3. We validate state (CSRF protection) and retrieve stored code_verifier (PKCE)
126
+ * 4. We exchange the code + code_verifier for user data + cloud session token
127
+ * 5. Mirror the cloud user locally, store cloud token, create session
128
+ * 6. Store session token for Electron to pick up via polling
129
+ * 7. Show a "success — close this tab" page
130
+ */
131
+ authRoutes.get("/cloud-callback", async (c) => {
132
+ const code = c.req.query("code");
133
+ if (!code) {
134
+ return c.html(desktopResultPage("Missing authentication code", "Please return to Openship and try again."));
135
+ }
136
+
137
+ // Validate state parameter (CSRF) and retrieve PKCE code_verifier
138
+ const state = c.req.query("state");
139
+ if (!state) {
140
+ return c.html(desktopResultPage("Missing state parameter", "Please return to Openship and try again."));
141
+ }
142
+
143
+ try {
144
+ const { exchangeCodeWithCloud, mirrorCloudUser, storeCloudSession, createLocalSession, resolveDesktopAuth, validateDesktopState } =
145
+ await import("../../lib/cloud-auth-proxy");
146
+
147
+ const validated = validateDesktopState(state);
148
+ if (!validated) {
149
+ return c.html(desktopResultPage("Invalid or expired session", "The authorization request has expired. Please return to Openship and try again."));
150
+ }
151
+
152
+ const data = await exchangeCodeWithCloud(code, validated.codeVerifier);
153
+ if (!data) {
154
+ return c.html(desktopResultPage("Authentication failed", "Could not verify with Openship Cloud. Please return to Openship and try again."));
155
+ }
156
+
157
+ const localUserId = await mirrorCloudUser(data.user);
158
+ await storeCloudSession(localUserId, data.sessionToken);
159
+
160
+ const session = await createLocalSession(localUserId, "127.0.0.1", "desktop");
161
+
162
+ // Resolve the pending nonce so Electron can pick up the session via polling
163
+ resolveDesktopAuth(validated.nonce, session.token, session.expiresAt);
164
+
165
+ return c.html(desktopResultPage("Signed in to Openship", "You can return to the Openship app now.", true));
166
+ } catch {
167
+ return c.html(desktopResultPage("Authentication failed", "Something went wrong. Please return to Openship and try again."));
168
+ }
169
+ });
170
+
171
+ /**
172
+ * POST /desktop-auth-start — register a nonce + PKCE + state for desktop auth.
173
+ * Called by the Electron main process before opening the system browser.
174
+ *
175
+ * Protected by internal token — prevents unauthorized registration.
176
+ */
177
+ authRoutes.post("/desktop-auth-start", internalAuth, async (c) => {
178
+ const body = await c.req.json();
179
+ const nonce = body?.nonce;
180
+ const state = body?.state;
181
+ const codeVerifier = body?.code_verifier;
182
+ if (!nonce || typeof nonce !== "string" || !state || typeof state !== "string" || !codeVerifier || typeof codeVerifier !== "string") {
183
+ return c.json({ error: "missing nonce, state, or code_verifier" }, 400);
184
+ }
185
+ const { registerDesktopNonce } = await import("../../lib/cloud-auth-proxy");
186
+ registerDesktopNonce(nonce, state, codeVerifier);
187
+ return c.json({ ok: true });
188
+ });
189
+
190
+ /**
191
+ * GET /desktop-auth-poll — Electron polls this to check if cloud auth completed.
192
+ * Returns { status: "pending" | "resolved" | "expired", claimCode? }
193
+ */
194
+ authRoutes.get("/desktop-auth-poll", async (c) => {
195
+ const nonce = c.req.query("nonce");
196
+ if (!nonce) {
197
+ return c.json({ error: "missing nonce" }, 400);
198
+ }
199
+ const { pollDesktopAuth } = await import("../../lib/cloud-auth-proxy");
200
+ return c.json(pollDesktopAuth(nonce));
201
+ });
202
+
203
+ /**
204
+ * GET /desktop-claim?code=xxx — exchange a one-time claim code for a
205
+ * session cookie. Electron navigates here after polling resolves.
206
+ *
207
+ * This sets the cookie via HTTP Set-Cookie (reliable across all
208
+ * Electron versions) and then redirects to the dashboard.
209
+ */
210
+ authRoutes.get("/desktop-claim", async (c) => {
211
+ const code = c.req.query("code");
212
+ if (!code) {
213
+ return c.text("Missing code", 400);
214
+ }
215
+
216
+ const { exchangeDesktopClaim } = await import("../../lib/cloud-auth-proxy");
217
+ const result = exchangeDesktopClaim(code);
218
+ if (!result) {
219
+ return c.text("Claim expired", 400);
220
+ }
221
+
222
+ await setSignedCookie(c, "better-auth.session_token", result.token, env.BETTER_AUTH_SECRET, {
223
+ httpOnly: true,
224
+ secure: false,
225
+ sameSite: "Lax",
226
+ path: "/",
227
+ expires: result.expiresAt,
228
+ });
229
+
230
+ return c.redirect(env.DASHBOARD_URL);
231
+ });
232
+ }
233
+
234
+ /** Better Auth catch-all — handles all standard auth endpoints. */
235
+ authRoutes.on(["GET", "POST"], "/*", (c) => {
236
+ return auth.handler(c.req.raw);
237
+ });
@@ -1,8 +1,16 @@
1
1
  import { Hono } from "hono";
2
+ import { authMiddleware } from "../../middleware";
2
3
  import * as billingController from "./billing.controller";
3
4
 
4
5
  export const billingRoutes = new Hono();
5
6
 
7
+ // Auth required for all billing routes except webhook
8
+ billingRoutes.use("*", async (c, next) => {
9
+ // Stripe webhook uses signature verification, not session auth
10
+ if (c.req.path.endsWith("/webhook/stripe")) return next();
11
+ return authMiddleware(c, next);
12
+ });
13
+
6
14
  /* ---------- Plans & Pricing ---------- */
7
15
  billingRoutes.get("/plans", billingController.listPlans);
8
16
 
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Cloud controller — two sides of the same coin:
3
+ *
4
+ * SaaS (CLOUD_MODE):
5
+ * POST /api/cloud/token — mint namespace-scoped Oblien tokens
6
+ * GET /api/cloud/desktop-handoff — OAuth → one-time code → redirect to desktop
7
+ * POST /api/cloud/exchange-code — exchange code for user + session (no auth)
8
+ *
9
+ * Local (!CLOUD_MODE):
10
+ * POST /api/cloud/disconnect — clear stored session
11
+ * GET /api/cloud/status — check connection state
12
+ * GET /api/cloud/connect-callback — exchange code from external auth
13
+ */
14
+
15
+ import type { Context } from "hono";
16
+ import { getUserId } from "../../lib/controller-helpers";
17
+
18
+ // ─── SaaS: namespace token minting ───────────────────────────────────────────
19
+
20
+ export async function getToken(c: Context) {
21
+ const { issueNamespaceToken } = await import("../../lib/openship-cloud");
22
+ const userId = getUserId(c);
23
+ const result = await issueNamespaceToken(userId);
24
+ return c.json({ data: result });
25
+ }
26
+
27
+ // ─── SaaS: desktop OAuth handoff ─────────────────────────────────────────────
28
+
29
+ /**
30
+ * GET /api/cloud/desktop-handoff?redirect=<url>&state=<state>&code_challenge=<challenge>
31
+ *
32
+ * Called from the authorize page after the user clicks "Authorize".
33
+ * Reads the authenticated session, generates a one-time code, and
34
+ * redirects to the desktop's local callback with that code + state.
35
+ *
36
+ * Security:
37
+ * - redirect MUST be localhost (desktop callback) — no open redirect
38
+ * - state is passed through unchanged for CSRF protection
39
+ * - code_challenge (PKCE S256) is bound to the one-time code
40
+ */
41
+ export async function desktopHandoff(c: Context) {
42
+ const { auth } = await import("../../lib/auth");
43
+ const session = await auth.api.getSession({ headers: c.req.raw.headers });
44
+ if (!session) {
45
+ return c.json({ error: "No active session" }, 401);
46
+ }
47
+
48
+ const redirect = c.req.query("redirect");
49
+ if (!redirect) {
50
+ return c.json({ error: "Missing redirect parameter" }, 400);
51
+ }
52
+
53
+ // Security: only allow localhost — desktop is the only consumer
54
+ let url: URL;
55
+ try {
56
+ url = new URL(redirect);
57
+ } catch {
58
+ return c.json({ error: "Invalid redirect URL" }, 400);
59
+ }
60
+ if (url.hostname !== "localhost" && url.hostname !== "127.0.0.1") {
61
+ return c.json({ error: "Redirect must target localhost" }, 400);
62
+ }
63
+ // Restrict to safe port range — prevent redirect to unrelated local services
64
+ const port = parseInt(url.port || "80", 10);
65
+ if (port < 1024 || port > 65535) {
66
+ return c.json({ error: "Redirect port must be ≥ 1024" }, 400);
67
+ }
68
+
69
+ const state = c.req.query("state");
70
+ const codeChallenge = c.req.query("code_challenge");
71
+
72
+ const { generateHandoffCode } = await import("../../lib/cloud-auth-proxy");
73
+ const code = await generateHandoffCode(
74
+ {
75
+ id: session.user.id,
76
+ name: session.user.name,
77
+ email: session.user.email,
78
+ emailVerified: session.user.emailVerified,
79
+ image: session.user.image,
80
+ },
81
+ session.session.token,
82
+ codeChallenge || undefined,
83
+ );
84
+
85
+ url.searchParams.set("code", code);
86
+ if (state) url.searchParams.set("state", state);
87
+ return c.redirect(url.toString());
88
+ }
89
+
90
+ /**
91
+ * GET /api/cloud/connect-handoff?redirect=<url>
92
+ *
93
+ * Used by self-hosted "Connect to Cloud" flow. After the user logs in
94
+ * on the SaaS, the login page redirects here. This generates a one-time
95
+ * code and redirects back to the self-hosted callback.
96
+ *
97
+ * Security:
98
+ * - redirect MUST be HTTPS (no downgrade to HTTP)
99
+ * - No PKCE (self-hosted server handles its own security)
100
+ * - Codes are single-use with 60s TTL
101
+ */
102
+ export async function connectHandoff(c: Context) {
103
+ const { auth } = await import("../../lib/auth");
104
+ const session = await auth.api.getSession({ headers: c.req.raw.headers });
105
+ if (!session) {
106
+ return c.json({ error: "No active session" }, 401);
107
+ }
108
+
109
+ const redirect = c.req.query("redirect");
110
+ if (!redirect) {
111
+ return c.json({ error: "Missing redirect parameter" }, 400);
112
+ }
113
+
114
+ let url: URL;
115
+ try {
116
+ url = new URL(redirect);
117
+ } catch {
118
+ return c.json({ error: "Invalid redirect URL" }, 400);
119
+ }
120
+
121
+ // Allow localhost HTTP (desktop connects via http://localhost:PORT)
122
+ // and require HTTPS for all other hosts (self-hosted production)
123
+ const isLocalhost = url.hostname === "localhost" || url.hostname === "127.0.0.1";
124
+ if (!isLocalhost && url.protocol !== "https:") {
125
+ return c.json({ error: "Redirect must use HTTPS" }, 400);
126
+ }
127
+ if (isLocalhost) {
128
+ const port = parseInt(url.port || "80", 10);
129
+ if (port < 1024 || port > 65535) {
130
+ return c.json({ error: "Redirect port must be ≥ 1024" }, 400);
131
+ }
132
+ }
133
+
134
+ const { generateHandoffCode } = await import("../../lib/cloud-auth-proxy");
135
+ const code = await generateHandoffCode(
136
+ {
137
+ id: session.user.id,
138
+ name: session.user.name,
139
+ email: session.user.email,
140
+ emailVerified: session.user.emailVerified,
141
+ image: session.user.image,
142
+ },
143
+ session.session.token,
144
+ );
145
+
146
+ url.searchParams.set("code", code);
147
+ return c.redirect(url.toString());
148
+ }
149
+
150
+ /**
151
+ * POST /api/cloud/exchange-code { code: string, code_verifier?: string }
152
+ *
153
+ * Called by the desktop instance to exchange a one-time code for the OAuth
154
+ * user data + cloud session token. No auth required — the code is the credential.
155
+ *
156
+ * If the code was generated with a PKCE code_challenge, the matching
157
+ * code_verifier must be provided or the exchange is rejected.
158
+ */
159
+ export async function exchangeCode(c: Context) {
160
+ const body = await c.req.json<{ code: string; code_verifier?: string }>();
161
+ if (!body.code) {
162
+ return c.json({ error: "Code required" }, 400);
163
+ }
164
+
165
+ const { exchangeHandoffCode } = await import("../../lib/cloud-auth-proxy");
166
+ const result = exchangeHandoffCode(body.code, body.code_verifier);
167
+ if (!result) {
168
+ return c.json({ error: "Invalid or expired code" }, 401);
169
+ }
170
+
171
+ return c.json({ data: result });
172
+ }
173
+
174
+ // ─── Local: cloud account management ─────────────────────────────────────────
175
+
176
+ export async function disconnect(c: Context) {
177
+ const userId = getUserId(c);
178
+ const { disconnectCloud } = await import("../../lib/cloud-client");
179
+ await disconnectCloud(userId);
180
+ return c.json({ connected: false });
181
+ }
182
+
183
+ export async function status(c: Context) {
184
+ const userId = getUserId(c);
185
+ const { isCloudConnected } = await import("../../lib/cloud-client");
186
+ const connected = await isCloudConnected(userId);
187
+ if (!connected) return c.json({ connected: false });
188
+
189
+ // Return cloud user info alongside status
190
+ const user = c.get("user");
191
+ return c.json({
192
+ connected: true,
193
+ user: user ? { name: user.name, email: user.email, image: user.image } : undefined,
194
+ });
195
+ }
196
+
197
+ /**
198
+ * GET /api/cloud/connect-callback?code=<one-time-code>
199
+ *
200
+ * Used by self-hosted settings page. After the user authenticates on
201
+ * Openship Cloud, they're redirected here with a code.
202
+ * We exchange it for the cloud session token and store it for deploys.
203
+ */
204
+ export async function connectCallback(c: Context) {
205
+ const userId = getUserId(c);
206
+ const code = c.req.query("code");
207
+ if (!code) {
208
+ return c.redirect("/settings?error=missing_code");
209
+ }
210
+
211
+ try {
212
+ const { exchangeCodeWithCloud, storeCloudSession } = await import("../../lib/cloud-auth-proxy");
213
+
214
+ const data = await exchangeCodeWithCloud(code);
215
+ if (!data) {
216
+ return c.redirect("/settings?error=exchange_failed");
217
+ }
218
+
219
+ await storeCloudSession(userId, data.sessionToken);
220
+
221
+ return c.redirect("/settings?cloud=connected");
222
+ } catch {
223
+ return c.redirect("/settings?error=connect_failed");
224
+ }
225
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Cloud routes — mounted at /api/cloud in app.ts.
3
+ *
4
+ * Two route sets from the same module:
5
+ * cloudSaasRoutes (CLOUD_MODE) — POST /token
6
+ * cloudLocalRoutes (!CLOUD_MODE) — POST /connect, POST /disconnect, GET /status
7
+ */
8
+
9
+ import { Hono } from "hono";
10
+ import { authMiddleware } from "../../middleware";
11
+ import { rateLimiter } from "../../middleware/rate-limiter";
12
+ import * as ctrl from "./cloud.controller";
13
+
14
+ /** SaaS routes — mints namespace tokens for local instances + desktop OAuth handoff */
15
+ export const cloudSaasRoutes = new Hono();
16
+
17
+ // Desktop OAuth handoff — localhost only, PKCE + state
18
+ cloudSaasRoutes.get("/desktop-handoff", ctrl.desktopHandoff);
19
+ // Self-hosted connect handoff — HTTPS only
20
+ cloudSaasRoutes.get("/connect-handoff", ctrl.connectHandoff);
21
+ // Code exchange — no auth (code is the credential), rate-limited
22
+ cloudSaasRoutes.use("/exchange-code", rateLimiter);
23
+ cloudSaasRoutes.post("/exchange-code", ctrl.exchangeCode);
24
+
25
+ // Token minting requires auth
26
+ cloudSaasRoutes.use("/token", authMiddleware);
27
+ cloudSaasRoutes.post("/token", ctrl.getToken);
28
+
29
+ /** Local routes — manage connection to Openship Cloud */
30
+ export const cloudLocalRoutes = new Hono();
31
+ cloudLocalRoutes.use("*", authMiddleware);
32
+ cloudLocalRoutes.post("/disconnect", ctrl.disconnect);
33
+ cloudLocalRoutes.get("/status", ctrl.status);
34
+
35
+ /**
36
+ * Cloud connect callback — used by self-hosted settings page.
37
+ * After the user authenticates on Openship Cloud, they're redirected
38
+ * here with a one-time code. We exchange it and store the cloud token.
39
+ */
40
+ cloudLocalRoutes.get("/connect-callback", ctrl.connectCallback);