chore: customizable dashboard auth (#38516)
* chore: customizable dashboard auth * minor fixes --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
This commit is contained in:
@@ -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 (
|
||||
<Button block onClick={handleCustomSignIn} size="large" type="default" loading={loading}>
|
||||
Continue with {providerName}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -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 (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<div className={cn('w-full flex items-center justify-center', fullHeight && 'h-full')}>
|
||||
<Admonition
|
||||
type="note"
|
||||
className="max-w-xl"
|
||||
|
||||
@@ -2,6 +2,8 @@ import { CONNECTION_TYPES } from 'components/interfaces/Connect/Connect.constant
|
||||
import type { CloudProvider } from 'shared-data'
|
||||
|
||||
export type CustomContentTypes = {
|
||||
dashboardAuthCustomProvider: string
|
||||
|
||||
organizationLegalDocuments: {
|
||||
id: string
|
||||
name: string
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"$schema": "./custom-content.schema.json",
|
||||
|
||||
"dashboard_auth:custom_provider": null,
|
||||
|
||||
"organization:legal_documents": null,
|
||||
|
||||
"project_homepage:example_projects": null,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"$schema": "./custom-content.schema.json",
|
||||
|
||||
"dashboard_auth:custom_provider": "Nimbus",
|
||||
|
||||
"organization:legal_documents": [
|
||||
{
|
||||
"id": "doc1",
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
"dashboard_auth:custom_provider": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Show a custom provider on the sign in page (Continue with X)"
|
||||
},
|
||||
|
||||
"organization:legal_documents": {
|
||||
"type": ["array", "null"],
|
||||
"description": "Renders a provided set of documents under the organization legal documents page",
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
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 type { NextPageWithLayout } from 'types'
|
||||
|
||||
const SignInSSOPage: NextPageWithLayout = () => {
|
||||
const signInWithSSOEnabled = useIsFeatureEnabled('dashboard_auth:sign_in_with_sso')
|
||||
|
||||
if (!signInWithSSOEnabled) {
|
||||
return <UnknownInterface fullHeight={false} urlBack="/sign-in" />
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-5">
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<div className="flex flex-col gap-5">
|
||||
<SignInWithGitHub />
|
||||
<LastSignInWrapper type="sso">
|
||||
<Button asChild block size="large" type="outline" icon={<Lock width={18} height={18} />}>
|
||||
{customProvider && <SignInWithCustom providerName={customProvider} />}
|
||||
{signInWithGithubEnabled && <SignInWithGitHub />}
|
||||
{signInWithSsoEnabled && (
|
||||
<LastSignInWrapper type="sso">
|
||||
<Button
|
||||
asChild
|
||||
block
|
||||
size="large"
|
||||
type="outline"
|
||||
icon={<Lock width={18} height={18} />}
|
||||
>
|
||||
<Link
|
||||
href={{
|
||||
pathname: '/sign-in-sso',
|
||||
query: router.query,
|
||||
}}
|
||||
>
|
||||
Continue with SSO
|
||||
</Link>
|
||||
</Button>
|
||||
</LastSignInWrapper>
|
||||
)}
|
||||
|
||||
{showOrDivider && (
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-strong" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 text-sm bg-studio text-foreground">or</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{signInWithEmailEnabled && <SignInForm />}
|
||||
</div>
|
||||
|
||||
{signUpEnabled && (
|
||||
<div className="self-center my-8 text-sm">
|
||||
<div>
|
||||
<span className="text-foreground-light">Don't have an account?</span>{' '}
|
||||
<Link
|
||||
href={{
|
||||
pathname: '/sign-in-sso',
|
||||
pathname: '/sign-up',
|
||||
query: router.query,
|
||||
}}
|
||||
className="underline transition text-foreground hover:text-foreground-light"
|
||||
>
|
||||
Continue with SSO
|
||||
Sign Up Now
|
||||
</Link>
|
||||
</Button>
|
||||
</LastSignInWrapper>
|
||||
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-strong" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 text-sm bg-studio text-foreground">or</span>
|
||||
</div>
|
||||
</div>
|
||||
<SignInForm />
|
||||
</div>
|
||||
|
||||
<div className="self-center my-8 text-sm">
|
||||
<div>
|
||||
<span className="text-foreground-light">Don't have an account?</span>{' '}
|
||||
<Link
|
||||
href={{
|
||||
pathname: '/sign-up',
|
||||
query: router.query,
|
||||
}}
|
||||
className="underline transition text-foreground hover:text-foreground-light"
|
||||
>
|
||||
Sign Up Now
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 <UnknownInterface fullHeight={false} urlBack="/sign-in" />
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-5">
|
||||
<SignInWithGitHub />
|
||||
{signInWithGithubEnabled && (
|
||||
<>
|
||||
<SignInWithGitHub />
|
||||
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-strong" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="bg-studio px-2 text-sm text-foreground">or</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-strong" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="bg-studio px-2 text-sm text-foreground">or</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<SignUpForm />
|
||||
</div>
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user