Skip to content

Commit f9dff8f

Browse files
author
Onur Solmaz
committed
fix(auth): honor shared-host route prefixes
1 parent 8d03e66 commit f9dff8f

12 files changed

Lines changed: 126 additions & 5 deletions

File tree

api/main.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ func main() {
244244
}
245245

246246
func (s *server) registerRoutes(e *echo.Echo) {
247-
group := e.Group("/api")
247+
group := e.Group(s.apiPathPrefix())
248248
group.GET("/healthz", s.handleHealthz)
249249
internal := group.Group("/internal/v1", s.internalAuthMiddleware())
250250
if s.internalAuth.enabled {
@@ -282,6 +282,23 @@ func (s *server) registerRoutes(e *echo.Echo) {
282282
}
283283
}
284284

285+
func (s *server) apiPathPrefix() string {
286+
prefix := strings.TrimSpace(s.routeModel.APIPathPrefix)
287+
if prefix == "" {
288+
return "/api"
289+
}
290+
if !strings.HasPrefix(prefix, "/") {
291+
prefix = "/" + prefix
292+
}
293+
if len(prefix) > 1 {
294+
prefix = strings.TrimRight(prefix, "/")
295+
}
296+
if prefix == "" {
297+
return "/api"
298+
}
299+
return prefix
300+
}
301+
285302
func (s *server) handleHealthz(c echo.Context) error {
286303
return c.String(http.StatusOK, "ok")
287304
}

api/main_routes_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,30 @@ func TestRegisterRoutesAppliesAuthToInstanceProxyPrefix(t *testing.T) {
9090
t.Fatalf("expected /i/openclaw-tide-wind response to mention unauthenticated, got %q", rec.Body.String())
9191
}
9292
}
93+
94+
func TestRegisterRoutesUsesConfiguredAPIPrefix(t *testing.T) {
95+
t.Setenv("SPRITZ_ROUTE_API_PATH_PREFIX", "/control-api")
96+
97+
s := &server{
98+
auth: authConfig{mode: authModeNone},
99+
internalAuth: internalAuthConfig{enabled: false},
100+
terminal: terminalConfig{enabled: false},
101+
routeModel: spritzRouteModelFromEnv(),
102+
}
103+
e := echo.New()
104+
s.registerRoutes(e)
105+
106+
customReq := httptest.NewRequest(http.MethodGet, "/control-api/healthz", nil)
107+
customRec := httptest.NewRecorder()
108+
e.ServeHTTP(customRec, customReq)
109+
if customRec.Code != http.StatusOK {
110+
t.Fatalf("expected configured api prefix to return 200, got %d", customRec.Code)
111+
}
112+
113+
legacyReq := httptest.NewRequest(http.MethodGet, "/api/healthz", nil)
114+
legacyRec := httptest.NewRecorder()
115+
e.ServeHTTP(legacyRec, legacyReq)
116+
if legacyRec.Code != http.StatusNotFound {
117+
t.Fatalf("expected legacy /api/healthz to return 404 when a custom prefix is configured, got %d", legacyRec.Code)
118+
}
119+
}

helm/spritz/templates/ui-deployment.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ spec:
3232
env:
3333
- name: SPRITZ_API_BASE_URL
3434
value: {{ .Values.ui.apiBaseUrl | default (include "spritz.routeModel.apiPathPrefix" .) | quote }}
35+
- name: SPRITZ_UI_CHAT_PATH_PREFIX
36+
value: {{ include "spritz.routeModel.chatPathPrefix" . | quote }}
3537
- name: SPRITZ_UI_OWNER_ID
3638
value: {{ .Values.ui.ownerId | quote }}
3739
- name: SPRITZ_UI_AUTH_MODE

