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
···
file_server
}
-
# Serve OAuth callback page
-
handle /oauth/callback {
-
root * /srv
-
rewrite * /oauth/callback.html
-
file_server
-
}
-
-
# Proxy all other requests to AppView
handle {
reverse_proxy appview:8080 {
# Health check
···
file_server
}
+
# Proxy all requests to AppView
handle {
reverse_proxy appview:8080 {
# Health check
-97
static/oauth/callback.html
···
-
<!DOCTYPE html>
-
<html>
-
<head>
-
<meta charset="utf-8">
-
<meta name="viewport" content="width=device-width, initial-scale=1">
-
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'unsafe-inline'; style-src 'unsafe-inline'">
-
<title>Authorization Successful - Coves</title>
-
<style>
-
body {
-
font-family: system-ui, -apple-system, sans-serif;
-
display: flex;
-
align-items: center;
-
justify-content: center;
-
min-height: 100vh;
-
margin: 0;
-
background: #f5f5f5;
-
}
-
.container {
-
text-align: center;
-
padding: 2rem;
-
background: white;
-
border-radius: 8px;
-
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
-
max-width: 400px;
-
}
-
.success { color: #22c55e; font-size: 3rem; margin-bottom: 1rem; }
-
h1 { margin: 0 0 0.5rem; color: #1f2937; font-size: 1.5rem; }
-
p { color: #6b7280; margin: 0.5rem 0; }
-
a {
-
display: inline-block;
-
margin-top: 1rem;
-
padding: 0.75rem 1.5rem;
-
background: #3b82f6;
-
color: white;
-
text-decoration: none;
-
border-radius: 6px;
-
font-weight: 500;
-
}
-
a:hover { background: #2563eb; }
-
</style>
-
</head>
-
<body>
-
<div class="container">
-
<div class="success">✓</div>
-
<h1>Authorization Successful!</h1>
-
<p id="status">Returning to Coves...</p>
-
<a href="#" id="manualLink">Open Coves</a>
-
</div>
-
<script>
-
(function() {
-
// Parse and sanitize query params - only allow expected OAuth parameters
-
const urlParams = new URLSearchParams(window.location.search);
-
const safeParams = new URLSearchParams();
-
-
// Whitelist only expected OAuth callback parameters
-
const code = urlParams.get('code');
-
const state = urlParams.get('state');
-
const error = urlParams.get('error');
-
const errorDescription = urlParams.get('error_description');
-
const iss = urlParams.get('iss');
-
-
if (code) safeParams.set('code', code);
-
if (state) safeParams.set('state', state);
-
if (error) safeParams.set('error', error);
-
if (errorDescription) safeParams.set('error_description', errorDescription);
-
if (iss) safeParams.set('iss', iss);
-
-
const sanitizedQuery = safeParams.toString() ? '?' + safeParams.toString() : '';
-
-
const userAgent = navigator.userAgent || '';
-
const isAndroid = /Android/i.test(userAgent);
-
-
// Build deep link based on platform
-
let deepLink;
-
if (isAndroid) {
-
// Android: Intent URL format
-
const pathAndQuery = '/oauth/callback' + sanitizedQuery;
-
deepLink = 'intent:/' + pathAndQuery + '#Intent;scheme=social.coves;package=social.coves;end';
-
} else {
-
// iOS: Custom scheme
-
deepLink = 'social.coves:/oauth/callback' + sanitizedQuery;
-
}
-
-
// Update manual link
-
document.getElementById('manualLink').href = deepLink;
-
-
// Attempt automatic redirect
-
window.location.href = deepLink;
-
-
// Update status after 2 seconds if redirect didn't work
-
setTimeout(function() {
-
document.getElementById('status').textContent = 'Click the button above to continue';
-
}, 2000);
-
})();
-
</script>
-
</body>
-
</html>
···