A community based topic aggregation platform built on atproto

fix(oauth): remove static HTML callback, let Go handler process OAuth

The static callback.html was intercepting OAuth callbacks before they
reached the Go handler. This prevented proper token exchange and caused
"Sign in successful" HTML to be shown instead of redirecting to the
mobile app's Universal Link callback URL.

Now all /oauth/callback requests go through the Go handler which:
- Exchanges OAuth code for tokens
- Creates sealed session tokens
- Redirects mobile flows to Universal Link URL with credentials

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+1 -105
static
+1 -8
Caddyfile
··· 60 file_server 61 } 62 63 - # Serve OAuth callback page 64 - handle /oauth/callback { 65 - root * /srv 66 - rewrite * /oauth/callback.html 67 - file_server 68 - } 69 - 70 - # Proxy all other requests to AppView 71 handle { 72 reverse_proxy appview:8080 { 73 # Health check
··· 60 file_server 61 } 62 63 + # Proxy all requests to AppView 64 handle { 65 reverse_proxy appview:8080 { 66 # Health check
-97
static/oauth/callback.html
··· 1 - <!DOCTYPE html> 2 - <html> 3 - <head> 4 - <meta charset="utf-8"> 5 - <meta name="viewport" content="width=device-width, initial-scale=1"> 6 - <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'unsafe-inline'; style-src 'unsafe-inline'"> 7 - <title>Authorization Successful - Coves</title> 8 - <style> 9 - body { 10 - font-family: system-ui, -apple-system, sans-serif; 11 - display: flex; 12 - align-items: center; 13 - justify-content: center; 14 - min-height: 100vh; 15 - margin: 0; 16 - background: #f5f5f5; 17 - } 18 - .container { 19 - text-align: center; 20 - padding: 2rem; 21 - background: white; 22 - border-radius: 8px; 23 - box-shadow: 0 2px 8px rgba(0,0,0,0.1); 24 - max-width: 400px; 25 - } 26 - .success { color: #22c55e; font-size: 3rem; margin-bottom: 1rem; } 27 - h1 { margin: 0 0 0.5rem; color: #1f2937; font-size: 1.5rem; } 28 - p { color: #6b7280; margin: 0.5rem 0; } 29 - a { 30 - display: inline-block; 31 - margin-top: 1rem; 32 - padding: 0.75rem 1.5rem; 33 - background: #3b82f6; 34 - color: white; 35 - text-decoration: none; 36 - border-radius: 6px; 37 - font-weight: 500; 38 - } 39 - a:hover { background: #2563eb; } 40 - </style> 41 - </head> 42 - <body> 43 - <div class="container"> 44 - <div class="success">✓</div> 45 - <h1>Authorization Successful!</h1> 46 - <p id="status">Returning to Coves...</p> 47 - <a href="#" id="manualLink">Open Coves</a> 48 - </div> 49 - <script> 50 - (function() { 51 - // Parse and sanitize query params - only allow expected OAuth parameters 52 - const urlParams = new URLSearchParams(window.location.search); 53 - const safeParams = new URLSearchParams(); 54 - 55 - // Whitelist only expected OAuth callback parameters 56 - const code = urlParams.get('code'); 57 - const state = urlParams.get('state'); 58 - const error = urlParams.get('error'); 59 - const errorDescription = urlParams.get('error_description'); 60 - const iss = urlParams.get('iss'); 61 - 62 - if (code) safeParams.set('code', code); 63 - if (state) safeParams.set('state', state); 64 - if (error) safeParams.set('error', error); 65 - if (errorDescription) safeParams.set('error_description', errorDescription); 66 - if (iss) safeParams.set('iss', iss); 67 - 68 - const sanitizedQuery = safeParams.toString() ? '?' + safeParams.toString() : ''; 69 - 70 - const userAgent = navigator.userAgent || ''; 71 - const isAndroid = /Android/i.test(userAgent); 72 - 73 - // Build deep link based on platform 74 - let deepLink; 75 - if (isAndroid) { 76 - // Android: Intent URL format 77 - const pathAndQuery = '/oauth/callback' + sanitizedQuery; 78 - deepLink = 'intent:/' + pathAndQuery + '#Intent;scheme=social.coves;package=social.coves;end'; 79 - } else { 80 - // iOS: Custom scheme 81 - deepLink = 'social.coves:/oauth/callback' + sanitizedQuery; 82 - } 83 - 84 - // Update manual link 85 - document.getElementById('manualLink').href = deepLink; 86 - 87 - // Attempt automatic redirect 88 - window.location.href = deepLink; 89 - 90 - // Update status after 2 seconds if redirect didn't work 91 - setTimeout(function() { 92 - document.getElementById('status').textContent = 'Click the button above to continue'; 93 - }, 2000); 94 - })(); 95 - </script> 96 - </body> 97 - </html>
···