# Plan: Make Galaxy Viewable Without Login Requirement ## Status - **Priority**: HIGH (User-requested) - **Status**: In Progress - **Created**: 2025-01-10 ## Problem Statement Currently, the galaxy visualization (`/galaxy`) requires user authentication via JWT cookie. This prevents: 1. Public sharing of thought galaxies 2. First-time visitors from seeing example galaxies 3. Social media link previews from working properly 4. Search engines from indexing public thought networks The galaxy should be publicly viewable while still respecting user privacy preferences. ## Current Implementation Analysis ### Authentication Check `app/api/galaxy/route.ts` (lines 27-38): ```typescript const surrealJwt = cookieStore.get('ponderants-auth')?.value; if (!surrealJwt) { return NextResponse.json({ error: 'Not authenticated' }, { status: 401 }); } const userSession = verifySurrealJwt(surrealJwt); if (!userSession) { return NextResponse.json({ error: 'Invalid auth token' }, { status: 401 }); } ``` ### Data Query Currently queries `WHERE user_did = $userDid` (line 49), showing only the authenticated user's nodes. ## Design Decisions ### Option 1: Public by Default (RECOMMENDED) **All galaxies are publicly viewable**, but users can mark individual nodes as private. **Pros:** - Simple implementation - Encourages public knowledge sharing - Better for SEO and discovery - Aligns with "decentralized social" vision **Cons:** - Users might accidentally share private thoughts - Requires clear UI indicators for node visibility **URL Structure:** - `/galaxy` - current user's galaxy (if logged in) or landing page - `/galaxy/{user_did}` or `/galaxy?user={user_did}` - specific user's public galaxy ### Option 2: Opt-in Public Galleries Galaxies are private by default, users must explicitly make them public. **Pros:** - More privacy-conscious - Users have full control **Cons:** - Reduces discovery and sharing - More complex implementation - Goes against ATproto's "public by default" philosophy **Decision: We'll implement Option 1** - Public by default, with optional private nodes. ## Implementation Plan ### Phase 1: Update API to Support Public Access #### 1.1 Modify `/api/galaxy/route.ts` ```typescript export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url); const targetUserDid = searchParams.get('user'); const cookieStore = await cookies(); const surrealJwt = cookieStore.get('ponderants-auth')?.value; // Determine which user's galaxy to show let userDid: string; let isOwnGalaxy = false; if (targetUserDid) { // Viewing someone else's public galaxy userDid = targetUserDid; } else if (surrealJwt) { // Viewing own galaxy (authenticated) const userSession = verifySurrealJwt(surrealJwt); if (!userSession) { return NextResponse.json({ error: 'Invalid auth token' }, { status: 401 }); } userDid = userSession.did; isOwnGalaxy = true; } else { // No target user and not authenticated - return empty galaxy with message return NextResponse.json({ nodes: [], links: [], message: 'Log in to view your galaxy, or visit a public galaxy via ?user={did}' }); } // Query nodes const nodesQuery = ` SELECT id, title, body, user_did, atp_uri, coords_3d FROM node WHERE user_did = $userDid AND coords_3d != NONE ${isOwnGalaxy ? '' : 'AND is_public = true'} `; // ... rest of implementation } ``` #### 1.2 Add `is_public` Field to Node Schema - Default: `true` (public by default) - Users can mark individual nodes as private - Private nodes are only visible to the owner ### Phase 2: Update Database Schema #### 2.1 Add `is_public` Column to `node` Table ```sql DEFINE FIELD is_public ON TABLE node TYPE bool DEFAULT true; ``` #### 2.2 Create Migration Script `scripts/add-is-public-field.ts`: ```typescript import { connectToDB } from '@/lib/db'; async function migrate() { const db = await connectToDB(); // Add field definition await db.query(` DEFINE FIELD is_public ON TABLE node TYPE bool DEFAULT true; `); // Set existing nodes to public await db.query(` UPDATE node SET is_public = true WHERE is_public = NONE; `); console.log('Migration complete: Added is_public field'); } migrate(); ``` ### Phase 3: Update Frontend Components #### 3.1 Update `ThoughtGalaxy.tsx` ```typescript // Support viewing other users' galaxies const { searchParams } = useSearchParams(); const targetUser = searchParams.get('user'); useEffect(() => { async function fetchData() { const url = targetUser ? `/api/galaxy?user=${targetUser}` : '/api/galaxy'; const response = await fetch(url, { credentials: 'include', }); // ... rest of implementation } fetchData(); }, [targetUser]); ``` #### 3.2 Add User Info Display When viewing another user's galaxy, show: - User's Bluesky handle - Link to their profile - Number of public nodes #### 3.3 Update `/galaxy` Page Add support for URL parameter: `/galaxy?user=did:plc:xxxxx` ### Phase 4: Navigation & User Experience #### 4.1 Landing Experience for Non-Authenticated Users When visiting `/galaxy` without login: - Show a sample/demo galaxy (could be a curated example) - Display call-to-action: "Create your own thought galaxy" - Provide login button #### 4.2 Add "Share Galaxy" Feature Add button to copy shareable link: ```typescript const shareUrl = `${window.location.origin}/galaxy?user=${userDid}`; ``` ### Phase 5: Privacy Controls (Future Enhancement) #### 5.1 Node-Level Privacy Toggle In node editor, add checkbox: ```typescript setIsPublic(e.currentTarget.checked)} /> ``` #### 5.2 Bulk Privacy Management Settings page to: - Make all nodes private/public - Set default for new nodes - Filter and update specific nodes ## Security Considerations ### 1. Data Exposure - **Risk**: Users accidentally share sensitive information - **Mitigation**: - Clear visual indicators for public/private nodes - Confirmation dialog when publishing nodes - Easy way to make nodes private retroactively ### 2. API Abuse - **Risk**: Scraping or excessive requests to public galaxies - **Mitigation**: - Rate limiting on `/api/galaxy` - Caching layer for public galaxies - Consider CDN for popular galaxies ### 3. Privacy Violations - **Risk**: Viewing history tracking or surveillance - **Mitigation**: - No analytics on public galaxy views - No "who viewed my galaxy" feature - Respect DNT headers ## Testing Plan ### Magnitude Tests #### Test 1: Public Galaxy Viewing Without Auth ```typescript test('Unauthenticated users can view public galaxies', async (agent) => { await agent.act('Navigate to /galaxy?user=did:plc:example123'); await agent.check('The galaxy visualization is displayed'); await agent.check('Public nodes are visible'); await agent.act('Click on a public node'); await agent.check('Node details are displayed'); }); ``` #### Test 2: Private Nodes Hidden from Public View ```typescript test('Private nodes are not visible in public galaxy', async (agent) => { // ... implementation }); ``` #### Test 3: Own Galaxy Requires Auth ```typescript test('Accessing own galaxy without target user requires authentication', async (agent) => { await agent.act('Navigate to /galaxy'); await agent.check('Login prompt or empty state is displayed'); }); ``` ### Manual Testing Checklist - [ ] Visit `/galaxy` without login → see landing page - [ ] Visit `/galaxy?user={valid_did}` → see public nodes - [ ] Visit `/galaxy?user={invalid_did}` → see error message - [ ] Log in and visit `/galaxy` → see own galaxy (including private nodes) - [ ] Share galaxy link → recipient can view public nodes - [ ] Mark node as private → confirm it disappears from public view ## Implementation Steps 1. **Create database migration** for `is_public` field 2. **Update API route** to support public access 3. **Update ThoughtGalaxy component** to handle URL parameters 4. **Add user info display** for public galaxies 5. **Test with manual checks** 6. **Write Magnitude tests** 7. **Update documentation** 8. **Create PR with changes** ## Acceptance Criteria ✅ Unauthenticated users can view public galaxies via `?user=` parameter ✅ Authenticated users see their own galaxy at `/galaxy` (no param) ✅ Private nodes are only visible to the owner ✅ Public nodes are visible to everyone ✅ Clear error messages for invalid user DIDs ✅ Shareable URLs work correctly ✅ All tests pass ## Notes - This aligns with ATproto's philosophy of public-by-default, user-controlled data - Future enhancement: Node-level privacy controls in UI - Consider adding Open Graph meta tags for social media previews - May want to add a "featured galaxies" page for discovery ## Related Files - `app/api/galaxy/route.ts` - Galaxy API endpoint - `components/ThoughtGalaxy.tsx` - 3D visualization component - `app/galaxy/page.tsx` - Galaxy page component - `lib/db/schema.surql` - Database schema