fix: keep a sign-in code from following a redirect to another host

This commit is contained in:
Linus Rath
2026-10-29 03:44:57 +01:00
parent 16bbb97ed2
commit 61654d7c40
2 changed files with 14 additions and 2 deletions
+11 -1
View File
@@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as WebBrowser from 'expo-web-browser';
import { setManualFetchForTests } from '../client-cert';
import { HandoffError, TransientRefreshError, refreshOAuthAccessToken, resetRefreshRotations } from '../oauth';
import { HandoffError, PairingError, TransientRefreshError, redeemPairingCode, refreshOAuthAccessToken, resetRefreshRotations } from '../oauth';
import { discoverOAuthMetadata, loginWithPkce, revokeRefreshToken, type OAuthMetadata } from '../oauth-native';
// The refresh token, the code + verifier and the metadata that decides where
@@ -149,3 +149,13 @@ describe('metadata discovery', () => {
expect((await discoverOAuthMetadata(SERVER))?.token_endpoint).toBe(`${IDP}/token`);
});
});
describe('pairing', () => {
it('does not take a sign-in code along a redirect to another host', async () => {
routes[`${SERVER}/api/auth/pair/redeem`] = () => redirect(`${EVIL}/api/auth/pair/redeem`);
await expect(redeemPairingCode(SERVER, 'ABCD-EFGH')).rejects.toBeInstanceOf(PairingError);
expect(evilHits()).toEqual([]);
});
});
+3 -1
View File
@@ -459,7 +459,9 @@ export async function redeemPairingCode(webmailUrl: string, code: string): Promi
const exchange = (async () => {
let response: Response;
try {
response = await secureFetch(`${base}/api/auth/pair/redeem`, {
// The code is a one-time credential: never let a redirect carry it
// to another host.
response = await fetchWithoutForeignRedirect(`${base}/api/auth/pair/redeem`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({ pairing_code: code }),