mirror of
https://github.com/jcreek/LivingDexTracker.git
synced 2026-09-14 17:42:17 +00:00
92d6460765
The endpoint overrides are read through `$env/dynamic/private`, so they are evaluated per request in production, not baked in at build time. That made a single injected environment variable enough to redirect the authorization-code and refresh-token POSTs - which carry the OAuth client secret and the user's refresh token - to an arbitrary host, and to redirect the user's authorize hop to an arbitrary URL. Overrides are now ignored unless ALLOW_PROVIDER_ENDPOINT_OVERRIDES is exactly "true" and the value is a loopback URL. `npm run test:bdd` sets the flag; nothing else should. resolveProviderEndpoints is pure so the refusals are unit tested, including near-miss hosts such as http://127.0.0.1.example. Also drops the unused `pokedex` parameter from buildCsv rather than silencing it with `void`, and the dead hasGigantamaxed field from its fallback record.
78 lines
2.8 KiB
TypeScript
78 lines
2.8 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
PROVIDER_ENDPOINT_DEFAULTS,
|
|
resolveProviderEndpoints
|
|
} from '$lib/services/providerEndpoints';
|
|
|
|
const localOverrides = {
|
|
GOOGLE_OAUTH_AUTHORIZE_URL: 'http://127.0.0.1:4199/google/authorize',
|
|
GOOGLE_OAUTH_TOKEN_URL: 'http://127.0.0.1:4199/google/token',
|
|
GOOGLE_DRIVE_API_URL: 'http://127.0.0.1:4199/google/drive',
|
|
GOOGLE_DRIVE_UPLOAD_URL: 'http://127.0.0.1:4199/google/upload',
|
|
DROPBOX_OAUTH_AUTHORIZE_URL: 'http://127.0.0.1:4199/dropbox/authorize',
|
|
DROPBOX_OAUTH_TOKEN_URL: 'http://127.0.0.1:4199/dropbox/token',
|
|
DROPBOX_UPLOAD_URL: 'http://127.0.0.1:4199/dropbox/files/upload'
|
|
};
|
|
|
|
describe('provider endpoints', () => {
|
|
it('uses the real provider endpoints when nothing is configured', () => {
|
|
expect(resolveProviderEndpoints({})).toEqual(PROVIDER_ENDPOINT_DEFAULTS);
|
|
});
|
|
|
|
it('ignores overrides unless they are explicitly allowed', () => {
|
|
// The exfiltration case: these variables carry the client secret and refresh token, and
|
|
// the env is read per request in production.
|
|
expect(resolveProviderEndpoints(localOverrides)).toEqual(PROVIDER_ENDPOINT_DEFAULTS);
|
|
expect(
|
|
resolveProviderEndpoints({ ...localOverrides, ALLOW_PROVIDER_ENDPOINT_OVERRIDES: 'false' })
|
|
).toEqual(PROVIDER_ENDPOINT_DEFAULTS);
|
|
expect(
|
|
resolveProviderEndpoints({ ...localOverrides, ALLOW_PROVIDER_ENDPOINT_OVERRIDES: '1' })
|
|
).toEqual(PROVIDER_ENDPOINT_DEFAULTS);
|
|
});
|
|
|
|
it('applies allowed loopback overrides', () => {
|
|
expect(
|
|
resolveProviderEndpoints({ ...localOverrides, ALLOW_PROVIDER_ENDPOINT_OVERRIDES: 'true' })
|
|
).toEqual({
|
|
google: {
|
|
authorize: localOverrides.GOOGLE_OAUTH_AUTHORIZE_URL,
|
|
token: localOverrides.GOOGLE_OAUTH_TOKEN_URL,
|
|
driveApi: localOverrides.GOOGLE_DRIVE_API_URL,
|
|
driveUpload: localOverrides.GOOGLE_DRIVE_UPLOAD_URL
|
|
},
|
|
dropbox: {
|
|
authorize: localOverrides.DROPBOX_OAUTH_AUTHORIZE_URL,
|
|
token: localOverrides.DROPBOX_OAUTH_TOKEN_URL,
|
|
upload: localOverrides.DROPBOX_UPLOAD_URL
|
|
}
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
'https://attacker.example/token',
|
|
'http://127.0.0.1.attacker.example/token',
|
|
'http://[::2]/token',
|
|
'file:///etc/passwd',
|
|
'not-a-url',
|
|
''
|
|
])('refuses the non-loopback override %j even when overrides are allowed', (value) => {
|
|
const endpoints = resolveProviderEndpoints({
|
|
ALLOW_PROVIDER_ENDPOINT_OVERRIDES: 'true',
|
|
GOOGLE_OAUTH_TOKEN_URL: value,
|
|
DROPBOX_OAUTH_TOKEN_URL: value
|
|
});
|
|
expect(endpoints.google.token).toBe(PROVIDER_ENDPOINT_DEFAULTS.google.token);
|
|
expect(endpoints.dropbox.token).toBe(PROVIDER_ENDPOINT_DEFAULTS.dropbox.token);
|
|
});
|
|
|
|
it('accepts localhost as well as 127.0.0.1', () => {
|
|
expect(
|
|
resolveProviderEndpoints({
|
|
ALLOW_PROVIDER_ENDPOINT_OVERRIDES: 'true',
|
|
GOOGLE_OAUTH_TOKEN_URL: 'http://localhost:4199/google/token'
|
|
}).google.token
|
|
).toBe('http://localhost:4199/google/token');
|
|
});
|
|
});
|