From 5bd78a49ce2102c61ec39d36cabb606cddea6d03 Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Mon, 5 Oct 2026 15:58:15 -0400 Subject: [PATCH 1/8] feat(auth): expose current session identity --- backend/src/controllers/authController.ts | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/backend/src/controllers/authController.ts b/backend/src/controllers/authController.ts index 5fc95b7d..386148fe 100644 --- a/backend/src/controllers/authController.ts +++ b/backend/src/controllers/authController.ts @@ -319,6 +319,49 @@ export class AuthController { ); } + /** + * GET /api/auth/session + * Returns the authenticated user's display identity and organization. + */ + static async session(req: express.Request, res: express.Response) { + try { + const result = await query( + `SELECT + u.id, + u.email, + u.name, + u.wallet_address, + u.organization_id, + u.role, + o.name AS organization_name + FROM users u + LEFT JOIN organizations o ON o.id = u.organization_id + WHERE u.id = $1`, + [req.user!.id] + ); + + if (result.rows.length === 0) { + return res.status(404).json({ error: 'User not found' }); + } + + const user = result.rows[0]; + return res.json({ + user: { + id: user.id, + email: user.email ?? null, + name: user.name ?? null, + walletAddress: user.wallet_address ?? null, + organizationId: user.organization_id ?? null, + organizationName: user.organization_name ?? null, + role: user.role, + }, + }); + } catch (error) { + console.error('Session lookup failed:', error); + return res.status(500).json({ error: 'Internal server error' }); + } + } + /** * POST /api/auth/refresh * Refreshes access token using a valid refresh token. From 2482256a70f53f174b391580b21e859611e341e8 Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Mon, 5 Oct 2026 15:58:23 -0400 Subject: [PATCH 2/8] feat(auth): add current session endpoint --- backend/src/routes/authRoutes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/routes/authRoutes.ts b/backend/src/routes/authRoutes.ts index 16f75526..1183d365 100644 --- a/backend/src/routes/authRoutes.ts +++ b/backend/src/routes/authRoutes.ts @@ -10,6 +10,7 @@ const router = Router(); router.post('/login', AuthController.login); router.post('/register', AuthController.register); router.post('/refresh', AuthController.refresh); +router.get('/session', authenticateJWT, AuthController.session); router.post( '/invitations', From 702a39b8d2d72210a3e8d9e3a177770021c83707 Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Mon, 5 Oct 2026 15:58:39 -0400 Subject: [PATCH 3/8] feat(auth): add frontend session provider --- frontend/src/providers/AuthProvider.tsx | 106 ++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 frontend/src/providers/AuthProvider.tsx diff --git a/frontend/src/providers/AuthProvider.tsx b/frontend/src/providers/AuthProvider.tsx new file mode 100644 index 00000000..b3ccb88c --- /dev/null +++ b/frontend/src/providers/AuthProvider.tsx @@ -0,0 +1,106 @@ +import React, { + createContext, + useCallback, + useContext, + useEffect, + useState, +} from 'react'; +import axios from 'axios'; +import { useLocation } from 'react-router-dom'; +import api from '../utils/api'; + +export interface AuthUser { + id: number; + email: string | null; + name: string | null; + walletAddress: string | null; + organizationId: number | null; + organizationName: string | null; + role: string; +} + +interface AuthContextValue { + user: AuthUser | null; + loading: boolean; + logout: () => void; +} + +const AuthContext = createContext(undefined); + +function storedToken() { + return localStorage.getItem('payd_auth_token'); +} + +export const AuthProvider: React.FC = ({ children }) => { + const location = useLocation(); + const [token, setToken] = useState(() => storedToken()); + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(() => Boolean(storedToken())); + + const loadSession = useCallback(async (currentToken: string | null) => { + if (!currentToken) { + setUser(null); + setLoading(false); + return; + } + + setLoading(true); + try { + const response = await api.get<{ user: AuthUser }>('/auth/session'); + setUser(response.data.user); + } catch (error) { + setUser(null); + + if ( + axios.isAxiosError(error) && + (error.response?.status === 401 || error.response?.status === 404) + ) { + localStorage.removeItem('payd_auth_token'); + localStorage.removeItem('accessToken'); + setToken(null); + } + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + const currentToken = storedToken(); + if (currentToken !== token) { + setToken(currentToken); + } + }, [location.pathname, token]); + + useEffect(() => { + void loadSession(token); + }, [loadSession, token]); + + useEffect(() => { + const handleStorage = (event: StorageEvent) => { + if (event.key === 'payd_auth_token') { + setToken(event.newValue); + } + }; + + window.addEventListener('storage', handleStorage); + return () => window.removeEventListener('storage', handleStorage); + }, []); + + const logout = useCallback(() => { + localStorage.removeItem('payd_auth_token'); + localStorage.removeItem('accessToken'); + setToken(null); + setUser(null); + window.location.assign('/login'); + }, []); + + return {children}; +}; + +export function useAuth() { + const context = useContext(AuthContext); + if (!context) { + throw new Error('useAuth must be used within AuthProvider'); + } + return context; +} From e191a24b949b3f2cdeeaba4c6ca858d2ef2276af Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Mon, 5 Oct 2026 15:58:52 -0400 Subject: [PATCH 4/8] feat(auth): provide session context to app --- frontend/src/main.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index f84f1879..0a14c323 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -8,6 +8,7 @@ import { WalletProvider } from './providers/WalletProvider.tsx'; import { NotificationProvider } from './providers/NotificationProvider.tsx'; import { SocketProvider } from './providers/SocketProvider.tsx'; import { ThemeProvider } from './providers/ThemeProvider.tsx'; +import { AuthProvider } from './providers/AuthProvider.tsx'; import * as Sentry from '@sentry/react'; import ErrorBoundary from './components/ErrorBoundary'; import ErrorFallback from './components/ErrorFallback'; @@ -35,9 +36,11 @@ ReactDOM.createRoot(document.getElementById('root')!).render( - {}} />}> - - + + {}} />}> + + + From c2925bcdb8840d056dafb59aa08aae1b2dca499a Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Mon, 5 Oct 2026 15:59:23 -0400 Subject: [PATCH 5/8] feat(nav): show authenticated user and logout --- frontend/src/components/AppNav.tsx | 81 +++++++++++++++++------------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/frontend/src/components/AppNav.tsx b/frontend/src/components/AppNav.tsx index ced8ef3b..fe928960 100644 --- a/frontend/src/components/AppNav.tsx +++ b/frontend/src/components/AppNav.tsx @@ -13,18 +13,15 @@ import { X, BarChart2, TrendingUp, + LogOut, } from 'lucide-react'; import { Avatar } from './Avatar'; +import { useAuth } from '../providers/AuthProvider'; const AppNav: React.FC = () => { const [mobileOpen, setMobileOpen] = useState(false); - // Mock user data - replace with actual user context - const currentUser = { - email: 'user@example.com', - name: 'John Doe', - imageUrl: undefined, - }; + const { user, loading, logout } = useAuth(); const navLinks = ( <> @@ -202,6 +199,46 @@ const AppNav: React.FC = () => { ); + const displayName = user?.name || user?.email || user?.walletAddress || ''; + const displayDetail = user?.email || user?.walletAddress || ''; + + const userProfile = loading ? ( +
+ ) : user ? ( +
+
+ +
+

{displayName}

+

{displayDetail}

+
+
+ +
+ ) : ( + + Sign in + + ); + return (
{/* Mobile menu footer with user info */} -
-
- -
-

