From c5b2f069194be29cf21126fb0828c54b6e0ba8c1 Mon Sep 17 00:00:00 2001 From: seb Date: Sun, 19 Jul 2026 23:42:17 +0200 Subject: [PATCH] login for RN app --- src/api/index.js | 68 +++++++++++++++++++++++++- src/auth/AuthProvider.jsx | 15 ++++-- src/auth/googleSignIn.js | 61 ++++++++++++++++++++++- src/components/LoginPage.jsx | 24 ++++++++- src/pages/artikel/index.jsx | 22 --------- src/pages/artikel/withSearchParams.jsx | 16 ++++++ vite.config.js | 22 +++++++++ 7 files changed, 199 insertions(+), 29 deletions(-) diff --git a/src/api/index.js b/src/api/index.js index 7957ef8..1c75d6e 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -1,7 +1,38 @@ // Single source of truth for all backend communication. +/** WebView consoles stringify objects as [object Object]; log JSON text. */ +function logApi(tag, details) { + let text; + try { + text = JSON.stringify(details, null, 2); + } catch { + text = String(details); + } + console.log(`[api] ${tag} ${text}`); +} + +function readableHeaders(headers) { + const out = {}; + headers.forEach((value, key) => { + out[key] = value; + }); + return out; +} + async function http(path, { method = 'GET', body } = {}) { const headers = { 'Content-Type': 'application/json' }; + const url = new URL(path, window.location.origin).href; + + logApi('request', { + method, + path, + url, + origin: window.location.origin, + credentials: 'include', + // HttpOnly cookies are invisible here; this only shows JS-readable cookies. + documentCookie: document.cookie || '(empty)', + hasBody: Boolean(body), + }); const res = await fetch(path, { method, @@ -10,6 +41,18 @@ async function http(path, { method = 'GET', body } = {}) { body: body ? JSON.stringify(body) : undefined, }); + const responseHeaders = readableHeaders(res.headers); + // Browsers hide Set-Cookie from JS; if it's missing here that proves nothing. + logApi('response', { + method, + path, + status: res.status, + statusText: res.statusText, + ok: res.ok, + responseHeaders, + documentCookieAfter: document.cookie || '(empty)', + }); + if (!res.ok) { let message = res.statusText; let data; @@ -19,13 +62,21 @@ async function http(path, { method = 'GET', body } = {}) { } catch { message = (await res.text().catch(() => message)) || message; } + logApi('error-body', { method, path, status: res.status, message, data }); const err = new Error(message || `Request failed (${res.status})`); err.status = res.status; err.data = data; throw err; } if (res.status === 204) return null; - return res.json(); + const json = await res.json(); + logApi('ok-body', { + method, + path, + keys: json && typeof json === 'object' ? Object.keys(json) : typeof json, + hasUser: Boolean(json?.user), + }); + return json; } export const article = { @@ -65,11 +116,24 @@ export const auth = { }, async loginWithGoogle(idToken) { + logApi('loginWithGoogle', { + idTokenLength: idToken?.length ?? 0, + idTokenPrefix: idToken ? `${idToken.slice(0, 12)}…` : null, + }); const data = await http('/api/auth/login', { method: 'POST', body: { idToken }, }); - return { user: data.user }; + // Confirm whether the session cookie actually works (not just login JSON). + const session = await auth.me(); + logApi('session-after-login', { + loginUser: data.user?.email ?? data.user?.sub ?? data.user ?? null, + meUser: session.user?.email ?? session.user?.sub ?? session.user ?? null, + meForbidden: session.forbidden ?? false, + meMessage: session.message ?? null, + sessionOk: Boolean(session.user), + }); + return { user: data.user, session }; }, async logout() { diff --git a/src/auth/AuthProvider.jsx b/src/auth/AuthProvider.jsx index f29b663..d64414d 100644 --- a/src/auth/AuthProvider.jsx +++ b/src/auth/AuthProvider.jsx @@ -35,10 +35,19 @@ export default function AuthProvider({ children }) { }, [enqueueSnackbar]); const loginWithGoogle = useCallback(async (credential) => { - const { user: me } = await auth.loginWithGoogle(credential); - setUser(me); + const { session } = await auth.loginWithGoogle(credential); + if (!session?.user) { + setUser(null); + setStatus('anon'); + enqueueSnackbar( + `Login ok but session cookie not accepted (/api/auth/me still unauthorized)`, + { variant: 'error', autoHideDuration: 12000 }, + ); + return; + } + setUser(session.user); setStatus('authed'); - }, []); + }, [enqueueSnackbar]); const logout = useCallback(async () => { await auth.logout(); diff --git a/src/auth/googleSignIn.js b/src/auth/googleSignIn.js index ef86e07..6bebff8 100644 --- a/src/auth/googleSignIn.js +++ b/src/auth/googleSignIn.js @@ -2,6 +2,32 @@ const GIS_SCRIPT = 'https://accounts.google.com/gsi/client'; let scriptPromise; +/** WebView consoles print objects as [object Object]; always log a string. */ +export function logError(tag, details) { + let text; + try { + text = JSON.stringify(details, errorReplacer, 2); + } catch { + text = String(details); + } + console.error(`${tag} ${text}`); +} + +function errorReplacer(_key, value) { + if (value instanceof Error) { + return { + name: value.name, + message: value.message, + code: value.code, + status: value.status, + stack: value.stack, + cause: value.cause, + ...Object.fromEntries(Object.entries(value)), + }; + } + return value; +} + function loadGoogleScript() { if (window.google?.accounts?.id) return Promise.resolve(); if (scriptPromise) return scriptPromise; @@ -12,7 +38,21 @@ function loadGoogleScript() { script.async = true; script.defer = true; script.onload = () => resolve(); - script.onerror = () => reject(new Error('Failed to load Google Sign-In')); + script.onerror = () => { + scriptPromise = null; + //