From 774825c1ccc17d1ef43a30683fb6d60b69741756 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 9 Sep 2025 09:04:27 +0200 Subject: [PATCH] chore: customizable dashboard auth (#38516) * chore: customizable dashboard auth * minor fixes --------- Co-authored-by: Joshen Lim --- .../interfaces/SignIn/SignInWithCustom.tsx | 48 +++++++++ .../studio/components/ui/UnknownInterface.tsx | 12 ++- .../custom-content/CustomContent.types.ts | 2 + .../hooks/custom-content/custom-content.json | 2 + .../custom-content/custom-content.sample.json | 2 + .../custom-content/custom-content.schema.json | 5 + apps/studio/pages/sign-in-sso.tsx | 8 ++ apps/studio/pages/sign-in.tsx | 97 +++++++++++++------ apps/studio/pages/sign-up.tsx | 33 +++++-- .../enabled-features/enabled-features.json | 5 + .../enabled-features.schema.json | 21 ++++ 11 files changed, 192 insertions(+), 43 deletions(-) create mode 100644 apps/studio/components/interfaces/SignIn/SignInWithCustom.tsx diff --git a/apps/studio/components/interfaces/SignIn/SignInWithCustom.tsx b/apps/studio/components/interfaces/SignIn/SignInWithCustom.tsx new file mode 100644 index 0000000000..a51b1a25a0 --- /dev/null +++ b/apps/studio/components/interfaces/SignIn/SignInWithCustom.tsx @@ -0,0 +1,48 @@ +import * as Sentry from '@sentry/nextjs' +import { useState } from 'react' +import { toast } from 'sonner' + +import { BASE_PATH } from 'lib/constants' +import { auth, buildPathWithParams } from 'lib/gotrue' +import { Button } from 'ui' + +interface SignInWithCustomProps { + providerName: string +} + +export const SignInWithCustom = ({ providerName }: SignInWithCustomProps) => { + const [loading, setLoading] = useState(false) + + async function handleCustomSignIn() { + setLoading(true) + + try { + // redirects to /sign-in to check if the user has MFA setup (handled in SignInLayout.tsx) + const redirectTo = buildPathWithParams( + `${ + process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview' + ? location.origin + : process.env.NEXT_PUBLIC_SITE_URL + }${BASE_PATH}/sign-in-mfa?method=${providerName.toLowerCase()}` + ) + + const { error } = await auth.signInWithOAuth({ + // @ts-expect-error - providerName is a string + provider: providerName.toLowerCase(), + options: { redirectTo }, + }) + + if (error) throw error + } catch (error: any) { + toast.error(`Failed to sign in via ${providerName}: ${error.message}`) + Sentry.captureMessage('[CRITICAL] Failed to sign in via GH: ' + error.message) + setLoading(false) + } + } + + return ( + + ) +} diff --git a/apps/studio/components/ui/UnknownInterface.tsx b/apps/studio/components/ui/UnknownInterface.tsx index d53ef6c7aa..0baf1c5a40 100644 --- a/apps/studio/components/ui/UnknownInterface.tsx +++ b/apps/studio/components/ui/UnknownInterface.tsx @@ -1,11 +1,17 @@ import Link from 'next/link' -import { Button } from 'ui' +import { Button, cn } from 'ui' import { Admonition } from 'ui-patterns' -export const UnknownInterface = ({ urlBack }: { urlBack: string }) => { +export const UnknownInterface = ({ + urlBack, + fullHeight = true, +}: { + urlBack: string + fullHeight?: boolean +}) => { return ( -
+
{ + const signInWithSSOEnabled = useIsFeatureEnabled('dashboard_auth:sign_in_with_sso') + + if (!signInWithSSOEnabled) { + return + } + return ( <>
diff --git a/apps/studio/pages/sign-in.tsx b/apps/studio/pages/sign-in.tsx index 62f383d277..db421158ce 100644 --- a/apps/studio/pages/sign-in.tsx +++ b/apps/studio/pages/sign-in.tsx @@ -5,9 +5,12 @@ import { useEffect } from 'react' import { LastSignInWrapper } from 'components/interfaces/SignIn/LastSignInWrapper' import { SignInForm } from 'components/interfaces/SignIn/SignInForm' +import { SignInWithCustom } from 'components/interfaces/SignIn/SignInWithCustom' import { SignInWithGitHub } from 'components/interfaces/SignIn/SignInWithGitHub' 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 type { NextPageWithLayout } from 'types' import { Button } from 'ui' @@ -15,6 +18,25 @@ import { Button } from 'ui' const SignInPage: NextPageWithLayout = () => { const router = useRouter() + const { + dashboardAuthSignInWithGithub: signInWithGithubEnabled, + dashboardAuthSignInWithSso: signInWithSsoEnabled, + dashboardAuthSignInWithEmail: signInWithEmailEnabled, + dashboardAuthSignUp: signUpEnabled, + } = useIsFeatureEnabled([ + 'dashboard_auth:sign_in_with_github', + 'dashboard_auth:sign_in_with_sso', + 'dashboard_auth:sign_in_with_email', + 'dashboard_auth:sign_up', + ]) + + const { dashboardAuthCustomProvider: customProvider } = useCustomContent([ + 'dashboard_auth:custom_provider', + ]) + + const showOrDivider = + (signInWithGithubEnabled || signInWithSsoEnabled || customProvider) && signInWithEmailEnabled + useEffect(() => { if (!IS_PLATFORM) { // on selfhosted instance just redirect to projects page @@ -25,45 +47,58 @@ const SignInPage: NextPageWithLayout = () => { return ( <>
- - - + + )} + + {showOrDivider && ( +
+
+
+
+
+ or +
+
+ )} + {signInWithEmailEnabled && } +
+ + {signUpEnabled && ( +
+
+ Don't have an account?{' '} - Continue with SSO + Sign Up Now - - - -
-
-
-
-
- or
- -
- -
-
- Don't have an account?{' '} - - Sign Up Now - -
-
+ )} ) } diff --git a/apps/studio/pages/sign-up.tsx b/apps/studio/pages/sign-up.tsx index c60e96d342..5fbaeb116e 100644 --- a/apps/studio/pages/sign-up.tsx +++ b/apps/studio/pages/sign-up.tsx @@ -3,22 +3,37 @@ import Link from 'next/link' import { SignInWithGitHub } from 'components/interfaces/SignIn/SignInWithGitHub' import { SignUpForm } from 'components/interfaces/SignIn/SignUpForm' import SignInLayout from 'components/layouts/SignInLayout/SignInLayout' +import { UnknownInterface } from 'components/ui/UnknownInterface' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from 'types' const SignUpPage: NextPageWithLayout = () => { + const { + dashboardAuthSignUp: signUpEnabled, + dashboardAuthSignInWithGithub: signInWithGithubEnabled, + } = useIsFeatureEnabled(['dashboard_auth:sign_up', 'dashboard_auth:sign_in_with_github']) + + if (!signUpEnabled) { + return + } + return ( <>
- + {signInWithGithubEnabled && ( + <> + -
-
-
-
-
- or -
-
+
+
+
+
+
+ or +
+
+ + )}
diff --git a/packages/common/enabled-features/enabled-features.json b/packages/common/enabled-features/enabled-features.json index 7c7ee99b44..0f2a3178fb 100644 --- a/packages/common/enabled-features/enabled-features.json +++ b/packages/common/enabled-features/enabled-features.json @@ -24,6 +24,11 @@ "billing:all": true, + "dashboard_auth:sign_up": true, + "dashboard_auth:sign_in_with_github": true, + "dashboard_auth:sign_in_with_sso": true, + "dashboard_auth:sign_in_with_email": true, + "database:replication": true, "database:roles": true, diff --git a/packages/common/enabled-features/enabled-features.schema.json b/packages/common/enabled-features/enabled-features.schema.json index ae6eeb10c6..13325bae07 100644 --- a/packages/common/enabled-features/enabled-features.schema.json +++ b/packages/common/enabled-features/enabled-features.schema.json @@ -86,6 +86,23 @@ "description": "Enable the billing settings page" }, + "dashboard_auth:sign_up": { + "type": "boolean", + "description": "Enable the sign up page in the dashboard" + }, + "dashboard_auth:sign_in_with_github": { + "type": "boolean", + "description": "Enable the sign in with github provider" + }, + "dashboard_auth:sign_in_with_sso": { + "type": "boolean", + "description": "Enable the sign in with sso provider" + }, + "dashboard_auth:sign_in_with_email": { + "type": "boolean", + "description": "Enable the sign in with email/password provider" + }, + "database:replication": { "type": "boolean", "description": "Enable the database replication page" @@ -235,6 +252,10 @@ "authentication:show_sort_by_phone", "authentication:show_user_type_filter", "billing:all", + "dashboard_auth:sign_up", + "dashboard_auth:sign_in_with_github", + "dashboard_auth:sign_in_with_sso", + "dashboard_auth:sign_in_with_email", "database:replication", "database:roles", "docs:self-hosting",