- {currentUser.name} -

-

{currentUser.email}

-
-
-
+
{userProfile}
From d901257d879d1e5ac551774002e00e819e404f4c Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Mon, 5 Oct 2026 15:59:40 -0400 Subject: [PATCH 6/8] feat(nav): show authenticated organization --- frontend/src/components/DashboardTopBar.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/DashboardTopBar.tsx b/frontend/src/components/DashboardTopBar.tsx index 617350d5..63e4dcb3 100644 --- a/frontend/src/components/DashboardTopBar.tsx +++ b/frontend/src/components/DashboardTopBar.tsx @@ -2,10 +2,10 @@ import React from 'react'; import ThemeToggle from './ThemeToggle'; import ConnectAccount from './ConnectAccount'; import { Text } from '@stellar/design-system'; +import { useAuth } from '../providers/AuthProvider'; export const DashboardTopBar: React.FC = () => { - // Mock org data - const orgName = 'FutureLabs Inc.'; + const { user, loading } = useAuth(); const balance = '1,250.45 USDC'; return ( @@ -20,9 +20,16 @@ export const DashboardTopBar: React.FC = () => { > Organization - - {orgName} - + {loading ? ( + + ) : ( + + {user?.organizationName ?? 'No organization'} + + )}
From bb5292dcb927b1df0961aadf5b42477c8485963f Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Mon, 5 Oct 2026 16:21:15 -0400 Subject: [PATCH 7/8] fix(auth-nav): show identity details in mobile drawer Render the authenticated account details explicitly in the mobile drawer while retaining the compact profile in the narrow top bar. This closes the mobile acceptance gap without duplicating session state or changing auth behavior. Attribution: GPT-5.6 Sol Pro / SOL-MC-COORD-20261005 / connected ChatGPT cloud connector harness --- frontend/src/components/AppNav.tsx | 73 ++++++++++++++++-------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/frontend/src/components/AppNav.tsx b/frontend/src/components/AppNav.tsx index fe928960..b03e7017 100644 --- a/frontend/src/components/AppNav.tsx +++ b/frontend/src/components/AppNav.tsx @@ -202,42 +202,45 @@ const AppNav: React.FC = () => { const displayName = user?.name || user?.email || user?.walletAddress || ''; const displayDetail = user?.email || user?.walletAddress || ''; - const userProfile = loading ? ( -
- ) : user ? ( -
-
- -
-

{displayName}

-

{displayDetail}

+ const renderUserProfile = (showDetails: boolean) => + loading ? ( +
+ ) : user ? ( +
+
+ +
+

{displayName}

+

{displayDetail}

+
+
- -
- ) : ( - - Sign in - - ); + Sign in + + ); return (
{/* Mobile dropdown menu */} @@ -298,7 +301,7 @@ const AppNav: React.FC = () => {
{/* Mobile menu footer with user info */} -
{userProfile}
+
{renderUserProfile(true)}
From f64ccb13ae5819a08d68751020227f90a8986fb4 Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Tue, 6 Oct 2026 22:45:48 -0400 Subject: [PATCH 8/8] fix(auth): clear stale forbidden sessions --- frontend/src/providers/AuthProvider.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/providers/AuthProvider.tsx b/frontend/src/providers/AuthProvider.tsx index b3ccb88c..3342a8df 100644 --- a/frontend/src/providers/AuthProvider.tsx +++ b/frontend/src/providers/AuthProvider.tsx @@ -53,7 +53,9 @@ export const AuthProvider: React.FC = ({ children }) => if ( axios.isAxiosError(error) && - (error.response?.status === 401 || error.response?.status === 404) + (error.response?.status === 401 || + error.response?.status === 403 || + error.response?.status === 404) ) { localStorage.removeItem('payd_auth_token'); localStorage.removeItem('accessToken');