Files
TREK/server/tests/unit/middleware/globalMiddleware.redaction.test.ts
Maurice 1f66ef1d32 fix(maps): let users and admins enter a CARTO API key (#2060)
CARTO stamps an "API KEY REQUIRED" watermark onto every basemap tile
fetched without a key since 26.08.2026, and both CartoDB presets plus the
built-in fallback are CARTO. The key is free and needs no account, so add
it as a setting rather than dropping the provider.

carto_api_key sits next to mapbox_access_token: per user in Settings/Map,
instance-wide under Admin/User Defaults, encrypted at rest, injected from
CARTO_API_KEY on managed instances. Unlike Mapbox it never switches the
map provider, since a raster key says nothing about the renderer.

The key is appended as ?key= at render time instead of being stored in the
template, so a saved URL stays portable and survives a rotation. That is
also what reaches the four maps that pinned CARTO and ignored the user's
tile setting: atlas, collections, the public trip share and the public
journey. The two share pages read it from their token-gated payload, the
same way baseCurrency already travels.

Also lifts the release notice above page chrome. Its overlay sat at
z-index 50, under the Vacay mode toolbar at 61, so the dialog was neither
on top nor modal. Adds a six-step layer scale to index.css and moves the
three notice shells onto it.

Closes #2054
Closes #2052
2026-08-27 12:06:55 +02:00

90 lines
3.4 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { redact, SENSITIVE_KEYS } from '../../../src/middleware/globalMiddleware';
describe('globalMiddleware request-log redaction', () => {
it('redacts secretAccessKey (any casing) — storage admin PUT bodies land in the same debug log line', () => {
expect(redact({ secretAccessKey: 'sk-super-secret' })).toEqual({ secretAccessKey: '[REDACTED]' });
expect(redact({ SecretAccessKey: 'sk-super-secret' })).toEqual({ SecretAccessKey: '[REDACTED]' });
expect(redact({ secretaccesskey: 'sk-super-secret' })).toEqual({ secretaccesskey: '[REDACTED]' });
});
it('lists secretaccesskey in the lowercase lookup set (lookup lowercases the key first)', () => {
expect(SENSITIVE_KEYS.has('secretaccesskey')).toBe(true);
});
it('still redacts every pre-existing sensitive key (no regression)', () => {
const input = {
password: 'p',
new_password: 'p',
current_password: 'p',
token: 't',
jwt: 'j',
authorization: 'a',
cookie: 'c',
client_secret: 's',
mfa_token: 'm',
code: '123456',
smtp_pass: 's',
};
const out = redact(input) as Record<string, unknown>;
for (const key of Object.keys(input)) {
expect(out[key]).toBe('[REDACTED]');
}
});
it('redacts nested objects and arrays, leaving non-sensitive fields untouched', () => {
const out = redact({
name: 'off-box',
options: { secretAccessKey: 'sk-1', accessKeyId: 'ak-1' },
backends: [{ secretAccessKey: 'sk-2' }, { accessKeyId: 'ak-2' }],
});
expect(out).toEqual({
name: 'off-box',
options: { secretAccessKey: '[REDACTED]', accessKeyId: 'ak-1' },
backends: [{ secretAccessKey: '[REDACTED]' }, { accessKeyId: 'ak-2' }],
});
});
it('redacts secret-shaped keys by suffix — the OAuth token body and the settings API keys', () => {
const out = redact({
refresh_token: 'rt-1',
code_verifier: 'cv-1',
llm_api_key: 'sk-1',
mapbox_access_token: 'pk-1',
grant_type: 'refresh_token',
}) as Record<string, unknown>;
expect(out.refresh_token).toBe('[REDACTED]');
expect(out.code_verifier).toBe('[REDACTED]');
expect(out.llm_api_key).toBe('[REDACTED]');
expect(out.mapbox_access_token).toBe('[REDACTED]');
expect(out.grant_type).toBe('refresh_token');
});
it('redacts the value of a {key, value} settings body when the key names a secret', () => {
expect(redact({ key: 'carto_api_key', value: 'carto-secret' })).toEqual({
key: 'carto_api_key',
value: '[REDACTED]',
});
expect(redact({ key: 'mapbox_access_token', value: 'pk-1' })).toEqual({
key: 'mapbox_access_token',
value: '[REDACTED]',
});
// A harmless setting keeps its value: the name is what decides.
expect(redact({ key: 'map_tile_url', value: 'https://tiles.example/{z}/{x}/{y}.png' })).toEqual({
key: 'map_tile_url',
value: 'https://tiles.example/{z}/{x}/{y}.png',
});
});
it('matches the suffix on the end of the key only, so identifiers stay readable', () => {
expect(redact({ accessKeyId: 'ak-1', tokenCount: 5 })).toEqual({ accessKeyId: 'ak-1', tokenCount: 5 });
});
it('passes non-object values through untouched', () => {
expect(redact('plain string')).toBe('plain string');
expect(redact(42)).toBe(42);
expect(redact(null)).toBe(null);
expect(redact(undefined)).toBe(undefined);
});
});