mirror of
https://github.com/Silentely/eSIM-Tools.git
synced 2026-09-03 06:24:20 +08:00
feat(giffgaff): 支持 PKCE 公有客户端和机密客户端的 OAuth 令牌交换
- 新增 giffgaff-token-exchange.js 函数处理 OAuth 令牌交换 - 增加对 PKCE 公有客户端和机密客户端的支持 - 优化令牌交换流程,支持可选的 Basic 认证 - 更新前端和后端的 OAuth 配置
This commit is contained in:
@@ -7,9 +7,8 @@
|
||||
const axios = require('axios');
|
||||
|
||||
exports.handler = async (event) => {
|
||||
const allowedOrigin = 'https://esim.cosr.eu.org';
|
||||
const headers = {
|
||||
'Access-Control-Allow-Origin': allowedOrigin,
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
||||
'Content-Type': 'application/json'
|
||||
@@ -19,11 +18,6 @@ exports.handler = async (event) => {
|
||||
return { statusCode: 200, headers, body: '' };
|
||||
}
|
||||
|
||||
const reqOrigin = (event.headers && (event.headers.origin || event.headers.Origin)) || '';
|
||||
if (reqOrigin && reqOrigin !== allowedOrigin) {
|
||||
return { statusCode: 403, headers, body: JSON.stringify({ error: 'Forbidden', message: 'Origin not allowed' }) };
|
||||
}
|
||||
|
||||
if (event.httpMethod !== 'POST') {
|
||||
return {
|
||||
statusCode: 405,
|
||||
@@ -88,6 +82,4 @@ exports.handler = async (event) => {
|
||||
body: JSON.stringify({ error: 'Token Exchange Failed', details: data })
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
@@ -39,6 +39,7 @@ app.use(express.static('.'));
|
||||
const giffgaffMfaChallenge = require('./netlify/functions/giffgaff-mfa-challenge');
|
||||
const giffgaffMfaValidation = require('./netlify/functions/giffgaff-mfa-validation');
|
||||
const giffgaffGraphql = require('./netlify/functions/giffgaff-graphql');
|
||||
const giffgaffTokenExchange = require('./netlify/functions/giffgaff-token-exchange');
|
||||
const verifyCookie = require('./netlify/functions/verify-cookie');
|
||||
|
||||
// 包装Netlify Functions为Express路由
|
||||
@@ -83,6 +84,7 @@ function wrapNetlifyFunction(handler) {
|
||||
app.use('/.netlify/functions/giffgaff-mfa-challenge', wrapNetlifyFunction(giffgaffMfaChallenge));
|
||||
app.use('/.netlify/functions/giffgaff-mfa-validation', wrapNetlifyFunction(giffgaffMfaValidation));
|
||||
app.use('/.netlify/functions/giffgaff-graphql', wrapNetlifyFunction(giffgaffGraphql));
|
||||
app.use('/.netlify/functions/giffgaff-token-exchange', wrapNetlifyFunction(giffgaffTokenExchange));
|
||||
app.use('/.netlify/functions/verify-cookie', wrapNetlifyFunction(verifyCookie));
|
||||
|
||||
// Simyo API代理路由
|
||||
|
||||
@@ -1243,10 +1243,12 @@
|
||||
const oauthConfig = {
|
||||
clientId: "4a05bf219b3985647d9b9a3ba610a9ce",
|
||||
redirectUri: "giffgaff://auth/callback/",
|
||||
authUrl: "https://id.giffgaff.com/auth/oauth/authorize",
|
||||
authUrl: "https://id.giffgaff.com/oauth/authorize",
|
||||
// 前端不再保存/使用 clientSecret;令牌交换改由后端函数完成
|
||||
tokenUrl: "/.netlify/functions/giffgaff-token-exchange",
|
||||
scope: "read"
|
||||
scope: "read",
|
||||
// 可选:显式要求后端使用 Basic(仅在你使用机密客户端时打开)
|
||||
// useBasic: true
|
||||
};
|
||||
|
||||
// Giffgaff API端点
|
||||
@@ -1885,6 +1887,13 @@
|
||||
appState.codeVerifier = generateCodeVerifier();
|
||||
const codeChallenge = await generateCodeChallenge(appState.codeVerifier);
|
||||
const state = generateState();
|
||||
// 临时持久化(使用 state->verifier 映射,避免多次点击导致覆盖)
|
||||
try {
|
||||
const pkceMap = JSON.parse(sessionStorage.getItem('gg_pkce_map') || '{}');
|
||||
pkceMap[state] = appState.codeVerifier;
|
||||
sessionStorage.setItem('gg_pkce_map', JSON.stringify(pkceMap));
|
||||
sessionStorage.setItem('gg_oauth_last_state', state);
|
||||
} catch (e) {}
|
||||
|
||||
// 构建授权URL
|
||||
const authParams = new URLSearchParams({
|
||||
@@ -1953,6 +1962,22 @@
|
||||
console.log('解析到的授权码:', code);
|
||||
console.log('解析到的状态:', state);
|
||||
|
||||
// 如果内存中的 codeVerifier 丢失,优先通过 state 在映射中恢复
|
||||
if (!appState.codeVerifier) {
|
||||
try {
|
||||
const pkceMap = JSON.parse(sessionStorage.getItem('gg_pkce_map') || '{}');
|
||||
const byState = state && pkceMap[state];
|
||||
if (byState && byState.length >= 43) appState.codeVerifier = byState;
|
||||
if (!appState.codeVerifier) {
|
||||
const savedVerifier = sessionStorage.getItem('gg_code_verifier');
|
||||
if (savedVerifier && savedVerifier.length >= 43) appState.codeVerifier = savedVerifier;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
if (!appState.codeVerifier) {
|
||||
throw new Error('会话已重置或过期:缺少 code_verifier,请重新点击“开始OAuth登录”');
|
||||
}
|
||||
|
||||
// 交换访问令牌
|
||||
const tokenResponse = await fetch(oauthConfig.tokenUrl, {
|
||||
method: 'POST',
|
||||
@@ -1960,7 +1985,9 @@
|
||||
body: JSON.stringify({
|
||||
code: code,
|
||||
code_verifier: appState.codeVerifier,
|
||||
redirect_uri: oauthConfig.redirectUri
|
||||
redirect_uri: oauthConfig.redirectUri,
|
||||
client_id: oauthConfig.clientId,
|
||||
...(typeof oauthConfig.useBasic !== 'undefined' ? { use_basic: oauthConfig.useBasic } : {})
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1970,6 +1997,11 @@
|
||||
|
||||
const tokenData = await tokenResponse.json();
|
||||
appState.accessToken = tokenData.access_token;
|
||||
try {
|
||||
const pkceMap = JSON.parse(sessionStorage.getItem('gg_pkce_map') || '{}');
|
||||
if (state && pkceMap[state]) { delete pkceMap[state]; }
|
||||
sessionStorage.setItem('gg_pkce_map', JSON.stringify(pkceMap));
|
||||
} catch (e) {}
|
||||
|
||||
showStatus(elements.callbackStatus, "OAuth登录成功!", "success");
|
||||
updateStatus(); // 更新状态显示
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('Giffgaff OAuthManager', () => {
|
||||
const verifier = 'test-verifier';
|
||||
const url = await OAuthManager.buildAuthorizationUrl(verifier);
|
||||
|
||||
expect(url).toContain('https://id.giffgaff.com/auth/oauth/authorize');
|
||||
expect(url).toContain('https://id.giffgaff.com/oauth/authorize');
|
||||
expect(url).toContain('response_type=code');
|
||||
expect(url).toContain('client_id=');
|
||||
expect(url).toContain('redirect_uri=');
|
||||
|
||||
Reference in New Issue
Block a user