mirror of
https://github.com/HeyPuter/puter.git
synced 2026-10-10 22:01:40 +00:00
fix: alert errors from timeouts (#4066)
This commit is contained in:
1 parent
f533eb84ae
commit
51eba174b3
3 files changed
+77
-15
No files matched your search
@@ -701,6 +701,26 @@ describe('OIDCController login callback', () => {
|
||||
expect(captured.redirectUrl).toContain('auth_error=1');
|
||||
});
|
||||
|
||||
it('redirects with auth_error when the provider is unreachable', async () => {
|
||||
const state = oidc().signState({
|
||||
provider: 'custom',
|
||||
redirect_uri: TEST_ORIGIN + '/',
|
||||
});
|
||||
vi.spyOn(oidc(), 'exchangeCodeForTokens').mockRejectedValue(
|
||||
new TypeError('fetch failed'),
|
||||
);
|
||||
|
||||
const { res, captured } = makeRes();
|
||||
await callRoute(
|
||||
'get',
|
||||
'/auth/oidc/callback/login',
|
||||
makeReq({ query: { code: 'c', state } }),
|
||||
res,
|
||||
);
|
||||
expect(captured.redirectStatus).toBe(302);
|
||||
expect(captured.redirectUrl).toContain('auth_error=1');
|
||||
});
|
||||
|
||||
it('parses code/state from the POST body (Apple form_post)', async () => {
|
||||
const state = oidc().signState({
|
||||
provider: 'custom',
|
||||
@@ -1726,6 +1746,30 @@ describe('OIDCController revalidate callback', () => {
|
||||
expect(String(captured.body)).toContain('Missing');
|
||||
});
|
||||
|
||||
it('returns 400 when the userinfo request cannot reach the provider', async () => {
|
||||
const state = oidc().signState({
|
||||
provider: 'custom',
|
||||
flow: 'revalidate',
|
||||
user_uuid: uuidv4(),
|
||||
});
|
||||
vi.spyOn(oidc(), 'exchangeCodeForTokens').mockResolvedValue({
|
||||
access_token: 'access',
|
||||
} as never);
|
||||
vi.spyOn(oidc(), 'getUserInfo').mockRejectedValue(
|
||||
new TypeError('fetch failed'),
|
||||
);
|
||||
|
||||
const { res, captured } = makeRes();
|
||||
await callRoute(
|
||||
'get',
|
||||
'/auth/oidc/callback/revalidate',
|
||||
makeReq({ query: { code: 'c', state } }),
|
||||
res,
|
||||
);
|
||||
expect(captured.statusCode).toBe(400);
|
||||
expect(String(captured.body)).toContain('Please try again');
|
||||
});
|
||||
|
||||
it('returns 403 when the OIDC sub resolves to a different account than the session', async () => {
|
||||
// The linked account exists, but state.user_uuid points at someone else.
|
||||
const sub = `sub-${Math.random().toString(36).slice(2, 8)}`;
|
||||
|
||||
@@ -963,23 +963,33 @@ if (window.opener) {
|
||||
const callbackUrl = this.services.oidc.getCallbackUrl(flow);
|
||||
if (!callbackUrl) return { error: 'Invalid flow.' };
|
||||
|
||||
const tokens = await this.services.oidc.exchangeCodeForTokens(
|
||||
provider,
|
||||
code,
|
||||
callbackUrl,
|
||||
);
|
||||
if (!tokens || !tokens.access_token)
|
||||
return { error: 'Token exchange failed.' };
|
||||
try {
|
||||
const tokens = await this.services.oidc.exchangeCodeForTokens(
|
||||
provider,
|
||||
code,
|
||||
callbackUrl,
|
||||
);
|
||||
if (!tokens || !tokens.access_token)
|
||||
return { error: 'Token exchange failed.' };
|
||||
|
||||
const userinfo = await this.services.oidc.getUserInfo(
|
||||
provider,
|
||||
tokens.access_token,
|
||||
typeof tokens.id_token === 'string' ? tokens.id_token : undefined,
|
||||
);
|
||||
if (!userinfo || !userinfo.sub)
|
||||
return { error: 'Could not get user info.' };
|
||||
const userinfo = await this.services.oidc.getUserInfo(
|
||||
provider,
|
||||
tokens.access_token,
|
||||
typeof tokens.id_token === 'string'
|
||||
? tokens.id_token
|
||||
: undefined,
|
||||
);
|
||||
if (!userinfo || !userinfo.sub)
|
||||
return { error: 'Could not get user info.' };
|
||||
|
||||
return { provider, userinfo, stateDecoded };
|
||||
return { provider, userinfo, stateDecoded };
|
||||
} catch (e) {
|
||||
// Network failures reaching the provider; the user can retry.
|
||||
console.warn(`[oidc] ${provider} request failed`, e);
|
||||
return {
|
||||
error: 'Could not reach the sign-in provider. Please try again.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async #finishLogin(
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
import { isSpanContextValid, trace } from '@opentelemetry/api';
|
||||
import { setDefaultAutoSelectFamilyAttemptTimeout } from 'node:net';
|
||||
import { puterClients } from './clients';
|
||||
import { loadConfig } from './config';
|
||||
import { puterControllers } from './controllers';
|
||||
@@ -32,8 +33,15 @@ import { installJsonConsole } from './util/jsonConsole.js';
|
||||
// it stops accepting connections.
|
||||
const GRACEFUL_DRAIN_MS = 90_000;
|
||||
|
||||
// Node's happy-eyeballs default (250 ms) abandons a slow IPv4 handshake and
|
||||
// falls through to IPv6; on hosts with no IPv6 route, one dropped SYN fails
|
||||
// the whole connect. 2 s outlasts a single SYN retransmit.
|
||||
const CONNECT_ATTEMPT_TIMEOUT_MS = 2_000;
|
||||
|
||||
// if called directly, start the server
|
||||
if (require.main === module) {
|
||||
setDefaultAutoSelectFamilyAttemptTimeout(CONNECT_ATTEMPT_TIMEOUT_MS);
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
// Structured logging: when `log_format: "json"`, replace the global console
|
||||
|
||||
Reference in new issue
Block a user