import { Request, Response } from 'express'; import { OAuth2Client, OAuth2AuthorizationCode, OAuth2AccessToken } from '../models/oauth2'; import { User } from '../models/User'; import { generateRandomString } from '../utils/crypto'; import { Session } from 'express-session'; import fetch from 'node-fetch'; import jwt from 'jsonwebtoken'; import { config } from '../config'; // JWT Payload 类型定义 interface UserPayload { _id: string; username: string; fullname: string; email: string; isAdmin: boolean; } // 添加 session 接口声明 interface CustomSession extends Session { userId?: string; isAuthenticated?: boolean; } // 扩展 Request 类型 interface CustomRequest extends Request { session: CustomSession; } export class OAuth2Controller { // 授权端点 async authorize(req: CustomRequest, res: Response) { try { // 调试日志:输出请求信息 console.log('--- OAuth2 authorize 调试信息 ---'); console.log('req.headers:', req.headers); console.log('req.originalUrl:', req.originalUrl); const { client_id, redirect_uri, scope, response_type, state } = req.query; // 统一使用 JWT 认证,从多个来源获取 token let token: string | undefined; let currentUser: UserPayload | null = null; // 1. 检查 Authorization 头 const authHeader = req.headers.authorization; if (authHeader && authHeader.startsWith('Bearer ')) { token = authHeader.substring(7); } // 2. 检查 cookie(如果存在) if (!token && req.headers.cookie) { const cookies: Record = {}; req.headers.cookie.split(';').forEach(cookie => { const parts = cookie.trim().split('='); if (parts.length === 2) { cookies[parts[0]] = parts[1]; } }); // 尝试从 cookie 中获取 token if (cookies.token) { token = cookies.token; } } // 3. 验证 token 并获取用户信息 if (token) { try { const decoded = jwt.verify(token, config.JWT_SECRET); if (typeof decoded === 'object' && decoded !== null && '_id' in decoded) { // 伪造session,让后续逻辑认为已登录 req.session.userId = decoded._id as string; req.session.isAuthenticated = true; currentUser = decoded as UserPayload; console.log('JWT验证成功,用户信息:', { userId: currentUser._id, username: currentUser.username }); } } catch (error) { console.log('JWT验证失败:', error); } } // 如果用户未登录,重定向到登录页面 if (!currentUser) { console.log('用户未登录,重定向到登录页'); const redirectPath = '/api' + req.originalUrl; const loginUrl = `/login?redirect=${encodeURIComponent(redirectPath)}`; return res.redirect(loginUrl); } // 用户已登录,继续OAuth2授权流程 console.log('用户已登录,继续OAuth2授权流程'); // 从数据库中获取完整用户信息 const user = await User.findById(currentUser._id); if (!user) { console.log('数据库中未找到用户:', currentUser._id); return res.status(401).json({ error: 'user_not_found' }); } // 验证客户端 const client = await OAuth2Client.findOne({ clientId: client_id }); if (!client) { return res.status(400).json({ error: 'invalid_client' }); } console.log('OAuth2 authorize start:', { user: user.username, clientId: client_id }); // 查找现有的用户关联 const existingLink = await OAuth2Client.findOne({ clientId: client_id, 'linkedUsers.username': user.username }); console.log('OAuth2 link check:', { username: user.username, exists: !!existingLink, linkDetails: existingLink }); // 只在没有现有关联时创建新的关联 if (!existingLink) { console.log('Creating new OAuth link'); await OAuth2Client.updateOne( { clientId: client_id }, { $addToSet: { linkedUsers: { userId: user._id, username: user.username, email: user.email } } } ); } else { console.log('Using existing OAuth link - proceeding with authorization'); } // 生成授权码 const code = generateRandomString(32); const authCode = new OAuth2AuthorizationCode({ code, clientId: client_id, userId: user._id, redirectUri: redirect_uri, scope: (typeof scope === 'string' ? scope.split(' ') : []) || [], expiresAt: new Date(Date.now() + 10 * 60 * 1000) }); await authCode.save(); console.log('Generated auth code:', { code, userId: user._id }); // 重定向回客户端 if (!redirect_uri || typeof redirect_uri !== 'string') { return res.status(400).json({ error: 'invalid_redirect_uri' }); } const redirectUrl = new URL(redirect_uri); redirectUrl.searchParams.set('code', code); redirectUrl.searchParams.set('state', state?.toString() || ''); console.log('Redirecting to:', redirectUrl.toString()); return res.redirect(redirectUrl.toString()); } catch (error: any) { console.error('OAuth2 authorize error:', error); throw error; } } // 令牌端点 async token(req: Request, res: Response) { try { console.log('=== OAuth2 Token Debug Info ==='); console.log('Token request body:', req.body); const { grant_type, code, client_id, client_secret, redirect_uri } = req.body; // 验证客户端 const client = await OAuth2Client.findOne({ clientId: client_id }); if (!client) { console.log('Client not found:', client_id); return res.status(401).json({ error: 'invalid_client' }); } if (grant_type === 'authorization_code') { // 查找授权码 const authCode = await OAuth2AuthorizationCode.findOne({ code }); console.log('Found auth code:', authCode); if (!authCode || authCode.expiresAt < new Date()) { console.log('Invalid or expired auth code'); return res.status(400).json({ error: 'invalid_grant' }); } // 查找用户 const user = await User.findById(authCode.userId); // 生成访问令牌 const accessToken = generateRandomString(32); const token = new OAuth2AccessToken({ accessToken, clientId: client_id, userId: authCode.userId, scope: authCode.scope, expiresAt: new Date(Date.now() + 60 * 60 * 1000) }); await token.save(); console.log('Generated access token:', accessToken); // 生成 id_token(只要 scope 里有 openid) let id_token; if (authCode.scope.includes('openid')) { const jwt = require('jsonwebtoken'); id_token = jwt.sign( { sub: user._id.toString(), name: user.username, fullname: user.fullname, email: user.email, // 可根据需要添加其它 claim }, process.env.OPENID_SESSION_SECRET || 'your-secret-keyq', // 用于签名的密钥 { algorithm: 'HS256', expiresIn: '1h', issuer: process.env.OPENID_ISSUER || 'https://d1kt.cn', audience: client_id, } ); } // 返回令牌响应 return res.json({ access_token: accessToken, token_type: 'Bearer', expires_in: 3600, scope: authCode.scope.join(' '), ...(id_token ? { id_token } : {}), }); } return res.status(400).json({ error: 'unsupported_grant_type' }); } catch (error: any) { console.error('OAuth2 Token Error:', { message: error.message, stack: error.stack, body: req.body }); return res.status(500).json({ error: 'server_error', error_description: error.message }); } } // 用户信息端点 async userinfo(req: Request, res: Response) { const authHeader = req.headers.authorization; if (!authHeader?.startsWith('Bearer ')) { return res.status(401).json({ error: 'invalid_token' }); } const accessToken = authHeader.substring(7); try { const token = await OAuth2AccessToken.findOne({ accessToken, expiresAt: { $gt: new Date() } }); if (!token) { return res.status(401).json({ error: 'invalid_token' }); } // 查找已关联的用户 const linkedUser = await OAuth2Client.findOne({ clientId: token.clientId, 'linkedUsers.userId': token.userId }); const user = await User.findById(token.userId); if (!user) { return res.status(401).json({ error: 'user_not_found' }); } // 根据scope返回用户信息 const userInfo: any = {}; if (token.scope.includes('openid')) { userInfo.sub = user._id; } if (token.scope.includes('profile')) { userInfo.name = user.username; userInfo.fullname = user.fullname; } if (token.scope.includes('email')) { userInfo.email = user.email; } if (token.scope.includes('firstname')) { userInfo.firstname = user.username; } if (token.scope.includes('lastname')) { userInfo.lastname = user.fullname; } if (token.scope.includes('username')) { userInfo.username = user.username; } console.log('user', user); console.log('userInfo', userInfo); console.log('Token scopes:', token.scope); console.log('最终返回的 userInfo:', userInfo); res.json(userInfo); } catch (error) { res.status(500).json({ error: 'server_error6' }); } } async getMoodleSesskey(req: Request, res: Response) { console.log('Received request for moodle-sesskey'); // 请求开始日志 try { console.log('Fetching Moodle login page...'); const response = await fetch('https://m.d1kt.cn/login/index.php'); const html = await response.text(); console.log('Moodle response received, length:', html.length); // 检查是否获取到响应 const sesskeyMatch = html.match(/sesskey":"([^"]+)/); console.log('Sesskey match result:', sesskeyMatch); // 检查正则匹配结果 const sesskey = sesskeyMatch ? sesskeyMatch[1] : ''; if (!sesskey) { console.log('No sesskey found in response'); // 未找到 sesskey return res.status(500).json({ error: 'Failed to get sesskey' }); } console.log('Successfully got sesskey:', sesskey); // 成功获取 sesskey res.json({ sesskey }); } catch (error) { console.error('Get sesskey error:', error); // 详细的错误信息 res.status(500).json({ error: 'Failed to get sesskey' }); } } }