Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/components/GoogleIdentityButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,27 @@ const GoogleIdentityButton: React.FC = () => {
const clientId = import.meta.env.VITE_GOOGLE_CLIENT_ID || '';
const useSupabase = Boolean(import.meta.env.VITE_LOCAL_USE_SUPABASE || import.meta.env.VITE_SUPABASE_URL);

// Quick reachability check for the configured Supabase URL. Returns false
// when the domain doesn't resolve or the request times out.
const checkSupabaseReachable = async (): Promise<boolean> => {
try {
const rawUrl = (import.meta.env.VITE_SUPABASE_URL && String(import.meta.env.VITE_SUPABASE_URL).trim()) || '';
if (!rawUrl) return false;
const url = rawUrl.replace(/\/$/, '') + '/auth/v1';
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), 3000);
try {
await fetch(url, { method: 'GET', signal: controller.signal, cache: 'no-store' });
clearTimeout(id);
return true;
} finally {
clearTimeout(id);
}
} catch (e) {
return false;
}
};

// If Supabase is available prefer the Supabase OAuth redirect flow. This
// ensures sessions are created/managed by Supabase rather than doing local
// ID token verification.
Expand All @@ -41,6 +62,16 @@ const GoogleIdentityButton: React.FC = () => {
className="btn btn-google"
onClick={async () => {
try {
const reachable = await checkSupabaseReachable();
if (!reachable) {
window.showFitBuddyNotification?.({
title: 'Sign-in Unavailable',
message:
'Google sign-in is temporarily unavailable because the authentication service cannot be reached. Please try again later or contact the site administrator.',
variant: 'warning'
});
return;
}
await signInWithGoogle();
} catch (e) {
console.warn('[GoogleIdentityButton] signInWithGoogle failed', e);
Expand Down
33 changes: 30 additions & 3 deletions src/services/authService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,30 @@ export async function fetchUserById(id: string): Promise<User | null> {
return null;
}
}

// Quick reachability test for the configured Supabase URL. We attempt a
// simple fetch to the Supabase auth root so we can detect DNS failures
// or network issues early and fall back to server-side auth endpoints.
async function isSupabaseReachable(): Promise<boolean> {
try {
const rawUrl = (import.meta.env.VITE_SUPABASE_URL || (process && process.env && process.env.SUPABASE_URL) || '');
if (!rawUrl) return false;
// Trim trailing slash and hit the auth endpoint; DNS failures surface as fetch errors.
const url = rawUrl.replace(/\/$/, '') + '/auth/v1';
// Use a short timeout to avoid blocking the UI for long
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), 3000);
try {
await fetch(url, { method: 'GET', signal: controller.signal, cache: 'no-store' });
clearTimeout(id);
return true;
} finally {
clearTimeout(id);
}
} catch (e) {
return false;
}
}
// src/services/authService.ts
export interface User {
id: string;
Expand All @@ -91,7 +115,8 @@ export interface User {
export async function signIn(email: string, password: string): Promise<User> {
const normalizedEmail = String(email).trim().toLowerCase();
const useSupabase = Boolean(import.meta.env.VITE_LOCAL_USE_SUPABASE || import.meta.env.VITE_SUPABASE_URL);
if (useSupabase) {
const reachable = useSupabase ? await isSupabaseReachable() : false;
if (useSupabase && reachable) {
const result = await supabase.auth.signInWithPassword({ email: normalizedEmail, password });
// Supabase may return error with status 400 and message indicating "User is not confirmed" or similar.
if (result.error || !result.data?.session) {
Expand Down Expand Up @@ -166,7 +191,8 @@ export async function signIn(email: string, password: string): Promise<User> {
export async function signUp(email: string, username: string, password: string): Promise<User> {
const normalizedEmail = String(email).trim().toLowerCase();
const useSupabase = Boolean(import.meta.env.VITE_LOCAL_USE_SUPABASE || import.meta.env.VITE_SUPABASE_URL);
if (useSupabase) {
const reachable = useSupabase ? await isSupabaseReachable() : false;
if (useSupabase && reachable) {
const result = await supabase.auth.signUp({ email: normalizedEmail, password, options: { data: { username, energy: DEFAULT_ENERGY } } });
if (result.error) throw new Error(result.error.message || 'Sign up failed');
// Supabase may not return a session depending on config; if a session exists save token
Expand Down Expand Up @@ -227,7 +253,8 @@ export async function signUp(email: string, username: string, password: string):
// unsupported.
export async function signInWithGoogle(): Promise<void> {
const useSupabase = Boolean(import.meta.env.VITE_LOCAL_USE_SUPABASE || import.meta.env.VITE_SUPABASE_URL);
if (useSupabase) {
const reachable = useSupabase ? await isSupabaseReachable() : false;
if (useSupabase && reachable) {
try {
// Prefer an explicit public app URL (so Supabase redirects back to your
// branded domain after it finishes the provider exchange). If you set
Expand Down