mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 14:06:27 +00:00
84d9fbff3c
#1939 moved the Google Places and Unsplash keys into encrypted app_settings rows, but the rotation script walks a hardcoded list of app_settings keys and the two new ones were not on it. Rotating ENCRYPTION_KEY would have left them encrypted under the old key, which reads back as null and silently drops the install to OpenStreetMap with nothing in the log to say why. users.unsplash_api_key was missing from the users column list for as long as the column has existed. It matters now that the resolver reads that column as the per-user fallback, so it goes in at the same time. The script cannot import the canonical list, since it deliberately runs without config.ts and without the container, so the copy is pinned by a parity test that reads the script and fails when a name is missing from the app_settings loop. Wiki: the rotation page listed the Maps key as per-user and did not mention the app_settings rows an operator now has to carry across.
50 lines
1.9 KiB
TypeScript
50 lines
1.9 KiB
TypeScript
/**
|
|
* Parity guard for scripts/migrate-encryption.ts.
|
|
*
|
|
* The rotation script re-encrypts at-rest secrets from one ENCRYPTION_KEY to the
|
|
* next, and it walks a hardcoded list of app_settings keys. It cannot import the
|
|
* canonical list from src/, because it deliberately runs without config.ts and
|
|
* without the Nest container, so the list is a copy.
|
|
*
|
|
* A missing name does not fail loudly: the value stays encrypted under the old
|
|
* key, decrypt_api_key returns null on the next read, and the instance quietly
|
|
* behaves as if no key were configured. This pins the copy instead.
|
|
*/
|
|
import { describe, it, expect } from 'vitest';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { INSTANCE_API_KEY_NAMES } from '../../../src/nest/settings/instance-api-keys';
|
|
|
|
const script = fs.readFileSync(
|
|
path.join(__dirname, '..', '..', '..', 'scripts', 'migrate-encryption.ts'),
|
|
'utf8',
|
|
);
|
|
|
|
// The app_settings loop only, so a name that happens to appear elsewhere in the
|
|
// script (a users column of the same name, a comment) cannot satisfy the test.
|
|
const appSettingsLoop = (() => {
|
|
const start = script.indexOf("'oidc_client_secret'");
|
|
expect(start).toBeGreaterThan(-1);
|
|
const end = script.indexOf('--- users:', start);
|
|
expect(end).toBeGreaterThan(start);
|
|
return script.slice(start, end);
|
|
})();
|
|
|
|
describe('migrate-encryption.ts app_settings parity', () => {
|
|
it('ROTPAR-001: rotates every instance-wide API key', () => {
|
|
for (const name of INSTANCE_API_KEY_NAMES) {
|
|
expect(appSettingsLoop).toContain(`'${name}'`);
|
|
}
|
|
});
|
|
|
|
it('ROTPAR-002: still rotates the app_settings secrets that predate them', () => {
|
|
for (const name of ['oidc_client_secret', 'smtp_pass', 'admin_webhook_url', 'admin_ntfy_token']) {
|
|
expect(appSettingsLoop).toContain(`'${name}'`);
|
|
}
|
|
});
|
|
|
|
it('ROTPAR-003: the canonical list is not empty, so ROTPAR-001 cannot pass vacuously', () => {
|
|
expect(INSTANCE_API_KEY_NAMES.length).toBeGreaterThan(0);
|
|
});
|
|
});
|