🔑 Add OAuth token exchange endpoint with support for Discord and error handling

This commit is contained in:
2026-01-29 21:30:09 +00:00
parent 553f7b1aef
commit d62106abf5

View File

@@ -176,5 +176,108 @@ export function createAuthRouter({ secret, adminPassword } = {}) {
}); });
}); });
/**
* POST /oauth/token
* Exchange OAuth authorization code for access token
* Supports multiple providers (Discord, Challonge, etc.)
*/
router.post('/oauth/token', async (req, res) => {
const { code, provider } = req.body;
if (!code) {
return res.status(400).json({
error: 'Authorization code is required',
code: 'MISSING_CODE'
});
}
if (!provider) {
return res.status(400).json({
error: 'Provider is required',
code: 'MISSING_PROVIDER'
});
}
try {
// Handle Discord OAuth
if (provider === 'discord') {
const clientId = process.env.VITE_DISCORD_CLIENT_ID;
const clientSecret = process.env.DISCORD_CLIENT_SECRET;
const redirectUri = process.env.VITE_DISCORD_REDIRECT_URI;
if (!clientId || !clientSecret) {
console.error('Discord OAuth not configured:', {
hasClientId: !!clientId,
hasClientSecret: !!clientSecret
});
return res.status(500).json({
error: 'Discord OAuth not configured on server',
code: 'OAUTH_NOT_CONFIGURED'
});
}
// Exchange code for token with Discord
const tokenResponse = await fetch(
'https://discord.com/api/oauth2/token',
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
client_id: clientId,
client_secret: clientSecret,
grant_type: 'authorization_code',
code: code,
redirect_uri: redirectUri
})
}
);
if (!tokenResponse.ok) {
const errorData = await tokenResponse.text();
console.error('Discord token exchange failed:', errorData);
return res.status(tokenResponse.status).json({
error: 'Failed to exchange code with Discord',
code: 'DISCORD_TOKEN_EXCHANGE_FAILED',
details: errorData
});
}
const tokenData = await tokenResponse.json();
// Return tokens to client
return res.json({
access_token: tokenData.access_token,
refresh_token: tokenData.refresh_token,
token_type: tokenData.token_type,
expires_in: tokenData.expires_in,
scope: tokenData.scope
});
}
// Handle Challonge OAuth (if needed in the future)
if (provider === 'challonge') {
// Challonge uses the existing /api/oauth/token endpoint via oauth-proxy.js
return res.status(400).json({
error: 'Use /api/oauth/token for Challonge OAuth',
code: 'WRONG_ENDPOINT'
});
}
// Unknown provider
return res.status(400).json({
error: `Unknown provider: ${provider}`,
code: 'UNKNOWN_PROVIDER'
});
} catch (err) {
console.error('OAuth token exchange error:', err);
return res.status(500).json({
error: err.message || 'Failed to exchange OAuth code',
code: 'TOKEN_EXCHANGE_ERROR'
});
}
});
return router; return router;
} }