diff --git a/.env.example b/.env.example index 63817705..100d2712 100644 --- a/.env.example +++ b/.env.example @@ -83,6 +83,10 @@ PLAID_COUNTRY_CODES= PLAID_INTERVAL_IN_DAYS= +# Automatically redirect sign-in when exactly one OAuth provider is configured and no other +# authentication providers are enabled +OAUTH_AUTO_REDIRECT=false + # Google Provider : https://next-auth.js.org/providers/google GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 5fc723d6..258021b3 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -67,6 +67,10 @@ Used for magic-link login and invites. ### OAuth providers +#### Shared OAuth settings + +- `OAUTH_AUTO_REDIRECT`: Optional flag. When set to `true`, the sign-in page automatically redirects when exactly one OAuth provider is configured and no other authentication providers are enabled. This applies to Google, Authentik, Keycloak, and generic OIDC. + #### Google - `GOOGLE_CLIENT_ID` diff --git a/src/env.ts b/src/env.ts index 913ed31c..8d91edf5 100644 --- a/src/env.ts +++ b/src/env.ts @@ -71,6 +71,7 @@ export const env = createEnv({ OIDC_CLIENT_SECRET: z.string().optional(), OIDC_WELL_KNOWN_URL: z.string().optional(), OIDC_ALLOW_DANGEROUS_EMAIL_LINKING: z.boolean().default(false), + OAUTH_AUTO_REDIRECT: z.boolean().default(false), UPLOAD_MAX_FILE_SIZE_MB: z.coerce.number().int().positive().default(10), }, @@ -142,6 +143,7 @@ export const env = createEnv({ OIDC_ALLOW_DANGEROUS_EMAIL_LINKING: Boolean( JSON.parse(process.env.OIDC_ALLOW_DANGEROUS_EMAIL_LINKING || 'false'), ), + OAUTH_AUTO_REDIRECT: Boolean(JSON.parse(process.env.OAUTH_AUTO_REDIRECT || 'false')), UPLOAD_MAX_FILE_SIZE_MB: process.env.UPLOAD_MAX_FILE_SIZE_MB ? Number(process.env.UPLOAD_MAX_FILE_SIZE_MB) : 10, diff --git a/src/pages/auth/signin.tsx b/src/pages/auth/signin.tsx index 35490494..2e2cfe3d 100644 --- a/src/pages/auth/signin.tsx +++ b/src/pages/auth/signin.tsx @@ -59,7 +59,8 @@ const Home: NextPage<{ feedbackEmail: string; providers: ClientSafeProvider[]; callbackUrl?: string; -}> = ({ error, providers: serverProviders, feedbackEmail, callbackUrl }) => { + oauthAutoRedirect: boolean; +}> = ({ error, providers: serverProviders, feedbackEmail, callbackUrl, oauthAutoRedirect }) => { const { t } = useTranslation(); const [emailStatus, setEmailStatus] = useState<'idle' | 'sending' | 'success'>('idle'); const [showVerificationStep, setShowVerificationStep] = useState(false); @@ -110,6 +111,29 @@ const Home: NextPage<{ } }, [error, t]); + useEffect(() => { + const oauthProviders = providers.filter((provider) => 'oauth' === provider.type); + // SessionRequired indicates an unauthenticated route redirect, not a failed sign-in. + const hasSignInError = Boolean(error && 'SessionRequired' !== error); + const shouldAutoRedirect = + oauthAutoRedirect && !hasSignInError && 1 === oauthProviders.length && 1 === providers.length; + + const oauthProvider = oauthProviders[0]; + if (shouldAutoRedirect && oauthProvider && !showVerificationStep && !isLoadingProviders) { + void signIn(oauthProvider.id, { callbackUrl }).catch(() => { + toast.error(t('errors.signin_error')); + }); + } + }, [ + oauthAutoRedirect, + error, + showVerificationStep, + providers, + isLoadingProviders, + callbackUrl, + t, + ]); + const onEmailSubmit = useCallback(async () => { setEmailStatus('sending'); const email = emailForm.getValues().email.toLowerCase(); @@ -293,6 +317,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => { feedbackEmail: env.FEEDBACK_EMAIL ?? '', providers: Object.values(providers ?? {}), callbackUrl: callbackUrl && !Array.isArray(callbackUrl) ? callbackUrl : '', + oauthAutoRedirect: env.OAUTH_AUTO_REDIRECT, }, }; }; diff --git a/src/server/auth.ts b/src/server/auth.ts index bbc73e30..f3951680 100644 --- a/src/server/auth.ts +++ b/src/server/auth.ts @@ -283,6 +283,14 @@ export function validateAuthEnv() { console.log('Validating auth env'); if (!process.env.SKIP_ENV_VALIDATION) { const providers = getProviders(); + const oauthProviders = providers.filter((provider) => 'oauth' === provider.type); + + if (env.OAUTH_AUTO_REDIRECT && (1 !== oauthProviders.length || 1 !== providers.length)) { + console.warn( + 'OAUTH_AUTO_REDIRECT is enabled, but automatic redirection will not happen until exactly one OAuth provider and no other authentication providers are configured.', + ); + } + if (0 === providers.length) { throw new Error( 'No authentication providers are configured, at least one is required. Learn more here: https://github.com/oss-apps/split-pro?tab=readme-ov-file#setting-up-the-environment',