diff --git a/code/websites/pokedex.online/server/routes/auth.js b/code/websites/pokedex.online/server/routes/auth.js index 6028daa..a0e1e89 100644 --- a/code/websites/pokedex.online/server/routes/auth.js +++ b/code/websites/pokedex.online/server/routes/auth.js @@ -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; }