diff --git a/apps/readest-app/src/app/auth/callback/page.tsx b/apps/readest-app/src/app/auth/callback/page.tsx index 390a0e5d..f980ee52 100644 --- a/apps/readest-app/src/app/auth/callback/page.tsx +++ b/apps/readest-app/src/app/auth/callback/page.tsx @@ -16,9 +16,27 @@ export default function AuthCallback() { const accessToken = params.get('access_token'); const refreshToken = params.get('refresh_token'); + const type = params.get('type'); const next = params.get('next') ?? '/'; + const error = params.get('error'); + const errorDescription = params.get('error_description'); + const errorCode = params.get('error_code'); - handleAuthCallback({ accessToken, refreshToken, next, login, navigate: router.push }); + handleAuthCallback({ + accessToken, + refreshToken, + type, + next, + error, + errorCode, + errorDescription, + login, + navigate: router.push, + }); - return null; + return ( +
+ +
+ ); } diff --git a/apps/readest-app/src/app/auth/page.tsx b/apps/readest-app/src/app/auth/page.tsx index efc2fe74..db617a29 100644 --- a/apps/readest-app/src/app/auth/page.tsx +++ b/apps/readest-app/src/app/auth/page.tsx @@ -24,6 +24,7 @@ import { openUrl } from '@tauri-apps/plugin-opener'; import { handleAuthCallback } from '@/helpers/auth'; import { getAppleIdAuth, Scope } from './utils/appleIdAuth'; import { authWithSafari } from './utils/safariAuth'; +import { READEST_WEB_BASE_URL } from '@/services/constants'; import WindowButtons from '@/components/WindowButtons'; type OAuthProvider = 'google' | 'apple' | 'azure' | 'github'; @@ -40,7 +41,7 @@ interface ProviderLoginProp { label: string; } -const WEB_AUTH_CALLBACK = 'https://web.readest.com/auth/callback'; +const WEB_AUTH_CALLBACK = `${READEST_WEB_BASE_URL}/auth/callback`; const DEEPLINK_CALLBACK = 'readest://auth/callback'; const ProviderLogin: React.FC = ({ provider, handleSignIn, Icon, label }) => { @@ -84,6 +85,12 @@ export default function AuthPage() { return `http://localhost:${port}`; // only for development env on Desktop }; + const getWebRedirectTo = () => { + return process.env.NODE_ENV === 'production' + ? WEB_AUTH_CALLBACK + : `${window.location.origin}/auth/callback`; + }; + const tauriSignInApple = async () => { if (!supabase) { throw new Error('No backend connected'); @@ -141,9 +148,10 @@ export default function AuthPage() { const params = new URLSearchParams(hash); const accessToken = params.get('access_token'); const refreshToken = params.get('refresh_token'); + const type = params.get('type'); const next = params.get('next') ?? '/'; if (accessToken) { - handleAuthCallback({ accessToken, refreshToken, next, login, navigate: router.push }); + handleAuthCallback({ accessToken, refreshToken, type, next, login, navigate: router.push }); } } }; @@ -240,13 +248,6 @@ export default function AuthPage() { link_text: _('Forgot your password?'), confirmation_text: _('Check your email for the password reset link'), }, - update_password: { - password_label: _('New Password'), - password_input_placeholder: _('Your new password'), - button_label: _('Update password'), - loading_button_label: _('Updating password ...'), - confirmation_text: _('Your password has been updated'), - }, verify_otp: { email_input_label: _('Email address'), email_input_placeholder: _('Your email address'), @@ -374,7 +375,7 @@ export default function AuthPage() { theme={isDarkMode ? 'dark' : 'light'} magicLink={true} providers={['google', 'apple', 'github']} - redirectTo='/auth/callback' + redirectTo={getWebRedirectTo()} localization={getAuthLocalization()} /> diff --git a/apps/readest-app/src/app/auth/recovery/page.tsx b/apps/readest-app/src/app/auth/recovery/page.tsx new file mode 100644 index 00000000..60ddf695 --- /dev/null +++ b/apps/readest-app/src/app/auth/recovery/page.tsx @@ -0,0 +1,62 @@ +'use client'; + +import { useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { useAuth } from '@/context/AuthContext'; +import { useThemeStore } from '@/store/themeStore'; +import { useTranslation } from '@/hooks/useTranslation'; +import { ThemeSupa } from '@supabase/auth-ui-shared'; +import { Auth } from '@supabase/auth-ui-react'; +import { supabase } from '@/utils/supabase'; + +export default function ResetPasswordPage() { + const _ = useTranslation(); + const router = useRouter(); + const { login } = useAuth(); + const { isDarkMode } = useThemeStore(); + + const getAuthLocalization = () => { + return { + variables: { + update_password: { + password_label: _('New Password'), + password_input_placeholder: _('Your new password'), + button_label: _('Update password'), + loading_button_label: _('Updating password ...'), + confirmation_text: _('Your password has been updated'), + }, + }, + }; + }; + + useEffect(() => { + const { data: subscription } = supabase.auth.onAuthStateChange((event, session) => { + if (session?.access_token && session.user && event === 'USER_UPDATED') { + login(session.access_token, session.user); + const redirectTo = new URLSearchParams(window.location.search).get('redirect'); + router.push(redirectTo ?? '/library'); + } + }); + + return () => { + subscription?.subscription.unsubscribe(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [router]); + + return ( +
+
+ +
+
+ ); +} diff --git a/apps/readest-app/src/helpers/auth.ts b/apps/readest-app/src/helpers/auth.ts index fa9f7d79..322831eb 100644 --- a/apps/readest-app/src/helpers/auth.ts +++ b/apps/readest-app/src/helpers/auth.ts @@ -6,7 +6,11 @@ interface UseAuthCallbackOptions { refreshToken?: string | null; login: (accessToken: string, user: User) => void; navigate: (path: string) => void; + type?: string | null; next?: string; + error?: string | null; + errorCode?: string | null; + errorDescription?: string | null; } export function handleAuthCallback({ @@ -14,22 +18,28 @@ export function handleAuthCallback({ refreshToken, login, navigate, + type, next = '/', + error, }: UseAuthCallbackOptions) { async function finalizeSession() { - if (!accessToken || !refreshToken) { - console.error('No access token or refresh token'); + if (error) { navigate('/auth/error'); return; } - const { error } = await supabase.auth.setSession({ + if (!accessToken || !refreshToken) { + navigate('/library'); + return; + } + + const { error: err } = await supabase.auth.setSession({ access_token: accessToken, refresh_token: refreshToken, }); - if (error) { - console.error('Error setting session:', error); + if (err) { + console.error('Error setting session:', err); navigate('/auth/error'); return; } @@ -39,6 +49,10 @@ export function handleAuthCallback({ } = await supabase.auth.getUser(); if (user) { login(accessToken, user); + if (type === 'recovery') { + navigate('/auth/recovery'); + return; + } navigate(next); } else { console.error('Error fetching user data');