mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(settings): make the self-hosted routing engine an instance setting
The field was rendered in every non-managed user's own Map settings and written to their personal settings row. The CSP is built from something else: bootstrap reads the ADMIN default and puts that origin into `connect-src`. So filling the field in was the one path the product offered, and it could not work — the route calculator happily built its URLs against the typed origin and the browser refused every one of them with a bare TypeError, not a status, so no fallback and no retry could see it. Day-plan routes, transport legs, the mobile day route, Studio's road route and the whole road-trip rail stopped routing, app-wide, with a generic "route could not be calculated". Nothing in the UI could repair it except emptying the field again, and there was no admin field to set the value that would have made the origin allowed. The repo has decided this shape once already: `llm_base_url` is admin-only because it names where the server sends its own requests and only whoever runs the instance can judge what is reachable. This is the same class for a different reason, so it joins the same guard, which is now named for what it actually covers. Clearing stays open to everyone, so a row written before the rule cleans itself up rather than blocking the save. The field moves to the admin's default-user settings, next to the tile URL and the Carto key, where a value takes effect.
This commit is contained in:
@@ -150,6 +150,31 @@ describe('Settings e2e (real auth guard + temp SQLite)', () => {
|
||||
.toEqual({ value: 'anthropic' });
|
||||
});
|
||||
|
||||
it('PUT 403 for a non-admin naming a routing engine (#1797)', async () => {
|
||||
// Same class for a different reason: this origin has to appear in the CSP
|
||||
// connect-src the server emits at boot, and that list is built from the
|
||||
// admin default. A value on a personal row is read by the route
|
||||
// calculator and then refused by the browser, so the app stops routing
|
||||
// with no error it could report. Refusing tells the caller instead.
|
||||
const res = await request(server).put('/api/settings').set('Cookie', sessionCookie(1))
|
||||
.send({ key: 'routing_base_url', value: 'https://osrm.example.org' });
|
||||
expect(res.status).toBe(403);
|
||||
expect(res.body).toEqual({ error: 'Admin access required' });
|
||||
expect(countRows()).toBe(0);
|
||||
});
|
||||
|
||||
it('PUT lets a non-admin clear a routing engine somebody set before the rule', async () => {
|
||||
expect((await request(server).put('/api/settings').set('Cookie', sessionCookie(1))
|
||||
.send({ key: 'routing_base_url', value: '' })).status).toBe(200);
|
||||
});
|
||||
|
||||
it('POST bulk refuses a routing engine from a non-admin', async () => {
|
||||
const res = await request(server).post('/api/settings/bulk').set('Cookie', sessionCookie(1))
|
||||
.send({ settings: { routing_base_url: 'https://osrm.example.org' } });
|
||||
expect(res.status).toBe(403);
|
||||
expect(countRows()).toBe(0);
|
||||
});
|
||||
|
||||
it('PUT is unaffected for a non-LLM key with the same value', async () => {
|
||||
const res = await request(server).put('/api/settings').set('Cookie', sessionCookie(1))
|
||||
.send({ key: 'start_page', value: 'local' });
|
||||
|
||||
@@ -48,7 +48,7 @@ import { encrypt_api_key } from '../../../src/nest/common/crypto/apiKeyCrypto';
|
||||
import { MASKED_SETTING_VALUE } from '@trek/shared';
|
||||
import { DISPLAY_PREFERENCE_KEYS } from '../../../src/nest/settings/settings.mcp';
|
||||
import { MANAGED_LOCKED_SETTING_KEYS } from '../../../src/nest/common/managed';
|
||||
import { isAdminOnlyLlmSetting, ENCRYPTED_SETTING_KEYS, MASKED_SETTING_KEYS } from '../../../src/nest/settings/settings.service';
|
||||
import { isAdminOnlyEndpointSetting, ENCRYPTED_SETTING_KEYS, MASKED_SETTING_KEYS } from '../../../src/nest/settings/settings.service';
|
||||
|
||||
beforeAll(() => {
|
||||
createTables(testDb);
|
||||
@@ -493,11 +493,11 @@ describe('Display-preference allow-list', () => {
|
||||
expect(overlap).toEqual([]);
|
||||
});
|
||||
|
||||
it('holds no key assertMayWriteLlmEndpoint would have to refuse', () => {
|
||||
// isAdminOnlyLlmSetting is value-dependent, so probe it with the values
|
||||
it('holds no key assertMayWriteInstanceEndpoint would have to refuse', () => {
|
||||
// isAdminOnlyEndpointSetting is value-dependent, so probe it with the values
|
||||
// that trip it rather than matching on the key name.
|
||||
const offenders = DISPLAY_PREFERENCE_KEYS.filter((key) =>
|
||||
isAdminOnlyLlmSetting(key, 'http://127.0.0.1:11434') || isAdminOnlyLlmSetting(key, 'local'));
|
||||
isAdminOnlyEndpointSetting(key, 'http://127.0.0.1:11434') || isAdminOnlyEndpointSetting(key, 'local'));
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user