From 57e0197d75998148ef2d994ce42aa35c20003c01 Mon Sep 17 00:00:00 2001 From: dave Date: Tue, 31 Mar 2026 14:52:18 +0000 Subject: [PATCH] storkit: merge 449_bug_oauth_callback_url_ignores_port_cli_flag --- frontend/src/App.test.tsx | 7 ++++++- frontend/src/components/Chat.tsx | 6 +++++- frontend/src/components/ChatHeader.tsx | 6 +++++- .../src/components/selection/SelectionScreen.tsx | 10 ++++++++-- server/src/http/mod.rs | 14 ++++++++++++-- server/src/http/oauth.rs | 7 +++++++ server/src/main.rs | 2 +- 7 files changed, 44 insertions(+), 8 deletions(-) diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 22a1aea8..07f94fc7 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -66,7 +66,12 @@ describe("App", () => { mockedApi.getAnthropicApiKeyExists.mockResolvedValue(false); mockedApi.getAnthropicModels.mockResolvedValue([]); mockedApi.getModelPreference.mockResolvedValue(null); - mockedApi.getOAuthStatus.mockResolvedValue({ authenticated: false, expired: false, expires_at: 0, has_refresh_token: false }); + mockedApi.getOAuthStatus.mockResolvedValue({ + authenticated: false, + expired: false, + expires_at: 0, + has_refresh_token: false, + }); }); async function renderApp() { diff --git a/frontend/src/components/Chat.tsx b/frontend/src/components/Chat.tsx index 1e73e51a..bad98dc3 100644 --- a/frontend/src/components/Chat.tsx +++ b/frontend/src/components/Chat.tsx @@ -168,7 +168,11 @@ interface ChatProps { oauthStatus?: OAuthStatus | null; } -export function Chat({ projectPath, onCloseProject, oauthStatus = null }: ChatProps) { +export function Chat({ + projectPath, + onCloseProject, + oauthStatus = null, +}: ChatProps) { const { messages, setMessages, clearMessages } = useChatHistory(projectPath); const [loading, setLoading] = useState(false); const [model, setModel] = useState("claude-code-pty"); diff --git a/frontend/src/components/ChatHeader.tsx b/frontend/src/components/ChatHeader.tsx index 83b5b25c..e8b925b0 100644 --- a/frontend/src/components/ChatHeader.tsx +++ b/frontend/src/components/ChatHeader.tsx @@ -349,7 +349,11 @@ export function ChatHeader({ type="button" title="Authenticate with Claude via OAuth" onClick={() => { - window.open("/oauth/authorize", "_blank", "noopener,noreferrer"); + window.open( + "/oauth/authorize", + "_blank", + "noopener,noreferrer", + ); }} style={{ padding: "6px 12px", diff --git a/frontend/src/components/selection/SelectionScreen.tsx b/frontend/src/components/selection/SelectionScreen.tsx index 9b3a637e..5274f760 100644 --- a/frontend/src/components/selection/SelectionScreen.tsx +++ b/frontend/src/components/selection/SelectionScreen.tsx @@ -66,7 +66,11 @@ export function SelectionScreen({ ) : ( >, slack_ctx: Option>, + port: u16, ) -> impl poem::Endpoint { let ctx_arc = std::sync::Arc::new(ctx); let (api_service, docs_service) = build_openapi_service(ctx_arc.clone()); - let oauth_state = Arc::new(oauth::OAuthState::new(resolve_port())); + let oauth_state = Arc::new(oauth::OAuthState::new(port)); let mut route = Route::new() .nest("/api", api_service) @@ -236,6 +237,15 @@ mod tests { fn build_routes_constructs_without_panic() { let tmp = tempfile::tempdir().unwrap(); let ctx = context::AppContext::new_test(tmp.path().to_path_buf()); - let _endpoint = build_routes(ctx, None, None); + let _endpoint = build_routes(ctx, None, None, 3001); + } + + #[test] + fn build_routes_accepts_custom_port() { + // Verify build_routes compiles and runs with a non-default port, + // ensuring the port parameter flows through to OAuthState. + let tmp = tempfile::tempdir().unwrap(); + let ctx = context::AppContext::new_test(tmp.path().to_path_buf()); + let _endpoint = build_routes(ctx, None, None, 9999); } } diff --git a/server/src/http/oauth.rs b/server/src/http/oauth.rs index 6d83d52b..8932e1ae 100644 --- a/server/src/http/oauth.rs +++ b/server/src/http/oauth.rs @@ -423,6 +423,13 @@ mod tests { assert_eq!(state.callback_url(), "http://localhost:3001/callback"); } + #[test] + fn oauth_state_callback_url_uses_given_port() { + // Ensure OAuthState::new uses the port passed to it, not a hardcoded value. + let state = OAuthState::new(9876); + assert_eq!(state.callback_url(), "http://localhost:9876/callback"); + } + #[tokio::test] async fn html_response_contains_title_and_message() { let resp = html_response(StatusCode::OK, "Test Title", "Test message"); diff --git a/server/src/main.rs b/server/src/main.rs index 4bd05f27..d957c081 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -495,7 +495,7 @@ async fn main() -> Result<(), std::io::Error> { matrix_shutdown_tx: Some(Arc::clone(&matrix_shutdown_tx)), }; - let app = build_routes(ctx, whatsapp_ctx.clone(), slack_ctx.clone()); + let app = build_routes(ctx, whatsapp_ctx.clone(), slack_ctx.clone(), port); // Optional Matrix bot: connect to the homeserver and start listening for // messages if `.storkit/bot.toml` is present and enabled.