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');