mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
fix(opensky): load OAuth credential files and retire Basic auth (#1229)
Read OpenSky OAuth credentials from OPENSKY_CREDENTIALS_FILE and stop sending retired Basic authentication. Update setup diagnostics and guidance. Keep credential-file warnings free of parser excerpts and filesystem error details, cover both failure paths with a regression test, and use a launcher-compatible absolute path example. Original contribution by quanticsoul4772, with credential-warning and documentation follow-up by Sameh Khamis. Co-authored-by: quanticsoul4772 <[email protected]> Co-authored-by: Sameh Khamis <[email protected]>
This commit is contained in:
1 parent
07318cc9a4
commit
1c234ce027
10 files changed
+309
-79
No files matched your search
@@ -13,6 +13,7 @@ import {
|
||||
npmProcessSpec,
|
||||
readDoctorDotenvValue,
|
||||
resolveOpenSkyAuthMode,
|
||||
resolveOpenSkyCredentialsFile,
|
||||
resolveCredential,
|
||||
} from '../scripts/setup-doctor.mjs';
|
||||
|
||||
@@ -237,7 +238,7 @@ test('doctor retains OAuth capability wording for a complete client pair', () =>
|
||||
);
|
||||
});
|
||||
|
||||
test('doctor reports Basic mode without inferring runtime auth from OAuth credentials', () => {
|
||||
test('doctor reports retired Basic mode as anonymous, whatever OAuth credentials exist', () => {
|
||||
const oauthCredentials = {
|
||||
OPENSKY_CLIENT_ID: { configured: true },
|
||||
OPENSKY_CLIENT_SECRET: { configured: true },
|
||||
@@ -245,7 +246,7 @@ test('doctor reports Basic mode without inferring runtime auth from OAuth creden
|
||||
for (const credentials of [{}, oauthCredentials]) {
|
||||
assert.equal(
|
||||
buildCapabilitySummary(credentials, { openSkyAuthMode: 'basic' }).flights,
|
||||
'OpenSky Basic mode selected (credential presence and validity not verified)',
|
||||
'OpenSky Basic mode selected: OpenSky retired username/password sign-in, so flights load anonymously (rate-limited)',
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -393,3 +394,39 @@ test('doctor never calls a dependency-missing setup ready', () => {
|
||||
assert.match(output, /Setup needs attention/);
|
||||
assert.doesNotMatch(output, /Ready\. Run/);
|
||||
});
|
||||
|
||||
test('doctor counts OPENSKY_CREDENTIALS_FILE as OAuth credentials, from the environment or .env', () => {
|
||||
assert.equal(
|
||||
buildCapabilitySummary({}, { openSkyCredentialsFile: true }).flights,
|
||||
'OpenSky OAuth credentials file set (OPENSKY_CREDENTIALS_FILE; file contents and validity not verified)',
|
||||
);
|
||||
assert.equal(
|
||||
buildCapabilitySummary({}, { openSkyCredentialsFile: false }).flights,
|
||||
'OpenSky keyless anonymous access (rate-limited)',
|
||||
);
|
||||
const root = mkdtempSync(path.join(tmpdir(), 'gev-doctor-opensky-file-'));
|
||||
try {
|
||||
assert.equal(
|
||||
resolveOpenSkyCredentialsFile({ environment: {}, rootDir: root }),
|
||||
false,
|
||||
);
|
||||
writeFileSync(
|
||||
path.join(root, '.env'),
|
||||
'OPENSKY_CREDENTIALS_FILE=~/Downloads/credentials.json\n',
|
||||
);
|
||||
assert.equal(
|
||||
resolveOpenSkyCredentialsFile({ environment: {}, rootDir: root }),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
resolveOpenSkyCredentialsFile({
|
||||
authoritativeEnvironment: true,
|
||||
environment: { OPENSKY_CREDENTIALS_FILE: '' },
|
||||
rootDir: root,
|
||||
}),
|
||||
false,
|
||||
);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -31,9 +31,7 @@ function openSkyError(response) {
|
||||
const reasons = {
|
||||
oauth_invalid_or_missing: 'OpenSky OAuth client missing/invalid',
|
||||
oauth_invalid_credentials: 'OpenSky OAuth rejected credentials',
|
||||
basic_invalid_credentials: 'OpenSky username/password rejected',
|
||||
missing_basic_creds: 'OpenSky auth missing',
|
||||
missing_oauth_and_basic_creds: 'OpenSky auth missing',
|
||||
missing_oauth_creds: 'OpenSky auth missing',
|
||||
auth_required: 'OpenSky auth required',
|
||||
forced_anonymous: 'OpenSky auth required',
|
||||
};
|
||||
|
||||
@@ -584,6 +584,69 @@ test('track backfill proxy returns 502 on an oversized upstream body and caches
|
||||
assert.equal(callCount, 1);
|
||||
});
|
||||
|
||||
test('OpenSky Basic mode no longer sends username and password: it asks anonymously and warns once', async (t) => {
|
||||
environment(t, {
|
||||
OPENSKY_CLIENT_ID: undefined,
|
||||
OPENSKY_CLIENT_SECRET: undefined,
|
||||
OPENSKY_CREDENTIALS_FILE: undefined,
|
||||
OPENSKY_AUTH_MODE: 'basic',
|
||||
OPENSKY_USERNAME: 'fixture-user',
|
||||
OPENSKY_PASSWORD: 'fixture-password',
|
||||
});
|
||||
const warnings = [];
|
||||
t.mock.method(console, 'warn', (message) => warnings.push(message));
|
||||
const authorizations = [];
|
||||
t.mock.method(globalThis, 'fetch', async (url, options) => {
|
||||
if (url.includes('/states/')) {
|
||||
authorizations.push(options.headers.Authorization);
|
||||
return Response.json({ time: Math.floor(Date.now() / 1000), states: [] });
|
||||
}
|
||||
throw Error(`Unexpected URL: ${url}`);
|
||||
});
|
||||
const fresh = await import(
|
||||
`../../server/providers/aircraft/opensky.js?basic=${Math.random()}`
|
||||
);
|
||||
const flights = install(fresh.openSkyProxy());
|
||||
const first = await flights('/api/flights');
|
||||
assert.equal(first.statusCode, 200);
|
||||
assert.equal(first.headers['x-opensky-auth'], 'anon');
|
||||
assert.equal(first.headers['x-opensky-auth-reason'], 'basic_retired');
|
||||
assert.deepEqual(authorizations, [undefined]);
|
||||
assert.equal(warnings.length, 1);
|
||||
assert.match(warnings[0], /retired username\/password sign-in/);
|
||||
assert.ok(!warnings[0].includes('fixture-password'));
|
||||
});
|
||||
|
||||
test('OpenSky auto mode never falls back to Basic, even when OAuth is rejected', async (t) => {
|
||||
environment(t, {
|
||||
OPENSKY_CLIENT_ID: 'fixture-client',
|
||||
OPENSKY_CLIENT_SECRET: 'fixture-secret',
|
||||
OPENSKY_CREDENTIALS_FILE: undefined,
|
||||
OPENSKY_AUTH_MODE: 'auto',
|
||||
OPENSKY_USERNAME: 'fixture-user',
|
||||
OPENSKY_PASSWORD: 'fixture-password',
|
||||
});
|
||||
t.mock.method(console, 'warn', () => {});
|
||||
t.mock.method(console, 'log', () => {});
|
||||
t.mock.method(console, 'error', () => {});
|
||||
const authorizations = [];
|
||||
t.mock.method(globalThis, 'fetch', async (url, options) => {
|
||||
if (url.includes('/token'))
|
||||
return Response.json({ access_token: 'fixture-token', expires_in: 1800 });
|
||||
if (url.includes('/states/')) {
|
||||
authorizations.push(options.headers.Authorization);
|
||||
return new Response('', { status: 401 });
|
||||
}
|
||||
if (url.includes('/lat/')) return regionalFallback();
|
||||
throw Error(`Unexpected URL: ${url}`);
|
||||
});
|
||||
const fresh = await import(
|
||||
`../../server/providers/aircraft/opensky.js?auto=${Math.random()}`
|
||||
);
|
||||
await install(fresh.openSkyProxy())('/api/flights', '?lat=30&lon=-97');
|
||||
assert.deepEqual(authorizations, ['Bearer fixture-token']);
|
||||
});
|
||||
|
||||
test('retired military history refuses without upstream I/O while validating identifiers', async (t) => {
|
||||
t.mock.method(globalThis, 'fetch', async () => {
|
||||
assert.fail('retired history must not contact the upstream');
|
||||
|
||||
Reference in new issue
Block a user