mirror of
https://github.com/jcreek/LivingDexTracker.git
synced 2026-09-15 01:52:39 +00:00
fix(export): restrict provider endpoint overrides to loopback test servers
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.
This commit is contained in:
@@ -1,18 +1,70 @@
|
||||
import { getEnv } from '$lib/utils/env';
|
||||
|
||||
export function getProviderEndpoints() {
|
||||
const env = getEnv();
|
||||
export type ProviderEndpoints = {
|
||||
google: { authorize: string; token: string; driveApi: string; driveUpload: string };
|
||||
dropbox: { authorize: string; token: string; upload: string };
|
||||
};
|
||||
|
||||
const DEFAULTS: ProviderEndpoints = {
|
||||
google: {
|
||||
authorize: 'https://accounts.google.com/o/oauth2/v2/auth',
|
||||
token: 'https://oauth2.googleapis.com/token',
|
||||
driveApi: 'https://www.googleapis.com/drive/v3',
|
||||
driveUpload: 'https://www.googleapis.com/upload/drive/v3'
|
||||
},
|
||||
dropbox: {
|
||||
authorize: 'https://www.dropbox.com/oauth2/authorize',
|
||||
token: 'https://api.dropbox.com/oauth2/token',
|
||||
upload: 'https://content.dropboxapi.com/2/files/upload'
|
||||
}
|
||||
};
|
||||
|
||||
const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '[::1]']);
|
||||
|
||||
/**
|
||||
* These endpoints receive the OAuth client secret and the user's refresh token, so an override
|
||||
* is only ever a local test seam - never a deployment knob. Two guards, because the env is
|
||||
* read at runtime (`$env/dynamic/private`) and a single injected variable would otherwise be
|
||||
* enough to redirect those credentials to an arbitrary host:
|
||||
*
|
||||
* 1. overrides are ignored unless ALLOW_PROVIDER_ENDPOINT_OVERRIDES is exactly "true", and
|
||||
* 2. even then, only loopback URLs are accepted.
|
||||
*/
|
||||
function isLocalOverride(value: string): boolean {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
return (
|
||||
(url.protocol === 'http:' || url.protocol === 'https:') && LOOPBACK_HOSTS.has(url.hostname)
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveProviderEndpoints(
|
||||
env: Record<string, string | undefined>
|
||||
): ProviderEndpoints {
|
||||
const overridesAllowed = env.ALLOW_PROVIDER_ENDPOINT_OVERRIDES === 'true';
|
||||
const pick = (override: string | undefined, fallback: string) =>
|
||||
overridesAllowed && override && isLocalOverride(override) ? override : fallback;
|
||||
|
||||
return {
|
||||
google: {
|
||||
authorize: env.GOOGLE_OAUTH_AUTHORIZE_URL || 'https://accounts.google.com/o/oauth2/v2/auth',
|
||||
token: env.GOOGLE_OAUTH_TOKEN_URL || 'https://oauth2.googleapis.com/token',
|
||||
driveApi: env.GOOGLE_DRIVE_API_URL || 'https://www.googleapis.com/drive/v3',
|
||||
driveUpload: env.GOOGLE_DRIVE_UPLOAD_URL || 'https://www.googleapis.com/upload/drive/v3'
|
||||
authorize: pick(env.GOOGLE_OAUTH_AUTHORIZE_URL, DEFAULTS.google.authorize),
|
||||
token: pick(env.GOOGLE_OAUTH_TOKEN_URL, DEFAULTS.google.token),
|
||||
driveApi: pick(env.GOOGLE_DRIVE_API_URL, DEFAULTS.google.driveApi),
|
||||
driveUpload: pick(env.GOOGLE_DRIVE_UPLOAD_URL, DEFAULTS.google.driveUpload)
|
||||
},
|
||||
dropbox: {
|
||||
authorize: env.DROPBOX_OAUTH_AUTHORIZE_URL || 'https://www.dropbox.com/oauth2/authorize',
|
||||
token: env.DROPBOX_OAUTH_TOKEN_URL || 'https://api.dropbox.com/oauth2/token',
|
||||
upload: env.DROPBOX_UPLOAD_URL || 'https://content.dropboxapi.com/2/files/upload'
|
||||
authorize: pick(env.DROPBOX_OAUTH_AUTHORIZE_URL, DEFAULTS.dropbox.authorize),
|
||||
token: pick(env.DROPBOX_OAUTH_TOKEN_URL, DEFAULTS.dropbox.token),
|
||||
upload: pick(env.DROPBOX_UPLOAD_URL, DEFAULTS.dropbox.upload)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function getProviderEndpoints(): ProviderEndpoints {
|
||||
return resolveProviderEndpoints(getEnv());
|
||||
}
|
||||
|
||||
export const PROVIDER_ENDPOINT_DEFAULTS = DEFAULTS;
|
||||
|
||||
Reference in New Issue
Block a user