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.
- package/.env.example +19 -3
- package/CONTRIBUTING.md +69 -19
- package/LICENSE +661 -0
- package/README.md +304 -108
- package/apps/api/Dockerfile +6 -8
- package/apps/api/package.json +25 -21
- package/apps/api/src/app.ts +46 -3
- package/apps/api/src/config/env.ts +81 -7
- package/apps/api/src/lib/auth-mode.ts +42 -0
- package/apps/api/src/lib/auth.ts +105 -0
- package/apps/api/src/lib/cache.ts +128 -0
- package/apps/api/src/lib/cloud-auth-proxy.ts +310 -0
- package/apps/api/src/lib/cloud-client.ts +103 -0
- package/apps/api/src/lib/compose-parser.ts +138 -0
- package/apps/api/src/lib/controller-helpers.ts +90 -0
- package/apps/api/src/lib/email-templates.ts +94 -0
- package/apps/api/src/lib/encryption.ts +102 -0
- package/apps/api/src/lib/local-user.ts +65 -0
- package/apps/api/src/lib/mail.ts +48 -0
- package/apps/api/src/lib/notifications.ts +195 -0
- package/apps/api/src/lib/openship-cloud.ts +101 -0
- package/apps/api/src/lib/resources.ts +96 -0
- package/apps/api/src/lib/screenshots.ts +189 -0
- package/apps/api/src/lib/ssh-manager.ts +369 -0
- package/apps/api/src/lib/ssl-scheduler.ts +109 -0
- package/apps/api/src/lib/stack-detector.ts +708 -0
- package/apps/api/src/lib/system-debug.ts +10 -0
- package/apps/api/src/middleware/auth.ts +49 -13
- package/apps/api/src/middleware/error-handler.ts +23 -3
- package/apps/api/src/middleware/index.ts +2 -0
- package/apps/api/src/middleware/internal-auth.ts +40 -0
- package/apps/api/src/middleware/local-only.ts +20 -0
- package/apps/api/src/middleware/rate-limiter.ts +9 -0
- package/apps/api/src/modules/analytics/analytics.controller.ts +111 -0
- package/apps/api/src/modules/analytics/analytics.routes.ts +29 -0
- package/apps/api/src/modules/analytics/analytics.schema.ts +37 -0
- package/apps/api/src/modules/analytics/analytics.service.ts +264 -0
- package/apps/api/src/modules/auth/auth.routes.ts +233 -6
- package/apps/api/src/modules/billing/billing.routes.ts +8 -0
- package/apps/api/src/modules/cloud/cloud.controller.ts +225 -0
- package/apps/api/src/modules/cloud/cloud.routes.ts +40 -0
- package/apps/api/src/modules/deployments/build.service.ts +791 -0
- package/apps/api/src/modules/deployments/deployment-lifecycle.ts +138 -0
- package/apps/api/src/modules/deployments/deployment.controller.ts +324 -16
- package/apps/api/src/modules/deployments/deployment.routes.ts +33 -7
- package/apps/api/src/modules/deployments/deployment.schema.ts +37 -5
- package/apps/api/src/modules/deployments/deployment.service.ts +178 -11
- package/apps/api/src/modules/deployments/preflight.ts +280 -0
- package/apps/api/src/modules/deployments/prepare.service.ts +252 -0
- package/apps/api/src/modules/deployments/session-manager.ts +283 -0
- package/apps/api/src/modules/deployments/ssl.service.ts +73 -0
- package/apps/api/src/modules/domains/domain.controller.ts +59 -7
- package/apps/api/src/modules/domains/domain.routes.ts +20 -5
- package/apps/api/src/modules/domains/domain.schema.ts +35 -0
- package/apps/api/src/modules/domains/domain.service.ts +375 -9
- package/apps/api/src/modules/github/github.auth.ts +397 -0
- package/apps/api/src/modules/github/github.controller.ts +387 -0
- package/apps/api/src/modules/github/github.local-auth.ts +251 -0
- package/apps/api/src/modules/github/github.routes.ts +50 -0
- package/apps/api/src/modules/github/github.schema.ts +52 -0
- package/apps/api/src/modules/github/github.service.ts +592 -0
- package/apps/api/src/modules/github/github.types.ts +158 -0
- package/apps/api/src/modules/github/github.webhook.ts +186 -0
- package/apps/api/src/modules/github/index.ts +16 -0
- package/apps/api/src/modules/health/health.routes.ts +35 -0
- package/apps/api/src/modules/projects/project.controller.ts +481 -12
- package/apps/api/src/modules/projects/project.routes.ts +63 -6
- package/apps/api/src/modules/projects/project.schema.ts +104 -6
- package/apps/api/src/modules/projects/project.service.ts +502 -11
- package/apps/api/src/modules/settings/settings.controller.ts +55 -0
- package/apps/api/src/modules/settings/settings.routes.ts +25 -0
- package/apps/api/src/modules/settings/settings.service.ts +49 -0
- package/apps/api/src/modules/system/filesystem.controller.ts +66 -0
- package/apps/api/src/modules/system/index.ts +1 -0
- package/apps/api/src/modules/system/server-check.controller.ts +475 -0
- package/apps/api/src/modules/system/setup-session.ts +255 -0
- package/apps/api/src/modules/system/setup.controller.ts +128 -0
- package/apps/api/src/modules/system/system.routes.ts +42 -0
- package/apps/api/src/modules/webhooks/webhook.controller.ts +57 -15
- package/apps/api/src/modules/webhooks/webhook.routes.ts +11 -8
- package/apps/api/src/modules/webhooks/webhook.service.ts +46 -5
- package/apps/api/src/modules/webhooks/webhook.types.ts +35 -0
- package/apps/api/tsconfig.json +3 -1
- package/apps/cli/package.json +8 -10
- package/apps/cli/tsconfig.json +2 -1
- package/apps/dashboard/Dockerfile +4 -6
- package/apps/dashboard/components.json +20 -0
- package/apps/dashboard/next-env.d.ts +6 -0
- package/apps/dashboard/next.config.mjs +11 -1
- package/apps/dashboard/package.json +25 -13
- package/apps/dashboard/postcss.config.mjs +5 -0
- package/apps/dashboard/public/android-chrome-192x192.png +0 -0
- package/apps/dashboard/public/android-chrome-512x512.png +0 -0
- package/apps/dashboard/public/apple-touch-icon.png +0 -0
- package/apps/dashboard/public/favicon-16x16.png +0 -0
- package/apps/dashboard/public/favicon-32x32.png +0 -0
- package/apps/dashboard/public/favicon.ico +0 -0
- package/apps/dashboard/public/site.webmanifest +19 -0
- package/apps/dashboard/src/app/(auth)/forgot-password/page.tsx +114 -0
- package/apps/dashboard/src/app/(auth)/layout.tsx +18 -3
- package/apps/dashboard/src/app/(auth)/login/page.tsx +215 -6
- package/apps/dashboard/src/app/(auth)/providers.tsx +34 -0
- package/apps/dashboard/src/app/(auth)/register/page.tsx +171 -5
- package/apps/dashboard/src/app/(auth)/reset-password/page.tsx +172 -0
- package/apps/dashboard/src/app/(auth)/verify-email/page.tsx +190 -0
- package/apps/dashboard/src/app/(dashboard)/(deployment)/build/[id]/page.tsx +162 -0
- package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/BuildSummary.tsx +166 -0
- package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/DnsConfiguration.tsx +119 -0
- package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/DomainSettings.tsx +291 -0
- package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/Header.tsx +71 -0
- package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/Sidebar.tsx +270 -0
- package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/SkeletonLoader.tsx +87 -0
- package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/page.tsx +169 -0
- package/apps/dashboard/src/app/(dashboard)/(deployment)/layout.tsx +7 -0
- package/apps/dashboard/src/app/(dashboard)/billing/page.tsx +1 -8
- package/apps/dashboard/src/app/(dashboard)/deployments/components/CommitDetailsModal.tsx +240 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentCard.tsx +121 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentHeader.tsx +89 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentMenu.tsx +182 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentsContent.tsx +324 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentsFilters.tsx +99 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentsList.tsx +31 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/EmptyState.tsx +101 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/LoadingSkeleton.tsx +57 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/ProjectFilter.tsx +109 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/components/index.ts +9 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/page.tsx +9 -3
- package/apps/dashboard/src/app/(dashboard)/deployments/types.ts +44 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/utils.ts +176 -0
- package/apps/dashboard/src/app/(dashboard)/domains/page.tsx +1 -6
- package/apps/dashboard/src/app/(dashboard)/integrations/page.tsx +321 -0
- package/apps/dashboard/src/app/(dashboard)/layout.tsx +22 -32
- package/apps/dashboard/src/app/(dashboard)/library/components/ConnectPrompt.tsx +187 -0
- package/apps/dashboard/src/app/(dashboard)/library/components/LibrarySidebar.tsx +147 -0
- package/apps/dashboard/src/app/(dashboard)/library/components/LoadingSkeleton.tsx +39 -0
- package/apps/dashboard/src/app/(dashboard)/library/components/LocalProjects.tsx +548 -0
- package/apps/dashboard/src/app/(dashboard)/library/components/RepositoryList.tsx +292 -0
- package/apps/dashboard/src/app/(dashboard)/library/components/TemplateGrid.tsx +36 -0
- package/apps/dashboard/src/app/(dashboard)/library/components/UrlImport.tsx +74 -0
- package/apps/dashboard/src/app/(dashboard)/library/page.tsx +121 -0
- package/apps/dashboard/src/app/(dashboard)/library/types.ts +26 -0
- package/apps/dashboard/src/app/(dashboard)/monitoring/page.tsx +1 -6
- package/apps/dashboard/src/app/(dashboard)/page.tsx +485 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/[[...slug]]/layout.tsx +22 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/[[...slug]]/page.tsx +286 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/AdvancedSettings.tsx +235 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/BuildSettings.tsx +174 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/DeleteConfirmationDialog.tsx +45 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/DeletionModal.tsx +95 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/Deployments.tsx +11 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/DomainSettings.tsx +523 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/EnvironmentSettings.tsx +246 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/GeneralSettings.tsx +93 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/GitInfo.tsx +261 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/GitSettings.tsx +391 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/LogsSettings.tsx +133 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/ProjectNotFound.tsx +95 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/ProjectsBottomNavigation.tsx +62 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/ResourceSettings.tsx +333 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/SettingSection.tsx +66 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/SettingsNavigation.tsx +27 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/SleepModeSettings.tsx +134 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/ProductionUrl.tsx +25 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/ProjectIdentity.tsx +159 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/ProjectInfo.tsx +58 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/SkeletonLoader.tsx +170 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/StatsCards.tsx +33 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/TopPaths.tsx +56 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/TrafficChart.tsx +157 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/index.ts +14 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/LogsActions.tsx +52 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/ServerLogs.tsx +249 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/TerminalLogs.tsx +682 -0
- package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/logs.css +127 -0
- package/apps/dashboard/src/app/(dashboard)/projects/components/DeploymentCard.tsx +73 -0
- package/apps/dashboard/src/app/(dashboard)/projects/components/ProjectCard.tsx +330 -0
- package/apps/dashboard/src/app/(dashboard)/projects/page.tsx +326 -4
- package/apps/dashboard/src/app/(dashboard)/providers.tsx +23 -0
- package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/_components/components-tab.tsx +329 -0
- package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/_components/overview-tab.tsx +229 -0
- package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/_components/terminal-tab.tsx +38 -0
- package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/page.tsx +437 -0
- package/apps/dashboard/src/app/(dashboard)/servers/new/_components/checking-state.tsx +15 -0
- package/apps/dashboard/src/app/(dashboard)/servers/new/_components/choose-mode.tsx +54 -0
- package/apps/dashboard/src/app/(dashboard)/servers/new/_components/component-row.tsx +65 -0
- package/apps/dashboard/src/app/(dashboard)/servers/new/_components/error-banner.tsx +13 -0
- package/apps/dashboard/src/app/(dashboard)/servers/new/_components/installing-panel.tsx +227 -0
- package/apps/dashboard/src/app/(dashboard)/servers/new/_components/results-panel.tsx +90 -0
- package/apps/dashboard/src/app/(dashboard)/servers/new/_components/setup-header.tsx +56 -0
- package/apps/dashboard/src/app/(dashboard)/servers/new/_components/types.ts +13 -0
- package/apps/dashboard/src/app/(dashboard)/servers/new/page.tsx +666 -0
- package/apps/dashboard/src/app/(dashboard)/servers/page.tsx +283 -0
- package/apps/dashboard/src/app/(dashboard)/settings/_components/BuildPreferences.tsx +110 -0
- package/apps/dashboard/src/app/(dashboard)/settings/_components/CloudConnection.tsx +147 -0
- package/apps/dashboard/src/app/(dashboard)/settings/_components/InstanceInfo.tsx +54 -0
- package/apps/dashboard/src/app/(dashboard)/settings/_components/ServerConnection.tsx +407 -0
- package/apps/dashboard/src/app/(dashboard)/settings/_components/SettingsSection.tsx +32 -0
- package/apps/dashboard/src/app/(dashboard)/settings/page.tsx +62 -3
- package/apps/dashboard/src/app/auth/callback/close/page.tsx +23 -0
- package/apps/dashboard/src/app/auth/callback/install/page.tsx +40 -0
- package/apps/dashboard/src/app/authorize/page.tsx +137 -0
- package/apps/dashboard/src/app/globals.css +57 -3
- package/apps/dashboard/src/app/layout.tsx +43 -3
- package/apps/dashboard/src/components/auth-shell.tsx +66 -0
- package/apps/dashboard/src/components/deployments/types.ts +44 -0
- package/apps/dashboard/src/components/global-deployments/index.ts +17 -0
- package/apps/dashboard/src/components/i18n-provider.tsx +100 -0
- package/apps/dashboard/src/components/import-project/BuildSettings.tsx +376 -0
- package/apps/dashboard/src/components/import-project/BuildSkeleton.tsx +161 -0
- package/apps/dashboard/src/components/import-project/BuildTerminal.tsx +358 -0
- package/apps/dashboard/src/components/import-project/ComposeServices.tsx +455 -0
- package/apps/dashboard/src/components/import-project/DeploymentProcessing.tsx +510 -0
- package/apps/dashboard/src/components/import-project/DockerSettings.tsx +64 -0
- package/apps/dashboard/src/components/import-project/EnvironmentVariables.tsx +481 -0
- package/apps/dashboard/src/components/import-project/Frameworks.tsx +90 -0
- package/apps/dashboard/src/components/import-project/ProjectSettings.tsx +160 -0
- package/apps/dashboard/src/components/import-project/types.ts +82 -0
- package/apps/dashboard/src/components/integrations/GithubPermissionModal.tsx +173 -0
- package/apps/dashboard/src/components/language-switcher.tsx +36 -0
- package/apps/dashboard/src/components/logo.tsx +13 -0
- package/apps/dashboard/src/components/network-error-handler.tsx +27 -0
- package/apps/dashboard/src/components/oauth-buttons.tsx +81 -0
- package/apps/dashboard/src/components/overview/ActivityChart.tsx +247 -0
- package/apps/dashboard/src/components/overview/ApiRequestsCard.tsx +111 -0
- package/apps/dashboard/src/components/overview/CreditsCard.tsx +137 -0
- package/apps/dashboard/src/components/overview/DeploymentsChart.tsx +195 -0
- package/apps/dashboard/src/components/overview/EmptyState.tsx +126 -0
- package/apps/dashboard/src/components/overview/LoadingSkeleton.tsx +92 -0
- package/apps/dashboard/src/components/overview/OverviewHeader.tsx +74 -0
- package/apps/dashboard/src/components/overview/OverviewHeroChart.tsx +344 -0
- package/apps/dashboard/src/components/overview/ProjectsCard.tsx +134 -0
- package/apps/dashboard/src/components/overview/QuickLinks.tsx +119 -0
- package/apps/dashboard/src/components/overview/SandboxesCard.tsx +150 -0
- package/apps/dashboard/src/components/overview/StatsGrid.tsx +166 -0
- package/apps/dashboard/src/components/overview/TokenUsageChart.tsx +206 -0
- package/apps/dashboard/src/components/overview/UsageStats.tsx +79 -0
- package/apps/dashboard/src/components/overview/index.ts +30 -0
- package/apps/dashboard/src/components/overview/types.ts +116 -0
- package/apps/dashboard/src/components/page-header.tsx +25 -0
- package/apps/dashboard/src/components/project-settings/ServerSideSwitch.jsx +60 -0
- package/apps/dashboard/src/components/shared/AlertBox.tsx +39 -0
- package/apps/dashboard/src/components/shared/CTASection.tsx +45 -0
- package/apps/dashboard/src/components/shared/ErrorState.tsx +176 -0
- package/apps/dashboard/src/components/shared/FeatureCard.tsx +20 -0
- package/apps/dashboard/src/components/shared/MDXComponents.tsx +24 -0
- package/apps/dashboard/src/components/shared/MachineSettingsModal.tsx +286 -0
- package/apps/dashboard/src/components/shared/Modal.tsx +39 -0
- package/apps/dashboard/src/components/shared/OTPInput.tsx +122 -0
- package/apps/dashboard/src/components/shared/PageHero.tsx +21 -0
- package/apps/dashboard/src/components/shared/PlatformFeatureCard.tsx +27 -0
- package/apps/dashboard/src/components/shared/StatCard.tsx +14 -0
- package/apps/dashboard/src/components/shared/ValueCard.tsx +20 -0
- package/apps/dashboard/src/components/shared/index.ts +8 -0
- package/apps/dashboard/src/components/sidebar.tsx +282 -0
- package/apps/dashboard/src/components/theme-provider.tsx +96 -0
- package/apps/dashboard/src/components/toast.tsx +106 -0
- package/apps/dashboard/src/components/ui/CustomCursor.tsx +107 -0
- package/apps/dashboard/src/components/ui/CustomSelect.tsx +126 -0
- package/apps/dashboard/src/components/ui/DropdownMenu.tsx +174 -0
- package/apps/dashboard/src/components/ui/FileIcon.jsx +59 -0
- package/apps/dashboard/src/components/ui/IconPickerModal.tsx +273 -0
- package/apps/dashboard/src/components/ui/InfoBanner.jsx +39 -0
- package/apps/dashboard/src/components/ui/Logo.tsx +14 -0
- package/apps/dashboard/src/components/ui/Modal.tsx +117 -0
- package/apps/dashboard/src/components/ui/PageContainer.tsx +12 -0
- package/apps/dashboard/src/components/ui/SectionContainer.jsx +7 -0
- package/apps/dashboard/src/components/ui/SlidingToggle.tsx +128 -0
- package/apps/dashboard/src/components/ui/Toast.tsx +29 -0
- package/apps/dashboard/src/components/ui/button.tsx +58 -0
- package/apps/dashboard/src/components/ui/card.tsx +60 -0
- package/apps/dashboard/src/components/ui/input.tsx +20 -0
- package/apps/dashboard/src/components/ui/label.tsx +19 -0
- package/apps/dashboard/src/components/ui/select.tsx +10 -0
- package/apps/dashboard/src/components/ui/textarea.tsx +9 -0
- package/apps/dashboard/src/constants/lang-colors.ts +25 -0
- package/apps/dashboard/src/constants/mock.ts +38 -0
- package/apps/dashboard/src/context/AuthContext.tsx +91 -0
- package/apps/dashboard/src/context/CloudContext.tsx +275 -0
- package/apps/dashboard/src/context/DeploymentContext.tsx +71 -0
- package/apps/dashboard/src/context/GitHubContext.tsx +288 -0
- package/apps/dashboard/src/context/ModalContext.tsx +300 -0
- package/apps/dashboard/src/context/NetworkContext.tsx +32 -0
- package/apps/dashboard/src/context/ProjectSettingsContext.tsx +727 -0
- package/apps/dashboard/src/context/ToastContext.tsx +129 -0
- package/apps/dashboard/src/context/deployment/index.ts +3 -0
- package/apps/dashboard/src/context/deployment/types.ts +183 -0
- package/apps/dashboard/src/context/deployment/useDeploymentBuild.ts +547 -0
- package/apps/dashboard/src/context/deployment/useDeploymentConfig.ts +176 -0
- package/apps/dashboard/src/hooks/useBuildConnection.ts +334 -0
- package/apps/dashboard/src/hooks/useMonitorStream.ts +146 -0
- package/apps/dashboard/src/hooks/useProjectData.ts +110 -0
- package/apps/dashboard/src/hooks/useSSEConnection.ts +465 -0
- package/apps/dashboard/src/hooks/useSSEStream.ts +309 -0
- package/apps/dashboard/src/hooks/useSessions.ts +104 -0
- package/apps/dashboard/src/hooks/useSetupStream.ts +223 -0
- package/apps/dashboard/src/i18n/dictionaries/ar.json +218 -0
- package/apps/dashboard/src/i18n/dictionaries/en.json +218 -0
- package/apps/dashboard/src/i18n/index.ts +16 -0
- package/apps/dashboard/src/lib/api/ai.ts +22 -0
- package/apps/dashboard/src/lib/api/auth.ts +13 -0
- package/apps/dashboard/src/lib/api/client.ts +199 -0
- package/apps/dashboard/src/lib/api/cloud.ts +15 -0
- package/apps/dashboard/src/lib/api/deploy.ts +58 -0
- package/apps/dashboard/src/lib/api/domains.ts +13 -0
- package/apps/dashboard/src/lib/api/endpoints.ts +129 -0
- package/apps/dashboard/src/lib/api/github.ts +31 -0
- package/apps/dashboard/src/lib/api/icons.ts +12 -0
- package/apps/dashboard/src/lib/api/index.ts +41 -0
- package/apps/dashboard/src/lib/api/projects.ts +153 -0
- package/apps/dashboard/src/lib/api/sandbox.ts +14 -0
- package/apps/dashboard/src/lib/api/settings.ts +21 -0
- package/apps/dashboard/src/lib/api/system.ts +159 -0
- package/apps/dashboard/src/lib/auth-client.ts +54 -0
- package/apps/dashboard/src/lib/country.ts +25 -0
- package/apps/dashboard/src/lib/server/api.ts +140 -0
- package/apps/dashboard/src/lib/server/session.ts +103 -0
- package/apps/dashboard/src/lib/sseClient.ts +241 -0
- package/apps/dashboard/src/lib/sseMessageProcessors.ts +323 -0
- package/apps/dashboard/src/lib/utils.ts +6 -0
- package/apps/dashboard/src/proxy.ts +46 -0
- package/apps/dashboard/src/styles/Toast.module.css +92 -0
- package/apps/dashboard/src/styles/fonts.css +93 -0
- package/apps/dashboard/src/styles/theme.css +336 -0
- package/apps/dashboard/src/types/research.ts +42 -0
- package/apps/dashboard/src/utils/SSEParser.js +150 -0
- package/apps/dashboard/src/utils/authWindow.ts +143 -0
- package/apps/dashboard/src/utils/codeTheme.ts +127 -0
- package/apps/dashboard/src/utils/colors.json +2810 -0
- package/apps/dashboard/src/utils/date.ts +121 -0
- package/apps/dashboard/src/utils/deployment.ts +128 -0
- package/apps/dashboard/src/utils/deploymentPhaseDetector.ts +232 -0
- package/apps/dashboard/src/utils/extToLang.js +103 -0
- package/apps/dashboard/src/utils/github.ts +62 -0
- package/apps/dashboard/src/utils/icons.js +46 -0
- package/apps/dashboard/src/utils/repoSlug.ts +110 -0
- package/apps/dashboard/src/utils/research-api.ts +163 -0
- package/apps/dashboard/src/utils/suggestions.ts +84 -0
- package/apps/dashboard/src/utils/theme.js +1 -0
- package/apps/dashboard/src/utils/ui-helpers.tsx +30 -0
- package/apps/dashboard/src/utils/upload.js +12 -0
- package/apps/dashboard/tsconfig.json +27 -5
- package/apps/desktop/forge.config.json +36 -0
- package/apps/desktop/package.json +26 -0
- package/apps/desktop/scripts/dev.mjs +58 -0
- package/apps/desktop/src/main/index.ts +675 -0
- package/apps/desktop/src/main/types/desktop.d.ts +33 -0
- package/apps/desktop/src/preload/index.ts +88 -0
- package/apps/desktop/src/renderer/index.html +462 -0
- package/apps/desktop/src/renderer/onboarding.js +511 -0
- package/apps/desktop/src/renderer/styles.css +1139 -0
- package/apps/desktop/tsconfig.json +17 -0
- package/apps/web/.source/browser.ts +13 -0
- package/apps/web/.source/dynamic.ts +8 -0
- package/apps/web/.source/server.ts +22 -0
- package/apps/web/.source/source.config.mjs +20 -0
- package/apps/web/Dockerfile +4 -6
- package/apps/web/content/blog/how-ai-builds-work.mdx +55 -0
- package/apps/web/content/blog/introducing-openship.mdx +46 -0
- package/apps/web/content/blog/self-hosting-cost-breakdown.mdx +59 -0
- package/apps/web/content/docs/_meta.json +14 -0
- package/apps/web/content/docs/api.mdx +98 -0
- package/apps/web/content/docs/cli.mdx +80 -0
- package/apps/web/content/docs/first-deployment.mdx +56 -0
- package/apps/web/content/docs/index.mdx +27 -0
- package/apps/web/content/docs/installation.mdx +65 -0
- package/apps/web/content/docs/quickstart.mdx +45 -0
- package/apps/web/next-env.d.ts +6 -0
- package/apps/web/next.config.mjs +16 -2
- package/apps/web/package.json +19 -12
- package/apps/web/postcss.config.mjs +5 -0
- package/apps/web/public/android-chrome-192x192.png +0 -0
- package/apps/web/public/android-chrome-512x512.png +0 -0
- package/apps/web/public/apple-touch-icon.png +0 -0
- package/apps/web/public/favicon-16x16.png +0 -0
- package/apps/web/public/favicon-32x32.png +0 -0
- package/apps/web/public/favicon.ico +0 -0
- package/apps/web/public/site.webmanifest +1 -0
- package/apps/web/source.config.ts +17 -0
- package/apps/web/src/app/(marketing)/layout.tsx +7 -22
- package/apps/web/src/app/blog/[...slug]/page.tsx +247 -0
- package/apps/web/src/app/blog/blog.css +575 -0
- package/apps/web/src/app/blog/layout.tsx +50 -0
- package/apps/web/src/app/blog/page.tsx +235 -0
- package/apps/web/src/app/docs/[[...slug]]/page.tsx +67 -0
- package/apps/web/src/app/docs/layout.tsx +62 -0
- package/apps/web/src/app/docs/toc-theme.css +71 -0
- package/apps/web/src/app/download/page.tsx +396 -0
- package/apps/web/src/app/globals.css +20 -3
- package/apps/web/src/app/layout.tsx +57 -3
- package/apps/web/src/app/page.tsx +47 -6
- package/apps/web/src/app/robots.ts +15 -0
- package/apps/web/src/app/sitemap.ts +35 -0
- package/apps/web/src/components/blog/share-sidebar.tsx +81 -0
- package/apps/web/src/components/blog/writer-sidebar.tsx +81 -0
- package/apps/web/src/components/landing/ai-native.tsx +169 -0
- package/apps/web/src/components/landing/backups.tsx +139 -0
- package/apps/web/src/components/landing/cdn.tsx +109 -0
- package/apps/web/src/components/landing/coming-soon.tsx +180 -0
- package/apps/web/src/components/landing/dark-section.tsx +166 -0
- package/apps/web/src/components/landing/dashboard.tsx +407 -0
- package/apps/web/src/components/landing/developer-experience.tsx +224 -0
- package/apps/web/src/components/landing/dns-domains.tsx +146 -0
- package/apps/web/src/components/landing/download-button.tsx +151 -0
- package/apps/web/src/components/landing/features/ai-visual.tsx +48 -0
- package/apps/web/src/components/landing/features/cloud-visual.tsx +44 -0
- package/apps/web/src/components/landing/features/git-visual.tsx +61 -0
- package/apps/web/src/components/landing/features/index.tsx +642 -0
- package/apps/web/src/components/landing/features/infra-visual.tsx +69 -0
- package/apps/web/src/components/landing/features/rollback-visual.tsx +42 -0
- package/apps/web/src/components/landing/features/ssl-visual.tsx +41 -0
- package/apps/web/src/components/landing/features/stacks-visual.tsx +54 -0
- package/apps/web/src/components/landing/final-cta.tsx +116 -0
- package/apps/web/src/components/landing/footer.tsx +145 -0
- package/apps/web/src/components/landing/hero.tsx +110 -0
- package/apps/web/src/components/landing/how-it-works.tsx +210 -0
- package/apps/web/src/components/landing/index.ts +21 -0
- package/apps/web/src/components/landing/mail-server.tsx +122 -0
- package/apps/web/src/components/landing/navbar.tsx +199 -0
- package/apps/web/src/components/landing/no-lockin.tsx +185 -0
- package/apps/web/src/components/landing/open-source-cta.tsx +151 -0
- package/apps/web/src/components/landing/portability.tsx +190 -0
- package/apps/web/src/components/landing/pricing.tsx +263 -0
- package/apps/web/src/components/landing/scaling.tsx +209 -0
- package/apps/web/src/components/landing/why-openship.tsx +291 -0
- package/apps/web/src/hooks/use-gsap-scroll.ts +158 -0
- package/apps/web/src/hooks/use-platform.ts +86 -0
- package/apps/web/src/lib/source.ts +12 -0
- package/apps/web/src/styles/fonts.css +84 -0
- package/apps/web/src/styles/landing.css +951 -0
- package/apps/web/src/styles/theme.css +227 -0
- package/apps/web/tsconfig.json +8 -3
- package/bun.lock +3260 -0
- package/docker-compose.yml +3 -0
- package/docs/screenshots/.gitkeep +0 -0
- package/docs/screenshots/screen.png +0 -0
- package/openship.code-workspace +7 -0
- package/package.json +23 -9
- package/packages/adapters/docs/ARCHITECTURE.md +160 -0
- package/packages/adapters/docs/BUILD-PIPELINE.md +171 -0
- package/packages/adapters/docs/CLOUD.md +239 -0
- package/packages/adapters/docs/EXECUTOR.md +174 -0
- package/packages/adapters/docs/LOG-STREAMING.md +175 -0
- package/packages/adapters/docs/SYSTEM.md +191 -0
- package/packages/adapters/package.json +14 -9
- package/packages/adapters/src/index.ts +144 -4
- package/packages/adapters/src/infra/cloud.ts +42 -0
- package/packages/adapters/src/infra/index.ts +9 -0
- package/packages/adapters/src/infra/noop.ts +27 -0
- package/packages/adapters/src/infra/traefik.ts +297 -0
- package/packages/adapters/src/infra/types.ts +36 -0
- package/packages/adapters/src/platform.ts +255 -0
- package/packages/adapters/src/runtime/bare.ts +567 -0
- package/packages/adapters/src/runtime/build-pipeline.ts +237 -0
- package/packages/adapters/src/runtime/cloud.ts +791 -0
- package/packages/adapters/src/runtime/deploy-pipeline.ts +146 -0
- package/packages/adapters/src/runtime/docker.ts +443 -0
- package/packages/adapters/src/runtime/index.ts +69 -0
- package/packages/adapters/src/runtime/local-build.ts +112 -0
- package/packages/adapters/src/runtime/transfer.ts +82 -0
- package/packages/adapters/src/runtime/types.ts +138 -0
- package/packages/adapters/src/system/catalog.ts +188 -0
- package/packages/adapters/src/system/checks.ts +228 -0
- package/packages/adapters/src/system/components.ts +45 -0
- package/packages/adapters/src/system/debug.ts +18 -0
- package/packages/adapters/src/system/environment.ts +168 -0
- package/packages/adapters/src/system/errors.ts +34 -0
- package/packages/adapters/src/system/executor.ts +600 -0
- package/packages/adapters/src/system/index.ts +58 -0
- package/packages/adapters/src/system/installer.ts +310 -0
- package/packages/adapters/src/system/setup.ts +442 -0
- package/packages/adapters/src/system/state.ts +96 -0
- package/packages/adapters/src/system/types.ts +116 -0
- package/packages/adapters/src/toolchain/catalog.ts +439 -0
- package/packages/adapters/src/toolchain/checks.ts +181 -0
- package/packages/adapters/src/toolchain/index.ts +38 -0
- package/packages/adapters/src/toolchain/installer.ts +189 -0
- package/packages/adapters/src/toolchain/types.ts +84 -0
- package/packages/adapters/src/types.ts +277 -0
- package/packages/adapters/tsconfig.json +3 -1
- package/packages/core/package.json +9 -9
- package/packages/core/src/constants.ts +44 -1
- package/packages/core/src/index.ts +2 -0
- package/packages/core/src/stacks.ts +778 -0
- package/packages/core/src/system.ts +111 -0
- package/packages/core/src/types.ts +22 -1
- package/packages/core/src/utils.ts +8 -0
- package/packages/core/tsconfig.json +3 -1
- package/packages/db/drizzle/0000_init.sql +231 -0
- package/packages/db/drizzle/0001_add_server_name.sql +1 -0
- package/packages/db/drizzle/meta/_journal.json +20 -0
- package/packages/db/drizzle.config.ts +10 -0
- package/packages/db/package.json +26 -14
- package/packages/db/src/client.ts +119 -0
- package/packages/db/src/index.ts +57 -8
- package/packages/db/src/repos/account.repo.ts +92 -0
- package/packages/db/src/repos/deployment.repo.ts +168 -0
- package/packages/db/src/repos/domain.repo.ts +102 -0
- package/packages/db/src/repos/git-installation.repo.ts +95 -0
- package/packages/db/src/repos/index.ts +43 -0
- package/packages/db/src/repos/instance-settings.repo.ts +43 -0
- package/packages/db/src/repos/project.repo.ts +159 -0
- package/packages/db/src/repos/session.repo.ts +82 -0
- package/packages/db/src/repos/settings.repo.ts +51 -0
- package/packages/db/src/repos/user.repo.ts +94 -0
- package/packages/db/src/schema/auth.ts +56 -0
- package/packages/db/src/schema/deployment.ts +87 -0
- package/packages/db/src/schema/domain.ts +46 -0
- package/packages/db/src/schema/github.ts +24 -0
- package/packages/db/src/schema/index.ts +6 -0
- package/packages/db/src/schema/project.ts +117 -0
- package/packages/db/src/schema/settings.ts +103 -0
- package/packages/db/tsconfig.json +3 -1
- package/packages/ui/package.json +17 -17
- package/packages/ui/src/components/status-dot.tsx +1 -1
- package/packages/ui/tsconfig.json +7 -2
- package/pnpm-workspace.yaml +0 -1
- package/{tooling/tsconfig/base.json → tsconfig.base.json} +0 -1
- package/turbo.json +4 -0
- package/apps/api/src/modules/auth/auth.controller.ts +0 -26
- package/apps/api/src/modules/auth/auth.schema.ts +0 -12
- package/apps/api/src/modules/auth/auth.service.ts +0 -23
- package/apps/dashboard/src/app/page.tsx +0 -6
- package/apps/web/src/app/(marketing)/docs/page.tsx +0 -10
- package/packages/adapters/src/base-adapter.ts +0 -49
- package/packages/adapters/src/docker-adapter.ts +0 -43
- package/packages/adapters/src/oblien-adapter.ts +0 -46
- package/packages/adapters/src/registry.ts +0 -22
- package/packages/db/prisma/schema.prisma +0 -160
- package/tooling/tsconfig/nextjs.json +0 -10
- package/tooling/tsconfig/node.json +0 -10
- package/tooling/tsconfig/package.json +0 -7
|
@@ -1,10 +1,237 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
-
import
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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);
|