| import axios from 'axios'; |
| import crypto from 'crypto'; |
| import log from '../utils/logger.js'; |
| import config from '../config/config.js'; |
| import { generateProjectId } from '../utils/idGenerator.js'; |
| import tokenManager from './token_manager.js'; |
| import { OAUTH_CONFIG, OAUTH_SCOPES } from '../constants/oauth.js'; |
| import { buildAxiosRequestConfig } from '../utils/httpClient.js'; |
|
|
| class OAuthManager { |
| constructor() { |
| this.state = crypto.randomUUID(); |
| } |
|
|
| |
| |
| |
| generateAuthUrl(port) { |
| const params = new URLSearchParams({ |
| access_type: 'offline', |
| client_id: OAUTH_CONFIG.CLIENT_ID, |
| prompt: 'consent', |
| redirect_uri: `http://localhost:${port}/oauth-callback`, |
| response_type: 'code', |
| scope: OAUTH_SCOPES.join(' '), |
| state: this.state |
| }); |
| return `${OAUTH_CONFIG.AUTH_URL}?${params.toString()}`; |
| } |
|
|
| |
| |
| |
| async exchangeCodeForToken(code, port) { |
| const postData = new URLSearchParams({ |
| code, |
| client_id: OAUTH_CONFIG.CLIENT_ID, |
| client_secret: OAUTH_CONFIG.CLIENT_SECRET, |
| redirect_uri: `http://localhost:${port}/oauth-callback`, |
| grant_type: 'authorization_code' |
| }); |
| |
| const response = await axios(buildAxiosRequestConfig({ |
| method: 'POST', |
| url: OAUTH_CONFIG.TOKEN_URL, |
| headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, |
| data: postData.toString(), |
| timeout: config.timeout |
| })); |
| |
| return response.data; |
| } |
|
|
| |
| |
| |
| async fetchUserEmail(accessToken) { |
| try { |
| const response = await axios(buildAxiosRequestConfig({ |
| method: 'GET', |
| url: 'https://www.googleapis.com/oauth2/v2/userinfo', |
| headers: { |
| 'Host': 'www.googleapis.com', |
| 'User-Agent': 'Go-http-client/1.1', |
| 'Authorization': `Bearer ${accessToken}`, |
| 'Accept-Encoding': 'gzip' |
| }, |
| timeout: config.timeout |
| })); |
| return response.data?.email; |
| } catch (err) { |
| log.warn('获取用户邮箱失败:', err.message); |
| return null; |
| } |
| } |
|
|
| |
| |
| |
| async validateAndGetProjectId(accessToken) { |
| |
| if (config.skipProjectIdFetch) { |
| const projectId = generateProjectId(); |
| log.info('已跳过API验证,使用随机生成的projectId: ' + projectId); |
| return { projectId, hasQuota: true }; |
| } |
|
|
| |
| try { |
| log.info('正在验证账号资格...'); |
| const projectId = await tokenManager.fetchProjectId({ access_token: accessToken }); |
| |
| if (projectId === undefined) { |
| |
| const randomProjectId = generateProjectId(); |
| log.warn('该账号无资格使用,已自动退回无资格模式,使用随机projectId: ' + randomProjectId); |
| return { projectId: randomProjectId, hasQuota: false }; |
| } |
| |
| log.info('账号验证通过,projectId: ' + projectId); |
| return { projectId, hasQuota: true }; |
| } catch (err) { |
| |
| const randomProjectId = generateProjectId(); |
| log.warn('验证账号资格失败: ' + err.message + ',已自动退回无资格模式'); |
| log.info('使用随机生成的projectId: ' + randomProjectId); |
| return { projectId: randomProjectId, hasQuota: false }; |
| } |
| } |
|
|
| |
| |
| |
| async authenticate(code, port) { |
| |
| const tokenData = await this.exchangeCodeForToken(code, port); |
| |
| if (!tokenData.access_token) { |
| throw new Error('Token交换失败:未获取到access_token'); |
| } |
|
|
| const account = { |
| access_token: tokenData.access_token, |
| refresh_token: tokenData.refresh_token, |
| expires_in: tokenData.expires_in, |
| timestamp: Date.now() |
| }; |
|
|
| |
| const email = await this.fetchUserEmail(account.access_token); |
| if (email) { |
| account.email = email; |
| log.info('获取到用户邮箱: ' + email); |
| } |
|
|
| |
| const { projectId, hasQuota } = await this.validateAndGetProjectId(account.access_token); |
| account.projectId = projectId; |
| account.hasQuota = hasQuota; |
| account.enable = true; |
|
|
| return account; |
| } |
| } |
|
|
| export default new OAuthManager(); |
|
|