diff --git a/src/app/api/me/route.ts b/src/app/api/me/route.ts new file mode 100644 index 0000000..7ec020e --- /dev/null +++ b/src/app/api/me/route.ts @@ -0,0 +1,52 @@ +/** + * /api/me — Returns current user's auth status. + * + * Checks for EncryptID token in Authorization header or cookie, + * then verifies it against the EncryptID server. + */ + +import { NextRequest, NextResponse } from 'next/server'; + +const ENCRYPTID_URL = process.env.ENCRYPTID_URL || 'https://auth.ridentity.online'; + +export async function GET(req: NextRequest) { + // Extract token from Authorization header or cookie + const auth = req.headers.get('Authorization'); + let token: string | null = null; + + if (auth?.startsWith('Bearer ')) { + token = auth.slice(7); + } else { + const tokenCookie = req.cookies.get('encryptid_token'); + if (tokenCookie) token = tokenCookie.value; + } + + if (!token) { + return NextResponse.json({ authenticated: false }); + } + + try { + const res = await fetch(`${ENCRYPTID_URL}/api/session/verify`, { + headers: { Authorization: `Bearer ${token}` }, + }); + + if (!res.ok) { + return NextResponse.json({ authenticated: false }); + } + + const data = await res.json(); + if (data.valid) { + return NextResponse.json({ + authenticated: true, + user: { + username: data.username || null, + did: data.did || data.userId || null, + }, + }); + } + + return NextResponse.json({ authenticated: false }); + } catch { + return NextResponse.json({ authenticated: false }); + } +} diff --git a/src/app/api/spaces/route.ts b/src/app/api/spaces/route.ts new file mode 100644 index 0000000..f5cf371 --- /dev/null +++ b/src/app/api/spaces/route.ts @@ -0,0 +1,45 @@ +/** + * Spaces API proxy — forwards to rSpace (the canonical spaces authority). + * + * Every r*App proxies /api/spaces to rSpace so the SpaceSwitcher dropdown + * shows the same spaces everywhere. The EncryptID token is forwarded so + * rSpace can return user-specific spaces (owned/member). + */ + +import { NextRequest, NextResponse } from 'next/server'; + +const RSPACE_API = process.env.RSPACE_API_URL || 'https://rspace.online'; + +export async function GET(req: NextRequest) { + const headers: Record = {}; + + // Forward the EncryptID token (from Authorization header or cookie) + const auth = req.headers.get('Authorization'); + if (auth) { + headers['Authorization'] = auth; + } else { + // Fallback: check for encryptid_token cookie + const tokenCookie = req.cookies.get('encryptid_token'); + if (tokenCookie) { + headers['Authorization'] = `Bearer ${tokenCookie.value}`; + } + } + + try { + const res = await fetch(`${RSPACE_API}/api/spaces`, { + headers, + next: { revalidate: 30 }, // cache for 30s to avoid hammering rSpace + }); + + if (!res.ok) { + // If rSpace is down, return empty spaces (graceful degradation) + return NextResponse.json({ spaces: [] }); + } + + const data = await res.json(); + return NextResponse.json(data); + } catch { + // rSpace unreachable — return empty list + return NextResponse.json({ spaces: [] }); + } +} diff --git a/src/components/SpaceSwitcher.tsx b/src/components/SpaceSwitcher.tsx index f5a9450..b23e813 100644 --- a/src/components/SpaceSwitcher.tsx +++ b/src/components/SpaceSwitcher.tsx @@ -10,10 +10,20 @@ interface SpaceInfo { } interface SpaceSwitcherProps { - /** Current app domain, e.g. 'rfunds.online'. Space links become . */ + /** Current app domain, e.g. 'rchats.online'. Space links become . */ domain?: string; } +/** Read the EncryptID token from localStorage (set by token-relay across r*.online) */ +function getEncryptIDToken(): string | null { + if (typeof window === 'undefined') return null; + try { + return localStorage.getItem('encryptid_token'); + } catch { + return null; + } +} + export function SpaceSwitcher({ domain }: SpaceSwitcherProps) { const [open, setOpen] = useState(false); const [spaces, setSpaces] = useState([]); @@ -38,18 +48,29 @@ export function SpaceSwitcher({ domain }: SpaceSwitcherProps) { // Check auth status on mount useEffect(() => { - fetch('/api/me') - .then((r) => r.json()) - .then((data) => { - if (data.authenticated) setIsAuthenticated(true); - }) - .catch(() => {}); + const token = getEncryptIDToken(); + if (token) { + setIsAuthenticated(true); + } else { + // Fallback: check /api/me + fetch('/api/me') + .then((r) => r.json()) + .then((data) => { + if (data.authenticated) setIsAuthenticated(true); + }) + .catch(() => {}); + } }, []); const loadSpaces = async () => { if (loaded) return; try { - const res = await fetch('/api/spaces'); + // Pass EncryptID token so the proxy can forward it to rSpace + const token = getEncryptIDToken(); + const headers: Record = {}; + if (token) headers['Authorization'] = `Bearer ${token}`; + + const res = await fetch('/api/spaces', { headers }); if (res.ok) { const data = await res.json(); setSpaces(data.spaces || []);