const express = require('express'); const { body, validationResult } = require('express-validator'); const OAuthClient = require('../models/OAuthClient'); const OAuthToken = require('../models/OAuthToken'); const User = require('../models/User'); const { authenticateToken } = require('../middleware/auth'); const { authenticateOAuthToken, requireScope } = require('../middleware/oauth'); const { oauthAuthLimiter, oauthTokenLimiter } = require('../middleware/rateLimit'); const router = express.Router(); // 验证重定向URI的通用函数 const isValidRedirectUri = (storedUris, requestedUri) => { try { const requestedUrl = new URL(requestedUri); for (const storedUri of storedUris) { const storedUrl = new URL(storedUri); // 检查协议是否匹配 if (requestedUrl.protocol !== storedUrl.protocol) { continue; } // 获取文件名(路径的最后一部分) const requestedFilename = requestedUrl.pathname.split('/').pop(); const storedFilename = storedUrl.pathname.split('/').pop(); // 检查文件名是否匹配,忽略路径前缀 if (requestedFilename === storedFilename) { return true; } // 如果文件名不匹配,检查完整路径是否匹配 if (requestedUrl.pathname === storedUrl.pathname) { return true; } } return false; } catch (error) { return false; } }; // 验证中间件 const handleValidationErrors = (req, res, next) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ success: false, message: '输入验证失败', errors: errors.array().map(error => ({ field: error.path, message: error.msg })) }); } next(); }; // 1. 授权端点 - 验证参数并返回授权信息 router.get('/authorize', oauthAuthLimiter, authenticateToken, async (req, res) => { try { const { response_type, client_id, redirect_uri, scope, state } = req.query; // 验证必需参数 if (!response_type || !client_id || !redirect_uri) { return res.status(400).json({ success: false, message: '缺少必需参数' }); } // 验证response_type if (response_type !== 'code') { return res.status(400).json({ success: false, message: '不支持的response_type' }); } // 验证客户端 const client = await OAuthClient.findByClientId(client_id); if (!client) { return res.status(400).json({ success: false, message: '无效的客户端ID' }); } // 验证重定向URI if (!isValidRedirectUri(client.redirect_uris, redirect_uri)) { return res.status(400).json({ success: false, message: '无效的重定向URI', debug: { requested: redirect_uri, allowed: client.redirect_uris } }); } // 获取用户信息(通过JWT中间件已经验证) const user = await User.findByUsername(req.user.username); if (!user) { return res.status(401).json({ success: false, message: '用户不存在' }); } // 返回授权信息,让前端显示授权页面 const scopes = scope ? scope.split(' ') : ['read', 'write']; res.json({ success: true, message: '授权信息获取成功', data: { client: { id: client.client_id, name: client.client_name, description: client.description }, scopes: scopes, state: state, redirect_uri: redirect_uri } }); } catch (error) { console.error('授权信息获取失败:', error); res.status(500).json({ success: false, message: '服务器内部错误' }); } }); // 2. 用户同意/拒绝授权端点 router.post('/authorize/consent', oauthAuthLimiter, authenticateToken, async (req, res) => { try { const { client_id, redirect_uri, scope, state, approved } = req.body; // 验证必需参数 if (!client_id || !redirect_uri || approved === undefined) { return res.status(400).json({ success: false, message: '缺少必需参数' }); } // 验证客户端 const client = await OAuthClient.findByClientId(client_id); if (!client) { return res.status(400).json({ success: false, message: '无效的客户端ID' }); } // 验证重定向URI if (!isValidRedirectUri(client.redirect_uris, redirect_uri)) { return res.status(400).json({ success: false, message: '无效的重定向URI', debug: { requested: redirect_uri, allowed: client.redirect_uris } }); } // 获取用户信息 const user = await User.findByUsername(req.user.username); if (!user) { return res.status(401).json({ success: false, message: '用户不存在' }); } if (!approved) { // 用户拒绝授权 const errorUrl = new URL(redirect_uri); errorUrl.searchParams.set('error', 'access_denied'); errorUrl.searchParams.set('error_description', '用户拒绝授权'); if (state) { errorUrl.searchParams.set('state', state); } return res.json({ success: true, message: '用户拒绝授权', data: { redirect_url: errorUrl.toString() } }); } // 用户同意授权,生成授权码 const authCode = OAuthToken.generateAuthCode(); const scopes = scope ? scope.split(' ') : ['read', 'write']; await OAuthToken.createAuthCode({ code: authCode, clientId: client_id, userId: user.id, redirectUri: redirect_uri, scopes: scopes }); // 构建重定向URL const redirectUrl = new URL(redirect_uri); redirectUrl.searchParams.set('code', authCode); if (state) { redirectUrl.searchParams.set('state', state); } res.json({ success: true, message: '授权成功', data: { redirect_url: redirectUrl.toString(), code: authCode, state: state } }); } catch (error) { console.error('授权处理失败:', error); res.status(500).json({ success: false, message: '服务器内部错误' }); } }); // 3. 令牌端点 - 交换授权码获取访问令牌 router.post('/token', oauthTokenLimiter, [ body('grant_type').notEmpty().withMessage('grant_type不能为空'), body('client_id').notEmpty().withMessage('client_id不能为空'), body('client_secret').notEmpty().withMessage('client_secret不能为空'), handleValidationErrors ], async (req, res) => { try { const { grant_type, client_id, client_secret, code, redirect_uri, refresh_token, code_verifier // PKCE支持 } = req.body; // 验证客户端 const isValidClient = await OAuthClient.validateClient(client_id, client_secret); if (!isValidClient) { return res.status(401).json({ success: false, message: '无效的客户端凭据' }); } if (grant_type === 'authorization_code') { // 授权码流程 if (!code || !redirect_uri) { return res.status(400).json({ success: false, message: '缺少code或redirect_uri' }); } // 验证授权码 const authCode = await OAuthToken.validateAuthCode(code, client_id, redirect_uri); if (!authCode) { return res.status(400).json({ success: false, message: '无效的授权码' }); } // 验证重定向URI const isValidRedirectUri = await OAuthClient.validateRedirectUri(client_id, redirect_uri); if (!isValidRedirectUri) { return res.status(400).json({ success: false, message: '无效的重定向URI' }); } // 生成访问令牌和刷新令牌 const accessToken = OAuthToken.generateAccessToken(); const refreshToken = OAuthToken.generateRefreshToken(); // 创建访问令牌 const accessTokenData = await OAuthToken.createAccessToken({ token: accessToken, clientId: client_id, userId: authCode.user_id, scopes: authCode.scopes }); // 创建刷新令牌 await OAuthToken.createRefreshToken({ token: refreshToken, accessTokenId: accessTokenData.id, clientId: client_id, userId: authCode.user_id }); // 删除已使用的授权码 await OAuthToken.deleteAuthCode(code); res.json({ success: true, data: { access_token: accessToken, token_type: 'Bearer', expires_in: 3600, // 1小时 refresh_token: refreshToken, scope: authCode.scopes.join(' ') } }); } else if (grant_type === 'refresh_token') { // 刷新令牌流程 if (!refresh_token) { return res.status(400).json({ success: false, message: '缺少refresh_token' }); } // 验证刷新令牌 const refreshTokenData = await OAuthToken.validateRefreshToken(refresh_token, client_id); if (!refreshTokenData) { return res.status(400).json({ success: false, message: '无效的刷新令牌' }); } // 生成新的访问令牌 const newAccessToken = OAuthToken.generateAccessToken(); const newRefreshToken = OAuthToken.generateRefreshToken(); // 创建新的访问令牌 const newAccessTokenData = await OAuthToken.createAccessToken({ token: newAccessToken, clientId: client_id, userId: refreshTokenData.user_id, scopes: refreshTokenData.scopes }); // 创建新的刷新令牌 await OAuthToken.createRefreshToken({ token: newRefreshToken, accessTokenId: newAccessTokenData.id, clientId: client_id, userId: refreshTokenData.user_id }); // 撤销旧的刷新令牌 await OAuthToken.revokeRefreshToken(refresh_token); res.json({ success: true, data: { access_token: newAccessToken, token_type: 'Bearer', expires_in: 3600, refresh_token: newRefreshToken, scope: refreshTokenData.scopes.join(' ') } }); } else { return res.status(400).json({ success: false, message: '不支持的授权类型' }); } } catch (error) { console.error('令牌交换失败:', error); res.status(500).json({ success: false, message: '服务器内部错误' }); } }); // 4. 撤销令牌端点 router.post('/revoke', [ body('token').notEmpty().withMessage('token不能为空'), body('client_id').notEmpty().withMessage('client_id不能为空'), body('client_secret').notEmpty().withMessage('client_secret不能为空'), handleValidationErrors ], async (req, res) => { try { const { token, client_id, client_secret } = req.body; // 验证客户端 const isValidClient = await OAuthClient.validateClient(client_id, client_secret); if (!isValidClient) { return res.status(401).json({ success: false, message: '无效的客户端凭据' }); } // 撤销令牌 const revoked = await OAuthToken.revokeAccessToken(token); if (!revoked) { await OAuthToken.revokeRefreshToken(token); } res.json({ success: true, message: '令牌已撤销' }); } catch (error) { console.error('撤销令牌失败:', error); res.status(500).json({ success: false, message: '服务器内部错误' }); } }); // 5. 用户信息端点 router.get('/userinfo', authenticateOAuthToken, requireScope('read'), async (req, res) => { try { res.json({ success: true, data: { user_id: req.oauth.userId, username: req.oauth.username, email: req.oauth.email, scopes: req.oauth.scopes } }); } catch (error) { console.error('获取用户信息失败:', error); res.status(500).json({ success: false, message: '服务器内部错误' }); } }); // 6. 令牌信息端点 router.get('/tokeninfo', async (req, res) => { try { const { access_token } = req.query; if (!access_token) { return res.status(400).json({ success: false, message: '缺少access_token参数' }); } const tokenData = await OAuthToken.validateAccessToken(access_token); if (!tokenData) { return res.status(400).json({ success: false, message: '无效的访问令牌' }); } res.json({ success: true, data: { user_id: tokenData.user_id, client_id: tokenData.client_id, scopes: tokenData.scopes, expires_in: Math.floor((new Date(tokenData.expires_at) - new Date()) / 1000) } }); } catch (error) { console.error('获取令牌信息失败:', error); res.status(500).json({ success: false, message: '服务器内部错误' }); } }); module.exports = router;