feat: add OAuth 2.0 authentication and README
- Implement RFC 8414 OAuth authorization server metadata - Add dynamic client registration (RFC 7591) - Add /authorize and /token endpoints - Create comprehensive README with setup instructions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -67,26 +67,94 @@ async function startSSEServer() {
|
||||
res.send('healthy\n');
|
||||
});
|
||||
|
||||
// OAuth discovery endpoints - return empty/minimal responses
|
||||
// Claude Code checks these before connecting
|
||||
// OAuth 2.0 Authorization Server Metadata (RFC 8414)
|
||||
// This tells Claude Code how to authenticate
|
||||
const serverUrl = 'https://ultra.runningwolf.com';
|
||||
|
||||
app.get('/.well-known/oauth-authorization-server', (_req, res) => {
|
||||
res.json({
|
||||
issuer: serverUrl,
|
||||
authorization_endpoint: `${serverUrl}/authorize`,
|
||||
token_endpoint: `${serverUrl}/token`,
|
||||
registration_endpoint: `${serverUrl}/register`,
|
||||
response_types_supported: ['code'],
|
||||
grant_types_supported: ['authorization_code'],
|
||||
code_challenge_methods_supported: ['S256'],
|
||||
token_endpoint_auth_methods_supported: ['none'],
|
||||
});
|
||||
});
|
||||
|
||||
// Protected Resource Metadata (RFC 9728)
|
||||
app.get('/.well-known/oauth-protected-resource', (_req, res) => {
|
||||
res.status(404).json({ error: 'not_found', error_description: 'OAuth not supported' });
|
||||
res.json({
|
||||
resource: serverUrl,
|
||||
authorization_servers: [serverUrl],
|
||||
bearer_methods_supported: ['header'],
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/.well-known/oauth-protected-resource/*', (_req, res) => {
|
||||
res.status(404).json({ error: 'not_found', error_description: 'OAuth not supported' });
|
||||
});
|
||||
|
||||
app.get('/.well-known/oauth-authorization-server', (_req, res) => {
|
||||
res.status(404).json({ error: 'not_found', error_description: 'OAuth not supported' });
|
||||
res.json({
|
||||
resource: serverUrl,
|
||||
authorization_servers: [serverUrl],
|
||||
bearer_methods_supported: ['header'],
|
||||
});
|
||||
});
|
||||
|
||||
// OpenID Connect discovery (optional, return 404)
|
||||
app.get('/.well-known/openid-configuration', (_req, res) => {
|
||||
res.status(404).json({ error: 'not_found', error_description: 'OAuth not supported' });
|
||||
res.status(404).json({ error: 'not_supported' });
|
||||
});
|
||||
|
||||
app.post('/register', (_req, res) => {
|
||||
res.status(404).json({ error: 'not_found', error_description: 'OAuth not supported' });
|
||||
// Dynamic Client Registration (RFC 7591)
|
||||
// Auto-register any client that asks
|
||||
app.post('/register', (req, res) => {
|
||||
const clientId = `client_${Date.now()}`;
|
||||
res.status(201).json({
|
||||
client_id: clientId,
|
||||
client_secret: '', // Public client, no secret needed
|
||||
client_id_issued_at: Math.floor(Date.now() / 1000),
|
||||
grant_types: ['authorization_code'],
|
||||
response_types: ['code'],
|
||||
token_endpoint_auth_method: 'none',
|
||||
});
|
||||
});
|
||||
|
||||
// Authorization endpoint - return the API key as code
|
||||
app.get('/authorize', (req, res) => {
|
||||
const redirectUri = req.query.redirect_uri as string;
|
||||
const state = req.query.state as string;
|
||||
|
||||
if (!redirectUri) {
|
||||
res.status(400).json({ error: 'invalid_request', error_description: 'Missing redirect_uri' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Return the API key as the authorization code
|
||||
const code = API_KEY;
|
||||
const redirectUrl = new URL(redirectUri);
|
||||
redirectUrl.searchParams.set('code', code || 'no-api-key');
|
||||
if (state) redirectUrl.searchParams.set('state', state);
|
||||
|
||||
res.redirect(redirectUrl.toString());
|
||||
});
|
||||
|
||||
// Token endpoint - exchange code for access token
|
||||
app.post('/token', express.urlencoded({ extended: true }), (req, res) => {
|
||||
const code = req.body.code;
|
||||
|
||||
// Verify the code is our API key
|
||||
if (code !== API_KEY) {
|
||||
res.status(400).json({ error: 'invalid_grant', error_description: 'Invalid authorization code' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Return the API key as access token
|
||||
res.json({
|
||||
access_token: API_KEY,
|
||||
token_type: 'Bearer',
|
||||
expires_in: 86400 * 365, // 1 year
|
||||
});
|
||||
});
|
||||
|
||||
// SSE endpoint - establishes the SSE stream (protected)
|
||||
|
||||
Reference in New Issue
Block a user