operator/api/v1/access_url.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ func InstanceURLForSpritz(spritz *Spritz) string {
1414
if spritz == nil {
1515
return ""
1616
}
17+
if !IsWebEnabled(spritz.Spec) {
18+
return ""
19+
}
1720
if spritz.Spec.Ingress != nil && spritz.Spec.Ingress.Host != "" {
1821
path := spritz.Spec.Ingress.Path
1922
if path == "" {

operator/api/v1/access_url_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,31 @@ func TestAccessURLForSpritzPrefersExplicitIngressOverSharedHostRouteModel(t *tes
109109
}
110110
}
111111

112+
func TestSharedHostURLsAreEmptyWhenWebIsDisabled(t *testing.T) {
113+
t.Setenv("SPRITZ_ROUTE_MODEL_TYPE", SharedHostRouteModelType)
114+
t.Setenv("SPRITZ_ROUTE_HOST", "console.example.com")
115+
116+
webDisabled := false
117+
spritz := &Spritz{
118+
ObjectMeta: metav1ObjectMeta("headless-agent", "spritz-test"),
119+
Spec: SpritzSpec{
120+
Features: &SpritzFeatures{
121+
Web: &webDisabled,
122+
},
123+
},
124+
}
125+
126+
if got := InstanceURLForSpritz(spritz); got != "" {
127+
t.Fatalf("expected no instance url for web-disabled spritz, got %q", got)
128+
}
129+
if got := ChatURLForSpritz(spritz); got != "" {
130+
t.Fatalf("expected no chat url for web-disabled spritz, got %q", got)
131+
}
132+
if got := AccessURLForSpritz(spritz); got != "" {
133+
t.Fatalf("expected no access url for web-disabled spritz, got %q", got)
134+
}
135+
}
136+
112137
func metav1ObjectMeta(name, namespace string) metav1.ObjectMeta {
113138
return metav1.ObjectMeta{
114139
Name: name,

ui/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ COPY entrypoint.sh /entrypoint.sh
2121
RUN chown -R 101:101 /usr/share/nginx/html /entrypoint.sh /etc/nginx/conf.d/default.conf
2222

2323
ENV SPRITZ_API_BASE_URL=/api
24+
ENV SPRITZ_UI_CHAT_PATH_PREFIX=/c
2425

2526
USER 101:101
2627
EXPOSE 8080

ui/entrypoint.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
set -eu
33

44
API_BASE_URL="${SPRITZ_API_BASE_URL:-}"
5+
CHAT_PATH_PREFIX="${SPRITZ_UI_CHAT_PATH_PREFIX:-}"
56
OWNER_ID="${SPRITZ_UI_OWNER_ID:-}"
67
AUTH_MODE="${SPRITZ_UI_AUTH_MODE:-}"
78
AUTH_TOKEN_STORAGE="${SPRITZ_UI_AUTH_TOKEN_STORAGE:-}"
@@ -33,6 +34,9 @@ ASSET_VERSION="${SPRITZ_UI_ASSET_VERSION:-}"
3334
if [ -z "$API_BASE_URL" ]; then
3435
API_BASE_URL="/api"
3536
fi
37+
if [ -z "$CHAT_PATH_PREFIX" ]; then
38+
CHAT_PATH_PREFIX="/c"
39+
fi
3640
if [ -z "$ASSET_VERSION" ]; then
3741
ASSET_VERSION="$(date +%s)"
3842
fi
@@ -42,6 +46,7 @@ escape_sed() {
4246
}
4347

4448
API_BASE_URL_ESCAPED="$(escape_sed "$API_BASE_URL")"
49+
CHAT_PATH_PREFIX_ESCAPED="$(escape_sed "$CHAT_PATH_PREFIX")"
4550
OWNER_ID_ESCAPED="$(escape_sed "$OWNER_ID")"
4651
AUTH_MODE_ESCAPED="$(escape_sed "$AUTH_MODE")"
4752
AUTH_TOKEN_STORAGE_ESCAPED="$(escape_sed "$AUTH_TOKEN_STORAGE")"
@@ -71,6 +76,7 @@ BRANDING_CONFIG_ESCAPED="$(escape_sed "$BRANDING_CONFIG_VALUE")"
7176
ASSET_VERSION_ESCAPED="$(escape_sed "$ASSET_VERSION")"
7277

7378
sed "s|__SPRITZ_API_BASE_URL__|${API_BASE_URL_ESCAPED}|g" "${HTML_DIR}/config.js" \
79+
| sed "s|__SPRITZ_UI_CHAT_PATH_PREFIX__|${CHAT_PATH_PREFIX_ESCAPED}|g" \
7480
| sed "s|__SPRITZ_OWNER_ID__|${OWNER_ID_ESCAPED}|g" \
7581
| sed "s|__SPRITZ_UI_AUTH_MODE__|${AUTH_MODE_ESCAPED}|g" \
7682
| sed "s|__SPRITZ_UI_AUTH_TOKEN_STORAGE__|${AUTH_TOKEN_STORAGE_ESCAPED}|g" \

ui/public/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
window.SPRITZ_CONFIG = {
22
apiBaseUrl: '__SPRITZ_API_BASE_URL__',
3+
chatPathPrefix: '__SPRITZ_UI_CHAT_PATH_PREFIX__',
34
ownerId: '__SPRITZ_OWNER_ID__',
45
presets: __SPRITZ_UI_PRESETS__,
56
repoDefaults: {

ui/src/App.test.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,17 @@ describe('App routing', () => {
5858
renderAtRoute('/terminal/my-spritz');
5959
expect(screen.getByTestId('terminal-page')).toBeDefined();
6060
});
61+
62+
it('renders ChatPage at the configured chat prefix', async () => {
63+
vi.resetModules();
64+
window.SPRITZ_CONFIG = {
65+
chatPathPrefix: '/chat',
66+
};
67+
window.history.pushState({}, '', '/chat/some-name');
68+
69+
const { App } = await import('@/App');
70+
render(<App />);
71+
72+
expect(screen.getByTestId('chat-page')).toBeDefined();
73+
});
6174
});

ui/src/App.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Layout } from '@/components/layout';
66
import { ChatPage } from '@/pages/chat';
77
import { CreatePage } from '@/pages/create';
88
import { TerminalPage } from '@/pages/terminal';
9+
import { chatConversationRoutePath, chatRoutePath } from '@/lib/urls';
910

1011
export function App() {
1112
return (
@@ -18,8 +19,8 @@ export function App() {
1819
<Route index element={<ChatPage />} />
1920
<Route path="create" element={<CreatePage />} />
2021
<Route path="terminal/:name" element={<TerminalPage />} />
21-
<Route path="c/:name?" element={<ChatPage />} />
22-
<Route path="c/:name/:conversationId" element={<ChatPage />} />
22+
<Route path={chatRoutePath(true)} element={<ChatPage />} />
23+
<Route path={chatConversationRoutePath()} element={<ChatPage />} />
2324
</Route>
2425
</Routes>
2526
</NoticeProvider>

0 commit comments

Comments
 (0)