+
{children}
)
diff --git a/apps/studio/lib/constants/index.ts b/apps/studio/lib/constants/index.ts
index 3bf961dec49..647a6d65477 100644
--- a/apps/studio/lib/constants/index.ts
+++ b/apps/studio/lib/constants/index.ts
@@ -3,6 +3,7 @@
export * from './infrastructure'
export const IS_PLATFORM = process.env.NEXT_PUBLIC_IS_PLATFORM === 'true'
+export const STUDIO_AUTH_ENABLED = process.env.NEXT_PUBLIC_STUDIO_AUTH_MODE === 'supabase'
export const API_URL = (() => {
if (process.env.NODE_ENV === 'test') return 'http://localhost:3000/api'
diff --git a/apps/studio/lib/gotrue.ts b/apps/studio/lib/gotrue.ts
index a80ab433d6e..d8bd487c606 100644
--- a/apps/studio/lib/gotrue.ts
+++ b/apps/studio/lib/gotrue.ts
@@ -1,10 +1,12 @@
import type { JwtPayload } from '@supabase/supabase-js'
import { type User } from 'common/auth'
import { gotrueClient } from 'common/gotrue'
+import { IS_PLATFORM } from './constants'
export const auth = gotrueClient
-export const DEFAULT_FALLBACK_PATH = '/organizations'
+// Use /project/default for self-hosted, /organizations for platform
+export const DEFAULT_FALLBACK_PATH = IS_PLATFORM ? '/organizations' : '/project/default'
export const validateReturnTo = (
returnTo: string,
diff --git a/apps/studio/pages/forgot-password-mfa.tsx b/apps/studio/pages/forgot-password-mfa.tsx
index 801d9507323..e954dfd06c1 100644
--- a/apps/studio/pages/forgot-password-mfa.tsx
+++ b/apps/studio/pages/forgot-password-mfa.tsx
@@ -7,6 +7,7 @@ import { toast } from 'sonner'
import { getAccessToken } from 'common'
import { SignInMfaForm } from 'components/interfaces/SignIn/SignInMfaForm'
import ForgotPasswordLayout from 'components/layouts/SignInLayout/ForgotPasswordLayout'
+import { IS_PLATFORM } from 'lib/constants'
import { auth, buildPathWithParams, getReturnToPath } from 'lib/gotrue'
import type { NextPageWithLayout } from 'types'
import { LogoLoader } from 'ui'
@@ -17,6 +18,13 @@ const ForgotPasswordMfa: NextPageWithLayout = () => {
const [loading, setLoading] = useState(true)
+ useEffect(() => {
+ // Forgot password flow is disabled for self-hosted
+ if (!IS_PLATFORM) {
+ router.replace('/project/default')
+ }
+ }, [router])
+
// This useEffect redirects the user to MFA if they're already halfway signed in
useEffect(() => {
auth
diff --git a/apps/studio/pages/forgot-password.tsx b/apps/studio/pages/forgot-password.tsx
index 8952cd12cc6..a26e640874d 100644
--- a/apps/studio/pages/forgot-password.tsx
+++ b/apps/studio/pages/forgot-password.tsx
@@ -1,10 +1,22 @@
import Link from 'next/link'
+import { useRouter } from 'next/router'
+import { useEffect } from 'react'
import { ForgotPasswordWizard } from 'components/interfaces/SignIn/ForgotPasswordWizard'
import ForgotPasswordLayout from 'components/layouts/SignInLayout/ForgotPasswordLayout'
+import { IS_PLATFORM } from 'lib/constants'
import type { NextPageWithLayout } from 'types'
const ForgotPasswordPage: NextPageWithLayout = () => {
+ const router = useRouter()
+
+ useEffect(() => {
+ // No self-service password reset for self-hosted - users must be managed manually
+ if (!IS_PLATFORM) {
+ router.replace('/project/default')
+ }
+ }, [router])
+
return (
<>
diff --git a/apps/studio/pages/logout.tsx b/apps/studio/pages/logout.tsx
index 8be367a62dd..fb930557bf7 100644
--- a/apps/studio/pages/logout.tsx
+++ b/apps/studio/pages/logout.tsx
@@ -3,6 +3,7 @@ import { useEffect } from 'react'
import { toast } from 'sonner'
import { useSignOut } from 'lib/auth'
+import { IS_PLATFORM, STUDIO_AUTH_ENABLED } from 'lib/constants'
import type { NextPageWithLayout } from 'types'
import { LogoLoader } from 'ui'
@@ -14,7 +15,9 @@ const LogoutPage: NextPageWithLayout = () => {
const logout = async () => {
await signOut()
toast('Successfully logged out')
- await router.push('/sign-in')
+ // Redirect to sign-in if using real auth, otherwise to project page
+ const redirectPath = IS_PLATFORM || STUDIO_AUTH_ENABLED ? '/sign-in' : '/project/default'
+ await router.push(redirectPath)
}
logout()
}, [])
diff --git a/apps/studio/pages/sign-in-fly-tos.tsx b/apps/studio/pages/sign-in-fly-tos.tsx
index d700355fb78..6c8ef65cf8e 100644
--- a/apps/studio/pages/sign-in-fly-tos.tsx
+++ b/apps/studio/pages/sign-in-fly-tos.tsx
@@ -7,7 +7,7 @@ import { useEffect, useState } from 'react'
import { useIsLoggedIn } from 'common'
import { useOrganizationByFlyOrgIdMutation } from 'data/organizations/organization-by-fly-organization-id-mutation'
import { useProjectByFlyExtensionIdMutation } from 'data/projects/project-by-fly-extension-id-mutation'
-import { API_URL, BASE_PATH } from 'lib/constants'
+import { API_URL, BASE_PATH, IS_PLATFORM } from 'lib/constants'
import { Button } from 'ui'
const SignInFlyTos = () => {
@@ -18,6 +18,13 @@ const SignInFlyTos = () => {
isReady,
query: { fly_extension_id, fly_organization_id },
} = router
+
+ useEffect(() => {
+ // Fly.io integration is platform-only feature
+ if (!IS_PLATFORM) {
+ router.replace('/project/default')
+ }
+ }, [router])
const { resolvedTheme } = useTheme()
const { mutateAsync: getProjectByFlyExtensionId } = useProjectByFlyExtensionIdMutation({
onSuccess: (res) => {
diff --git a/apps/studio/pages/sign-in-mfa.tsx b/apps/studio/pages/sign-in-mfa.tsx
index 33a09078d4e..ef9e65dd259 100644
--- a/apps/studio/pages/sign-in-mfa.tsx
+++ b/apps/studio/pages/sign-in-mfa.tsx
@@ -10,6 +10,7 @@ import SignInLayout from 'components/layouts/SignInLayout/SignInLayout'
import { useAddLoginEvent } from 'data/misc/audit-login-mutation'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import useLatest from 'hooks/misc/useLatest'
+import { IS_PLATFORM } from 'lib/constants'
import { auth, buildPathWithParams, getReturnToPath } from 'lib/gotrue'
import type { NextPageWithLayout } from 'types'
import { LogoLoader } from 'ui'
@@ -54,14 +55,16 @@ const SignInMfaPage: NextPageWithLayout = () => {
}
if (data.currentLevel === data.nextLevel) {
- sendEvent({
- action: 'sign_in',
- properties: {
- category: 'account',
- method: signInMethodRef.current,
- },
- })
- addLoginEvent({})
+ if (IS_PLATFORM) {
+ sendEvent({
+ action: 'sign_in',
+ properties: {
+ category: 'account',
+ method: signInMethodRef.current,
+ },
+ })
+ addLoginEvent({})
+ }
await queryClient.resetQueries()
router.push(getReturnToPath())
diff --git a/apps/studio/pages/sign-in-partner.tsx b/apps/studio/pages/sign-in-partner.tsx
index b51320a2d39..8721d149231 100644
--- a/apps/studio/pages/sign-in-partner.tsx
+++ b/apps/studio/pages/sign-in-partner.tsx
@@ -1,9 +1,22 @@
+import { useRouter } from 'next/router'
+import { useEffect } from 'react'
+
import { SignInPartner } from 'components/interfaces/SignIn/SignInPartner'
import ForgotPasswordLayout from 'components/layouts/SignInLayout/ForgotPasswordLayout'
+import { IS_PLATFORM } from 'lib/constants'
import type { NextPageWithLayout } from 'types'
import { cn } from 'ui'
const SignInPartnerPage: NextPageWithLayout = () => {
+ const router = useRouter()
+
+ useEffect(() => {
+ // Partner sign-in is platform-only feature
+ if (!IS_PLATFORM) {
+ router.replace('/project/default')
+ }
+ }, [router])
+
return
}
diff --git a/apps/studio/pages/sign-in-sso.tsx b/apps/studio/pages/sign-in-sso.tsx
index 387fd6723c4..fb92939cb8b 100644
--- a/apps/studio/pages/sign-in-sso.tsx
+++ b/apps/studio/pages/sign-in-sso.tsx
@@ -1,12 +1,24 @@
+import { useRouter } from 'next/router'
+import { useEffect } from 'react'
+
import { SignInSSOForm } from 'components/interfaces/SignIn/SignInSSOForm'
import SignInLayout from 'components/layouts/SignInLayout/SignInLayout'
import { UnknownInterface } from 'components/ui/UnknownInterface'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
+import { IS_PLATFORM } from 'lib/constants'
import type { NextPageWithLayout } from 'types'
const SignInSSOPage: NextPageWithLayout = () => {
+ const router = useRouter()
const signInWithSSOEnabled = useIsFeatureEnabled('dashboard_auth:sign_in_with_sso')
+ useEffect(() => {
+ // SSO is platform-only feature
+ if (!IS_PLATFORM) {
+ router.replace('/project/default')
+ }
+ }, [router])
+
if (!signInWithSSOEnabled) {
return
}
diff --git a/apps/studio/pages/sign-in.tsx b/apps/studio/pages/sign-in.tsx
index f55db7b8011..c9e09ce2cbc 100644
--- a/apps/studio/pages/sign-in.tsx
+++ b/apps/studio/pages/sign-in.tsx
@@ -11,7 +11,7 @@ import { AuthenticationLayout } from 'components/layouts/AuthenticationLayout'
import SignInLayout from 'components/layouts/SignInLayout/SignInLayout'
import { useCustomContent } from 'hooks/custom-content/useCustomContent'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
-import { IS_PLATFORM } from 'lib/constants'
+import { IS_PLATFORM, STUDIO_AUTH_ENABLED } from 'lib/constants'
import type { NextPageWithLayout } from 'types'
import { Button } from 'ui'
@@ -38,8 +38,8 @@ const SignInPage: NextPageWithLayout = () => {
(signInWithGithubEnabled || signInWithSsoEnabled || customProvider) && signInWithEmailEnabled
useEffect(() => {
- if (!IS_PLATFORM) {
- // on selfhosted instance just redirect to projects page
+ // Redirect to projects page on self-hosted unless studio auth mode is enabled
+ if (!IS_PLATFORM && !STUDIO_AUTH_ENABLED) {
router.replace('/project/default')
}
}, [router])
@@ -83,10 +83,11 @@ const SignInPage: NextPageWithLayout = () => {
{signInWithEmailEnabled && }
- {signUpEnabled && (
+ {/* Hide signup link for self-hosted - users must be added manually */}
+ {IS_PLATFORM && signUpEnabled && (
-
Don’t have an account?{' '}
+
Don't have an account?{' '}
{
+ const router = useRouter()
+
const {
dashboardAuthSignUp: signUpEnabled,
dashboardAuthSignInWithGithub: signInWithGithubEnabled,
} = useIsFeatureEnabled(['dashboard_auth:sign_up', 'dashboard_auth:sign_in_with_github'])
+ useEffect(() => {
+ // No self-service signup for self-hosted - users must be added manually
+ if (!IS_PLATFORM) {
+ router.replace('/project/default')
+ }
+ }, [router])
+
if (!signUpEnabled) {
return
}
diff --git a/docker/.env.example b/docker/.env.example
index b66761b81ca..2f48cb18aac 100644
--- a/docker/.env.example
+++ b/docker/.env.example
@@ -104,6 +104,12 @@ IMGPROXY_ENABLE_WEBP_DETECTION=true
# Add your OpenAI API key to enable SQL Editor Assistant
OPENAI_API_KEY=
+# Studio Authentication Mode
+# Set to 'supabase' to use Supabase Auth (GoTrue) for Studio login instead of Kong basic-auth
+# When enabled, you must create users manually via CLI: docker compose exec auth ./auth admin createuser --confirm
+# Leave empty for default Kong basic-auth (DASHBOARD_USERNAME/DASHBOARD_PASSWORD)
+NEXT_PUBLIC_STUDIO_AUTH_MODE=
+
############
# Functions - Configuration for Functions
diff --git a/docker/volumes/api/kong.yml b/docker/volumes/api/kong.yml
index 168634f5a9a..3299ae81036 100644
--- a/docker/volumes/api/kong.yml
+++ b/docker/volumes/api/kong.yml
@@ -62,6 +62,33 @@ services:
- /auth/v1/authorize
plugins:
- name: cors
+ - name: auth-v1-open-token
+ url: http://auth:9999/token
+ routes:
+ - name: auth-v1-open-token
+ strip_path: true
+ paths:
+ - /auth/v1/token
+ plugins:
+ - name: cors
+ - name: auth-v1-open-user
+ url: http://auth:9999/user
+ routes:
+ - name: auth-v1-open-user
+ strip_path: true
+ paths:
+ - /auth/v1/user
+ plugins:
+ - name: cors
+ - name: auth-v1-open-logout
+ url: http://auth:9999/logout
+ routes:
+ - name: auth-v1-open-logout
+ strip_path: true
+ paths:
+ - /auth/v1/logout
+ plugins:
+ - name: cors
## Secure Auth routes
- name: auth-v1
@@ -278,6 +305,7 @@ services:
- /
plugins:
- name: cors
- - name: basic-auth
- config:
- hide_credentials: true
+ # Uncomment to use Kong basic-auth instead of Supabase Auth (GoTrue)
+ # - name: basic-auth
+ # config:
+ # hide_credentials: true