ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto

fix logout cookie flags, clear client-side cache

Changed files
+68 -42
dist
netlify
functions
src
hooks
+1 -1
dist/index.html
··· 5 5 <link rel="icon" type="image/svg+xml" href="/vite.svg" /> 6 6 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 7 <title>ATLast: Sync Your TikTok Follows → ATmosphere (Skylight, Bluesky, etc.)</title> 8 - <script type="module" crossorigin src="/assets/index-WJfYS828.js"></script> 8 + <script type="module" crossorigin src="/assets/index-DRBs5R5Z.js"></script> 9 9 <link rel="stylesheet" crossorigin href="/assets/index-Bs4vTtm8.css"> 10 10 </head> 11 11 <body>
+33 -18
netlify/functions/logout.ts
··· 1 - import { Handler, HandlerEvent, HandlerResponse } from '@netlify/functions'; 2 - import { userSessions } from './oauth-stores-db'; 3 - import cookie from 'cookie'; 1 + import { Handler, HandlerEvent, HandlerResponse } from "@netlify/functions"; 2 + import { userSessions } from "./oauth-stores-db"; 3 + import { getOAuthConfig } from "./oauth-config"; 4 + import cookie from "cookie"; 4 5 5 - export const handler: Handler = async (event: HandlerEvent): Promise<HandlerResponse> => { 6 + export const handler: Handler = async ( 7 + event: HandlerEvent, 8 + ): Promise<HandlerResponse> => { 6 9 // Only allow POST for logout 7 - if (event.httpMethod !== 'POST') { 10 + if (event.httpMethod !== "POST") { 8 11 return { 9 12 statusCode: 405, 10 - headers: { 'Content-Type': 'application/json' }, 11 - body: JSON.stringify({ error: 'Method not allowed' }), 13 + headers: { "Content-Type": "application/json" }, 14 + body: JSON.stringify({ error: "Method not allowed" }), 12 15 }; 13 16 } 14 17 15 18 try { 19 + console.log("[logout] Starting logout process..."); 20 + console.log("[logout] Cookies received:", event.headers.cookie); 21 + 16 22 // Get session from cookie 17 - const cookies = event.headers.cookie ? cookie.parse(event.headers.cookie) : {}; 23 + const cookies = event.headers.cookie 24 + ? cookie.parse(event.headers.cookie) 25 + : {}; 18 26 const sessionId = cookies.atlast_session; 27 + console.log("[logout] Session ID from cookie:", sessionId); 19 28 20 29 if (sessionId) { 21 30 // Delete session from database 22 31 await userSessions.del(sessionId); 32 + console.log("[logout] Deleted session from database"); 23 33 } 24 34 25 - // Clear the session cookie 26 - const cookieFlags = 'HttpOnly; SameSite=Lax; Max-Age=0; Path=/; Secure'; 35 + // Clear the session cookie with matching flags from when it was set 36 + const config = getOAuthConfig(); 37 + const isDev = config.clientType === "loopback"; 38 + 39 + const cookieFlags = isDev 40 + ? "HttpOnly; SameSite=Lax; Max-Age=0; Path=/" 41 + : "HttpOnly; SameSite=Lax; Max-Age=0; Path=/; Secure"; 27 42 28 43 return { 29 44 statusCode: 200, 30 45 headers: { 31 - 'Content-Type': 'application/json', 32 - 'Set-Cookie': `atlast_session=; ${cookieFlags}`, 46 + "Content-Type": "application/json", 47 + "Set-Cookie": `atlast_session=; ${cookieFlags}`, 33 48 }, 34 49 body: JSON.stringify({ success: true }), 35 50 }; 36 51 } catch (error) { 37 - console.error('Logout error:', error); 52 + console.error("Logout error:", error); 38 53 return { 39 54 statusCode: 500, 40 - headers: { 'Content-Type': 'application/json' }, 41 - body: JSON.stringify({ 42 - error: 'Failed to logout', 43 - details: error instanceof Error ? error.message : 'Unknown error' 55 + headers: { "Content-Type": "application/json" }, 56 + body: JSON.stringify({ 57 + error: "Failed to logout", 58 + details: error instanceof Error ? error.message : "Unknown error", 44 59 }), 45 60 }; 46 61 } 47 - }; 62 + };
+34 -23
src/hooks/useAuth.ts
··· 1 - import { useState, useEffect } from 'react'; 2 - import { apiClient } from '../lib/apiClient'; 3 - import type { AtprotoSession, AppStep } from '../types'; 1 + import { useState, useEffect } from "react"; 2 + import { apiClient } from "../lib/apiClient"; 3 + import type { AtprotoSession, AppStep } from "../types"; 4 4 5 5 export function useAuth() { 6 6 const [session, setSession] = useState<AtprotoSession | null>(null); 7 - const [currentStep, setCurrentStep] = useState<AppStep>('checking'); 7 + const [currentStep, setCurrentStep] = useState<AppStep>("checking"); 8 8 const [statusMessage, setStatusMessage] = useState(""); 9 9 10 10 useEffect(() => { ··· 13 13 14 14 async function checkExistingSession() { 15 15 try { 16 + console.log("[useAuth] Checking existing session..."); 16 17 const params = new URLSearchParams(window.location.search); 17 - const sessionId = params.get('session'); 18 - const error = params.get('error'); 18 + const sessionId = params.get("session"); 19 + const error = params.get("error"); 19 20 20 21 if (error) { 22 + console.log("[useAuth] Error in URL:", error); 21 23 setStatusMessage(`Login failed: ${error}`); 22 - setCurrentStep('login'); 23 - window.history.replaceState({}, '', '/'); 24 + setCurrentStep("login"); 25 + window.history.replaceState({}, "", "/"); 24 26 return; 25 27 } 26 28 27 29 // If we have a session parameter in URL, this is an OAuth callback 28 30 if (sessionId) { 29 - setStatusMessage('Loading your session...'); 30 - 31 + console.log("[useAuth] Session ID in URL:", sessionId); 32 + setStatusMessage("Loading your session..."); 33 + 31 34 // Single call now gets both session AND profile data 32 35 const data = await apiClient.getSession(); 33 36 setSession({ ··· 37 40 avatar: data.avatar, 38 41 description: data.description, 39 42 }); 40 - setCurrentStep('home'); 43 + setCurrentStep("home"); 41 44 setStatusMessage(`Welcome back, ${data.handle}!`); 42 - 43 - window.history.replaceState({}, '', '/'); 45 + 46 + window.history.replaceState({}, "", "/"); 44 47 return; 45 48 } 46 49 47 50 // Otherwise, check if there's an existing session cookie 48 51 // Single call now gets both session AND profile data 52 + console.log("[useAuth] Checking for existing session cookie..."); 49 53 const data = await apiClient.getSession(); 54 + console.log("[useAuth] Found existing session:", data); 50 55 setSession({ 51 56 did: data.did, 52 57 handle: data.handle, ··· 54 59 avatar: data.avatar, 55 60 description: data.description, 56 61 }); 57 - setCurrentStep('home'); 62 + setCurrentStep("home"); 58 63 setStatusMessage(`Welcome back, ${data.handle}!`); 59 64 } catch (error) { 60 - console.error('Session check error:', error); 61 - setCurrentStep('login'); 65 + console.log("[useAuth] No valid session found:", error); 66 + setCurrentStep("login"); 62 67 } 63 68 } 64 69 ··· 70 75 } 71 76 72 77 setStatusMessage("Starting authentication..."); 73 - 78 + 74 79 const { url } = await apiClient.startOAuth(handle); 75 80 setStatusMessage("Redirecting to authentication..."); 76 81 window.location.href = url; ··· 78 83 79 84 async function logout() { 80 85 try { 81 - setStatusMessage('Logging out...'); 86 + console.log("[useAuth] Cookies before logout:", document.cookie); 87 + console.log("[useAuth] Starting logout..."); 88 + setStatusMessage("Logging out..."); 82 89 await apiClient.logout(); 90 + console.log("[useAuth] Cookies after logout:", document.cookie); 91 + 92 + apiClient.cache.clear(); // Clear client-side cache 93 + console.log("[useAuth] Cache cleared"); 83 94 setSession(null); 84 - setCurrentStep('login'); 85 - setStatusMessage('Logged out successfully'); 95 + setCurrentStep("login"); 96 + setStatusMessage("Logged out successfully"); 86 97 } catch (error) { 87 - console.error('Logout error:', error); 88 - setStatusMessage('Error logging out'); 98 + console.error("Logout error:", error); 99 + setStatusMessage("Error logging out"); 89 100 throw error; 90 101 } 91 102 } ··· 99 110 login, 100 111 logout, 101 112 }; 102 - } 113 + }