Files
2fauth-worker/backend/src/features/auth/providers/githubProvider.ts
T

98 lines
3.6 KiB
TypeScript

import { BaseOAuthProvider, OAuthUserInfo } from '@/features/auth/providers/baseOAuthProvider';
import { EnvBindings, AppError } from '@/app/config';
export class GitHubProvider extends BaseOAuthProvider {
readonly id = 'github';
readonly name = 'GitHub';
readonly color = '#24292e';
readonly icon = 'iconGithub';
readonly whitelistFields = ['email'];
constructor(env: EnvBindings) {
super(env);
}
getAuthorizeUrl(state: string) {
const clientId = this.env.OAUTH_GITHUB_CLIENT_ID;
const redirectUri = this.env.OAUTH_GITHUB_REDIRECT_URI;
const params = new URLSearchParams({
client_id: clientId,
redirect_uri: redirectUri,
state: state,
scope: 'read:user user:email' // 显式请求 Email 权限
});
return { url: `https://github.com/login/oauth/authorize?${params.toString()}` };
}
async handleCallback(params: string | URLSearchParams, _codeVerifier?: string): Promise<OAuthUserInfo> {
// 兼容处理:如果是 URLSearchParams,提取 code;如果是 string,直接使用
const code = typeof params === 'string' ? params : params.get('code');
if (!code) {
throw new AppError('GitHub OAuth callback missing code', 400);
}
// 1. 使用 Code 换取 Access Token
const tokenResponse = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json',
'User-Agent': '2FA-Manager-Backend/1.0'
},
body: new URLSearchParams({
client_id: this.env.OAUTH_GITHUB_CLIENT_ID,
client_secret: this.env.OAUTH_GITHUB_CLIENT_SECRET,
code: code,
redirect_uri: this.env.OAUTH_GITHUB_REDIRECT_URI,
})
});
if (!tokenResponse.ok) {
throw new AppError(`GitHub Token Exchange failed: ${tokenResponse.status}`, 502);
}
const tokenData: any = await tokenResponse.json();
if (tokenData.error) {
throw new AppError(`GitHub OAuth Error: ${tokenData.error_description || tokenData.error}`, 400);
}
const accessToken = tokenData.access_token;
// 2. 获取用户信息
const userResponse = await fetch('https://api.github.com/user', {
headers: {
'Authorization': `token ${accessToken}`,
'Accept': 'application/json',
'User-Agent': '2FA-Manager-Backend/1.0'
}
});
if (!userResponse.ok) throw new AppError(`GitHub API error: ${userResponse.status}`, 502);
const userData: any = await userResponse.json();
// 3. 补充获取 Email (如果公开资料中没有)
let email = userData.email;
if (!email) {
try {
const emailRes = await fetch('https://api.github.com/user/emails', {
headers: { 'Authorization': `token ${accessToken}`, 'User-Agent': '2FA-Manager-Backend/1.0' }
});
const emails: any[] = await emailRes.json();
email = emails.find(e => e.primary && e.verified)?.email || emails.find(e => e.verified)?.email;
} catch (e) { /* ignore */ }
}
// 4. 映射到标准结构
return {
id: String(userData.id),
username: userData.login || userData.username,
email: email || '',
avatar: userData.avatar_url,
provider: this.id
};
}
}