mirror of
https://github.com/Silentely/eSIM-Tools.git
synced 2026-09-03 06:24:20 +08:00
🐛 fix(auth): 收紧 BFF 鉴权并修复内部调用链路
This commit is contained in:
@@ -103,6 +103,31 @@ describe('APIService', () => {
|
||||
});
|
||||
|
||||
describe('错误处理', () => {
|
||||
|
||||
|
||||
it('应该为每次重试创建新的超时信号', async () => {
|
||||
const timeoutService = new APIService({ baseURL: 'https://api.test.com', timeout: 5000, retries: 2 });
|
||||
const firstSignal = { aborted: true };
|
||||
const secondSignal = { aborted: false };
|
||||
const createTimeoutSpy = jest.spyOn(timeoutService, 'createTimeoutSignal')
|
||||
.mockReturnValueOnce({ signal: firstSignal, clear: jest.fn() })
|
||||
.mockReturnValueOnce({ signal: secondSignal, clear: jest.fn() });
|
||||
|
||||
fetch.mockImplementationOnce(() => Promise.reject(new Error('timeout')));
|
||||
fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
headers: { get: () => 'application/json' },
|
||||
json: () => Promise.resolve({ ok: true })
|
||||
});
|
||||
|
||||
const result = await timeoutService.get('/retry-timeout');
|
||||
|
||||
expect(result).toEqual({ ok: true });
|
||||
expect(fetch).toHaveBeenCalledTimes(2);
|
||||
expect(createTimeoutSpy).toHaveBeenCalledTimes(2);
|
||||
expect(fetch.mock.calls[0][1].signal).toBe(firstSignal);
|
||||
expect(fetch.mock.calls[1][1].signal).toBe(secondSignal);
|
||||
});
|
||||
it('应该在 HTTP 错误时抛出异常', async () => {
|
||||
// mock fetch 返回 error response,但重试会多次调用
|
||||
fetch.mockResolvedValue({
|
||||
|
||||
@@ -3,6 +3,7 @@ describe('BFF proxy guardrails', () => {
|
||||
const OriginalRequest = global.Request;
|
||||
const OriginalResponse = global.Response;
|
||||
const OriginalDeno = global.Deno;
|
||||
const OriginalFetch = global.fetch;
|
||||
|
||||
class MockHeaders {
|
||||
constructor(init = {}) {
|
||||
@@ -92,6 +93,7 @@ describe('BFF proxy guardrails', () => {
|
||||
jest.restoreAllMocks();
|
||||
global.Request = OriginalRequest;
|
||||
global.Response = OriginalResponse;
|
||||
global.fetch = OriginalFetch;
|
||||
global.Deno = OriginalDeno;
|
||||
});
|
||||
|
||||
@@ -106,7 +108,21 @@ describe('BFF proxy guardrails', () => {
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
|
||||
it('blocks missing-origin calls to protected BFF targets', async () => {
|
||||
|
||||
|
||||
it('allows same-origin public GET requests without Origin', async () => {
|
||||
const mod = await import('../../netlify/edge-functions/bff-proxy.js');
|
||||
const request = new Request('https://example.com/bff/public-config', {
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
const response = await mod.default(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(global.fetch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('returns 403 for missing-origin protected POST requests', async () => {
|
||||
const mod = await import('../../netlify/edge-functions/bff-proxy.js');
|
||||
const request = new Request('https://example.com/bff/verify-cookie', {
|
||||
method: 'POST',
|
||||
@@ -115,7 +131,9 @@ describe('BFF proxy guardrails', () => {
|
||||
});
|
||||
|
||||
const response = await mod.default(request);
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
expect(response.headers.get('access-control-allow-origin')).toBe('https://esim.cosr.eu.org');
|
||||
});
|
||||
|
||||
it('forwards allowlisted BFF targets with the server x-esim-key', async () => {
|
||||
|
||||
@@ -49,6 +49,32 @@ describe('Functions authentication hardening', () => {
|
||||
expect(result.authorized).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
it('allows OPTIONS preflight for allowed origin', () => {
|
||||
const { middleware } = loadModules();
|
||||
|
||||
const result = middleware.authenticate({
|
||||
httpMethod: 'OPTIONS',
|
||||
headers: { origin: 'https://esim.cosr.eu.org' },
|
||||
body: '{}',
|
||||
queryStringParameters: {}
|
||||
});
|
||||
|
||||
expect(result.preflight).toBe(true);
|
||||
expect(result.origin).toBe('https://esim.cosr.eu.org');
|
||||
});
|
||||
|
||||
it('rejects OPTIONS preflight for disallowed origin', () => {
|
||||
const { middleware } = loadModules();
|
||||
|
||||
expect(() => middleware.authenticate({
|
||||
httpMethod: 'OPTIONS',
|
||||
headers: { origin: 'https://evil.example.com' },
|
||||
body: '{}',
|
||||
queryStringParameters: {}
|
||||
})).toThrow('Origin not allowed');
|
||||
});
|
||||
|
||||
it('does not accept authKey from query or body', () => {
|
||||
const { middleware } = loadModules();
|
||||
const event = {
|
||||
@@ -87,6 +113,27 @@ describe('Functions authentication hardening', () => {
|
||||
expect(form).not.toContain('attacker.example');
|
||||
});
|
||||
|
||||
|
||||
|
||||
it('throws a configuration error when ACCESS_KEY is missing for token exchange', async () => {
|
||||
const { tokenExchange } = loadModules({ ACCESS_KEY: '' });
|
||||
|
||||
const result = await tokenExchange.handler({
|
||||
httpMethod: 'POST',
|
||||
headers: {
|
||||
origin: 'https://esim.cosr.eu.org'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
code: 'authorization-code',
|
||||
code_verifier: 'a'.repeat(64)
|
||||
}),
|
||||
queryStringParameters: {}
|
||||
}, { functionName: 'giffgaff-token-exchange' });
|
||||
|
||||
expect(result.statusCode).toBe(500);
|
||||
expect(result.body).toContain('ACCESS_KEY');
|
||||
});
|
||||
|
||||
it('uses configured GIFFGAFF_REDIRECT_URI for token exchange', async () => {
|
||||
const { axios, tokenExchange } = loadModules({
|
||||
GIFFGAFF_REDIRECT_URI: 'giffgaff://custom/callback/'
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('Local server route coverage', () => {
|
||||
const app = require('../../server.js');
|
||||
const routes = app.locals.bffRoutes;
|
||||
|
||||
expect(routes).toEqual(expect.arrayContaining([
|
||||
const expectedRoutes = [
|
||||
'/bff/giffgaff-token-exchange',
|
||||
'/bff/giffgaff-graphql',
|
||||
'/bff/giffgaff-mfa-challenge',
|
||||
@@ -33,6 +33,8 @@ describe('Local server route coverage', () => {
|
||||
'/bff/auto-activate-esim',
|
||||
'/bff/verify-cookie',
|
||||
'/bff/public-config'
|
||||
]));
|
||||
];
|
||||
|
||||
expect([...routes].sort()).toEqual([...expectedRoutes].sort());
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user