fix: harden proxy auth and SSRF defenses
This commit is contained in:
@@ -87,6 +87,27 @@ function getClientRateLimitState(client: IJMAPClient | null): Pick<AuthState, 'i
|
||||
};
|
||||
}
|
||||
|
||||
async function syncStalwartAuthContext(
|
||||
serverUrl: string,
|
||||
username: string,
|
||||
authHeader: string,
|
||||
slot: number,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const response = await fetch('/api/auth/stalwart-context', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ serverUrl, username, authHeader, slot }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
debug.warn('auth', `Failed to sync Stalwart auth context: ${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
debug.warn('auth', 'Failed to sync Stalwart auth context:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function bindClientStatusHandlers(
|
||||
client: IJMAPClient,
|
||||
set: (state: Partial<AuthState>) => void,
|
||||
@@ -461,6 +482,8 @@ export const useAuthStore = create<AuthState>()(
|
||||
}
|
||||
}
|
||||
|
||||
await syncStalwartAuthContext(serverUrl, username, client.getAuthHeader(), cookieSlot);
|
||||
|
||||
set({
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
@@ -636,6 +659,8 @@ export const useAuthStore = create<AuthState>()(
|
||||
});
|
||||
accountStore.setActiveAccount(accountId);
|
||||
|
||||
await syncStalwartAuthContext(serverUrl, username, client.getAuthHeader(), slot);
|
||||
|
||||
set({
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
@@ -754,6 +779,9 @@ export const useAuthStore = create<AuthState>()(
|
||||
});
|
||||
accountStore.setActiveAccount(accountId);
|
||||
|
||||
const cookieSlot = accountStore.getAccountById(accountId)?.cookieSlot ?? 0;
|
||||
await syncStalwartAuthContext(ssoServerUrl, username, client.getAuthHeader(), cookieSlot);
|
||||
|
||||
set({
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
@@ -825,6 +853,15 @@ export const useAuthStore = create<AuthState>()(
|
||||
|
||||
get().client?.updateAccessToken(access_token);
|
||||
|
||||
if (account) {
|
||||
await syncStalwartAuthContext(
|
||||
account.serverUrl,
|
||||
account.username,
|
||||
`Bearer ${access_token}`,
|
||||
slot,
|
||||
);
|
||||
}
|
||||
|
||||
set({
|
||||
accessToken: access_token,
|
||||
tokenExpiresAt: Date.now() + expires_in * 1000,
|
||||
@@ -1012,6 +1049,12 @@ export const useAuthStore = create<AuthState>()(
|
||||
await targetClient.connect();
|
||||
clients.set(accountId, targetClient);
|
||||
scheduleRefresh(expires_in, get().refreshAccessToken, accountId);
|
||||
await syncStalwartAuthContext(
|
||||
targetAccount.serverUrl,
|
||||
targetAccount.username,
|
||||
targetClient.getAuthHeader(),
|
||||
targetAccount.cookieSlot,
|
||||
);
|
||||
}
|
||||
} else if (targetAccount.authMode === 'basic' && targetAccount.rememberMe) {
|
||||
const res = await fetch(`/api/auth/session?slot=${targetAccount.cookieSlot}`, { method: 'PUT' });
|
||||
@@ -1021,6 +1064,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
bindClientStatusHandlers(targetClient, set, get, accountId);
|
||||
await targetClient.connect();
|
||||
clients.set(accountId, targetClient);
|
||||
await syncStalwartAuthContext(serverUrl, username, targetClient.getAuthHeader(), targetAccount.cookieSlot);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -1174,6 +1218,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
await client.connect();
|
||||
clients.set(account.id, client);
|
||||
scheduleRefresh(expires_in, get().refreshAccessToken, account.id);
|
||||
await syncStalwartAuthContext(account.serverUrl, account.username, client.getAuthHeader(), account.cookieSlot);
|
||||
accountStore.updateAccount(account.id, { isConnected: true, hasError: false });
|
||||
} else {
|
||||
throw new Error(`Token refresh failed: ${res.status}`);
|
||||
@@ -1186,6 +1231,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
bindClientStatusHandlers(client, set, get, account.id);
|
||||
await client.connect();
|
||||
clients.set(account.id, client);
|
||||
await syncStalwartAuthContext(serverUrl, username, client.getAuthHeader(), account.cookieSlot);
|
||||
accountStore.updateAccount(account.id, { isConnected: true, hasError: false });
|
||||
} else {
|
||||
throw new Error(`Session cookie missing: ${res.status}`);
|
||||
@@ -1401,6 +1447,9 @@ export const useAuthStore = create<AuthState>()(
|
||||
});
|
||||
accountStore.setActiveAccount(accountId);
|
||||
|
||||
const cookieSlot = accountStore.getAccountById(accountId)?.cookieSlot ?? 0;
|
||||
await syncStalwartAuthContext(serverUrl, username, client.getAuthHeader(), cookieSlot);
|
||||
|
||||
const { identities, primaryIdentity } = loadIdentities(await client.getIdentities(), username);
|
||||
initializeFeatureStores(client);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user