feat(auth): base available login methods off GoTrue providers (#1869)
* feat(auth): base login fields on GoTrue providers Signed-off-by: Goostaf <gasplund2@gmail.com> # Conflicts: # app/(auth)/login/login-client.tsx # app/(auth)/register/page.tsx * fix: address feedback Signed-off-by: Goostaf <gasplund2@gmail.com> * chore: remove hardcoded Google enabled checks Signed-off-by: Goostaf <gasplund2@gmail.com> # Conflicts: # .env.example * feat: show label when password login is disabled Signed-off-by: Goostaf <gasplund2@gmail.com> * feat: use MicrosoftMark, correct comment Signed-off-by: Goostaf <gasplund2@gmail.com> * feat: display custom providers Signed-off-by: Goostaf <gasplund2@gmail.com> * feat: show when no methods are available Signed-off-by: Goostaf <gasplund2@gmail.com> * feat: display custom provider labels Signed-off-by: Goostaf <gasplund2@gmail.com> * feat: add SAML login path Signed-off-by: Goostaf <gasplund2@gmail.com> * feat: show when no methods are available Signed-off-by: Goostaf <gasplund2@gmail.com> * fix: display SAML button when enabled Signed-off-by: Goostaf <gasplund2@gmail.com> * fix: preserve nextPath and broken key Signed-off-by: Goostaf <gasplund2@gmail.com> * fix: redirect test to client Signed-off-by: Goostaf <gasplund2@gmail.com> * fix: expose registerEnabled Signed-off-by: Goostaf <gasplund2@gmail.com> * feat: provider allowlist and request timeout Signed-off-by: Goostaf <gasplund2@gmail.com> * fix: restore compact labels Signed-off-by: Goostaf <gasplund2@gmail.com> * refactor: move withTimeout implementation to utils Signed-off-by: Goostaf <gasplund2@gmail.com> * fix: include nextPath Signed-off-by: Goostaf <gasplund2@gmail.com> * fix: export function and test case Signed-off-by: Goostaf <gasplund2@gmail.com> * feat: only show SAML button if vars configured Signed-off-by: Goostaf <gasplund2@gmail.com> * fix(auth): map SAML sign-in error through getErrorMessage The antipattern ratchet (check:guards, raw-user-error) rejects a raw error.message reaching a user-visible sink. Route the signInWithSSO error through getErrorMessage like the other auth error paths. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BAzJjXQBa9F5L1U42wUMj Signed-off-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> * feat(auth): GitHub brand mark on the provider button; decision log GitHub allows its invertocat in solid black/white, so currentColor is correct; custom OIDC providers keep the generic key icon. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BAzJjXQBa9F5L1U42wUMj Signed-off-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> --------- Signed-off-by: Goostaf <gasplund2@gmail.com> Signed-off-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
Jakob Wennberg
parent
dc07ca8872
commit
6ac9679fb5
@@ -4,31 +4,39 @@ import { useState } from 'react'
|
||||
import { useLocale, useTranslations } from 'next-intl'
|
||||
import { createClient } from '@/lib/supabase/client'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Loader2 } from 'lucide-react'
|
||||
import { Loader2, KeyRound } from 'lucide-react'
|
||||
import { getErrorMessage, type ErrorLocale } from '@/lib/errors/get-error-message'
|
||||
import { GoogleMark } from '@/components/ui/provider-marks'
|
||||
|
||||
import { GitHubMark, GoogleMark, MicrosoftMark } from '@/components/ui/provider-marks'
|
||||
import type { ResolvedProvider } from '@/lib/auth/gotrue-providers'
|
||||
|
||||
/**
|
||||
* "Continue with Google" for the login and register pages.
|
||||
*
|
||||
* Kicks off the Supabase OAuth redirect; the round-trip lands in
|
||||
* /auth/callback (PKCE code exchange), which owns MFA routing, invite
|
||||
* acceptance and silent-team creation for OAuth sign-ins and sign-ups alike.
|
||||
* The flow=oauth marker lets the callback tag failures so the login page
|
||||
* shows Google-specific copy instead of the email-confirmation framing.
|
||||
* Render the brand mark for a known provider, or a generic key icon for
|
||||
* custom OIDC providers.
|
||||
*/
|
||||
export function GoogleAuthButton({
|
||||
function ProviderMark({ provider }: { provider: ResolvedProvider }) {
|
||||
if (provider.id === 'google') return <GoogleMark />
|
||||
else if (provider.id === 'azure') return <MicrosoftMark />
|
||||
else if (provider.id === 'github') return <GitHubMark />
|
||||
else return <KeyRound className="h-4 w-4 text-muted-foreground" />
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic OAuth login button. Works with any Supabase GoTrue provider.
|
||||
*
|
||||
* For known providers (Google, GitHub, etc.) the button shows the brand
|
||||
* name; for custom OIDC providers it shows "Sign in with SSO" style text.
|
||||
*
|
||||
* Kicks off the Supabase OAuth redirect, with flow=oauth so
|
||||
* /auth/callback can tag failures.
|
||||
*/
|
||||
export function OAuthButton({
|
||||
provider,
|
||||
onError,
|
||||
compact = false,
|
||||
next,
|
||||
}: {
|
||||
provider: ResolvedProvider
|
||||
onError: (message: string) => void
|
||||
/**
|
||||
* Half-width alternative-method chip on the login panel: shows just the
|
||||
* mark and "Google" (a brand name, never translated), with the full label
|
||||
* kept as the accessible name.
|
||||
*/
|
||||
compact?: boolean
|
||||
/**
|
||||
* Post-auth destination, already passed through safeReturnTo by the caller.
|
||||
@@ -51,7 +59,7 @@ export function GoogleAuthButton({
|
||||
callback.searchParams.set('flow', 'oauth')
|
||||
if (next && next !== '/') callback.searchParams.set('next', next)
|
||||
const { error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'google',
|
||||
provider: provider.id as Parameters<typeof supabase.auth.signInWithOAuth>[0]['provider'],
|
||||
options: {
|
||||
redirectTo: callback.toString(),
|
||||
},
|
||||
@@ -60,13 +68,14 @@ export function GoogleAuthButton({
|
||||
onError(getErrorMessage(error, { context: 'auth', locale: errorLocale }))
|
||||
setIsRedirecting(false)
|
||||
}
|
||||
// On success the browser navigates away; keep the spinner until then.
|
||||
} catch (error) {
|
||||
onError(getErrorMessage(error, { context: 'auth', locale: errorLocale }))
|
||||
setIsRedirecting(false)
|
||||
}
|
||||
}
|
||||
|
||||
const label = tAuth('continue_with_provider', { provider: provider.label })
|
||||
|
||||
return (
|
||||
<Button
|
||||
type="button"
|
||||
@@ -74,16 +83,16 @@ export function GoogleAuthButton({
|
||||
className={compact ? 'h-10 w-full gap-2' : 'w-full h-11'}
|
||||
onClick={handleClick}
|
||||
disabled={isRedirecting}
|
||||
aria-label={tAuth('continue_with_google')}
|
||||
aria-label={label}
|
||||
>
|
||||
{isRedirecting ? (
|
||||
<Loader2 className={compact ? 'h-4 w-4 animate-spin' : 'mr-2 h-4 w-4 animate-spin'} />
|
||||
) : (
|
||||
<span className={compact ? 'flex items-center' : 'mr-2 flex items-center'}>
|
||||
<GoogleMark />
|
||||
<ProviderMark provider={provider} />
|
||||
</span>
|
||||
)}
|
||||
{compact ? 'Google' : tAuth('continue_with_google')}
|
||||
{compact ? provider.label : label}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user