mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-14 08:11:03 +00:00
7d3ed21a03
The IMAP box, the webhook secret and the address template join the admin settings form (masked, encrypted, held by the operator on a managed install), and MAIL_INGEST_* variables override them through the mailIngestConfig token. The audit row masks every masked key, and key rotation covers the new secrets.
170 lines
6.7 KiB
TypeScript
170 lines
6.7 KiB
TypeScript
/**
|
||
* Who owns which setting, pinned.
|
||
*
|
||
* The lists grow and the mode does not: that is how this kind of switch drifts.
|
||
* A key added to the admin form (ADMIN_FORM_SETTING_KEYS) or DEFAULTABLE_USER_SETTING_KEYS belongs to
|
||
* neither set until somebody decides, and MANAGED-KEYS-001 is what makes that
|
||
* decision unavoidable rather than optional.
|
||
*
|
||
* Deliberately an assignment test, not an equality test. Pinning the locked list
|
||
* alone would pass forever while new keys quietly defaulted to "the admin may
|
||
* set it", which is the failure this is here to prevent.
|
||
*/
|
||
import { ADMIN_FORM_SETTING_KEYS } from '../../../src/nest/common/app-settings.registry';
|
||
import {
|
||
MANAGED_LOCKED_SETTING_KEYS,
|
||
MANAGED_LOCKED_PROFILE_KEYS,
|
||
MANAGED_CUSTOMER_KEYS,
|
||
envLockedKeys,
|
||
isManagedLockedKey,
|
||
splitManagedKeys,
|
||
withEnvLocks,
|
||
} from '../../../src/nest/common/managed';
|
||
import { DEFAULTABLE_USER_SETTING_KEYS } from '../../../src/nest/settings/settings.service';
|
||
|
||
import { afterEach, describe, it, expect, vi } from 'vitest';
|
||
|
||
const SOURCE_KEYS: string[] = [...ADMIN_FORM_SETTING_KEYS, ...DEFAULTABLE_USER_SETTING_KEYS];
|
||
const locked = new Set<string>(MANAGED_LOCKED_SETTING_KEYS);
|
||
const customer = new Set<string>(MANAGED_CUSTOMER_KEYS);
|
||
|
||
describe('managed key assignment', () => {
|
||
it('MANAGED-KEYS-001: every key from both source lists is assigned to exactly one set', () => {
|
||
const unassigned = SOURCE_KEYS.filter((k) => !locked.has(k) && !customer.has(k));
|
||
const both = SOURCE_KEYS.filter((k) => locked.has(k) && customer.has(k));
|
||
|
||
// Both in one assertion so the failure message names the offending key.
|
||
expect({ unassigned, both }).toEqual({ unassigned: [], both: [] });
|
||
});
|
||
|
||
it('MANAGED-KEYS-002: no customer entry outlives the key it describes', () => {
|
||
expect(MANAGED_CUSTOMER_KEYS.filter((k) => !SOURCE_KEYS.includes(k))).toEqual([]);
|
||
});
|
||
|
||
it('MANAGED-KEYS-003: the only locked names outside both source lists are the users columns and the service key', () => {
|
||
// service_key is written by the server alone, never through a settings
|
||
// form; it is locked so a managed install keeps the operator's key.
|
||
expect(
|
||
MANAGED_LOCKED_SETTING_KEYS.filter((k) => !SOURCE_KEYS.includes(k))
|
||
.slice()
|
||
.sort(),
|
||
).toEqual([...MANAGED_LOCKED_PROFILE_KEYS, 'service_key'].sort());
|
||
});
|
||
|
||
it('MANAGED-KEYS-004: neither set repeats itself', () => {
|
||
expect(locked.size).toBe(MANAGED_LOCKED_SETTING_KEYS.length);
|
||
expect(customer.size).toBe(MANAGED_CUSTOMER_KEYS.length);
|
||
});
|
||
|
||
it('MANAGED-KEYS-005: the locked list is pinned verbatim', () => {
|
||
expect([...MANAGED_LOCKED_SETTING_KEYS]).toEqual([
|
||
'amap_api_key',
|
||
'carto_api_key',
|
||
'llm_api_key',
|
||
'llm_base_url',
|
||
'llm_model',
|
||
'llm_multimodal',
|
||
'llm_provider',
|
||
'mail_ingest_address_template',
|
||
'mail_ingest_ai_per_user_daily',
|
||
'mail_ingest_enabled',
|
||
'mail_ingest_imap_host',
|
||
'mail_ingest_imap_mailbox',
|
||
'mail_ingest_imap_pass',
|
||
'mail_ingest_imap_port',
|
||
'mail_ingest_imap_tls',
|
||
'mail_ingest_imap_user',
|
||
'mail_ingest_transport',
|
||
'mail_ingest_webhook_secret',
|
||
'mapbox_access_token',
|
||
'maps_api_key',
|
||
'oidc_auto_redirect',
|
||
'oidc_login',
|
||
'oidc_registration',
|
||
'openweather_api_key',
|
||
'places_provider',
|
||
'routing_base_url',
|
||
'routing_valhalla_provider',
|
||
'service_key',
|
||
'smtp_from',
|
||
'smtp_host',
|
||
'smtp_pass',
|
||
'smtp_port',
|
||
'smtp_skip_tls_verify',
|
||
'smtp_user',
|
||
'unsplash_api_key',
|
||
'valhalla_base_url',
|
||
'webauthn_origins',
|
||
'webauthn_rp_id',
|
||
]);
|
||
});
|
||
|
||
it("MANAGED-KEYS-006: mapbox_access_token is the operator's, and reaches the browser by design", () => {
|
||
// The one locked key whose value is meant to be public: a managed instance
|
||
// ships the operator's pk.* token, injected when settings are read. Locked so
|
||
// a per-user save cannot land on top of it and break that user's map.
|
||
expect(isManagedLockedKey('mapbox_access_token')).toBe(true);
|
||
expect(customer.has('mapbox_access_token')).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe('splitManagedKeys', () => {
|
||
it('MANAGED-KEYS-007: hands the body back untouched on a self-hosted install', () => {
|
||
const body = { smtp_host: 'mail.example.test', allow_registration: 'true' };
|
||
|
||
expect(splitManagedKeys(body, false)).toEqual({ allowed: body, blocked: [] });
|
||
});
|
||
|
||
it('MANAGED-KEYS-008: keeps the customer keys and reports the locked ones', () => {
|
||
// The admin settings tab saves SMTP and the registration toggles in one
|
||
// request. Refusing the request would take the toggles down with the SMTP.
|
||
const { allowed, blocked } = splitManagedKeys(
|
||
{ smtp_host: 'mail.example.test', smtp_pass: 'hunter2', allow_registration: 'true' },
|
||
true,
|
||
);
|
||
|
||
expect(allowed).toEqual({ allow_registration: 'true' });
|
||
expect(blocked).toEqual(['smtp_host', 'smtp_pass']);
|
||
});
|
||
|
||
it('MANAGED-KEYS-009: reports blocked names sorted, so a response body is stable', () => {
|
||
const { blocked } = splitManagedKeys({ webauthn_rp_id: 'x', llm_api_key: 'y', smtp_from: 'z' }, true);
|
||
|
||
expect(blocked).toEqual(['llm_api_key', 'smtp_from', 'webauthn_rp_id']);
|
||
});
|
||
|
||
it('MANAGED-KEYS-010: an unknown key is not the filter’s business', () => {
|
||
// Whatever rejects unknown keys today keeps rejecting them; this only ever
|
||
// removes names it was told to remove.
|
||
const { allowed, blocked } = splitManagedKeys({ not_a_setting: '1' }, true);
|
||
|
||
expect(allowed).toEqual({ not_a_setting: '1' });
|
||
expect(blocked).toEqual([]);
|
||
});
|
||
});
|
||
|
||
describe('env-held admin switches', () => {
|
||
afterEach(() => vi.unstubAllEnvs());
|
||
|
||
it('MANAGED-ENV-001: unset, the form answers the stored value and a save writes it', () => {
|
||
vi.stubEnv('FLIGHT_LIVE_ENABLED', '');
|
||
expect(withEnvLocks({ flight_live_enabled: 'true', require_mfa: 'false' })).toEqual({
|
||
flight_live_enabled: 'true',
|
||
require_mfa: 'false',
|
||
});
|
||
expect(envLockedKeys({ flight_live_enabled: 'false' })).toEqual([]);
|
||
});
|
||
|
||
it('MANAGED-ENV-002: set, the form shows the variable and env_override_flight_live_enabled, and a save skips it', () => {
|
||
vi.stubEnv('FLIGHT_LIVE_ENABLED', 'off');
|
||
expect(withEnvLocks({ flight_live_enabled: 'true', require_mfa: 'false' })).toEqual({
|
||
flight_live_enabled: 'false',
|
||
env_override_flight_live_enabled: 'false',
|
||
require_mfa: 'false',
|
||
});
|
||
expect(withEnvLocks({})).toEqual({ flight_live_enabled: 'false', env_override_flight_live_enabled: 'false' });
|
||
expect(envLockedKeys({ flight_live_enabled: 'true', require_mfa: 'true' })).toEqual(['flight_live_enabled']);
|
||
expect(envLockedKeys({ require_mfa: 'true' })).toEqual([]);
|
||
});
|
||
});
|