@@ -24,8 +24,7 @@ function stubFetch(response: unknown, { ok = true, status = 200 } = {}): void {
2424}
2525
2626describe ( "startDeviceAuth" , ( ) => {
27- test ( "constructs the openclaw-advisor verification URL from the returned code, ignoring the server-returned verificationUrl" , async ( ) => {
28- // Server returns a legacy /device-auth?code=... URL — plugin should ignore it.
27+ test ( "rewrites the path on the server-provided URL to /openclaw-advisor" , async ( ) => {
2928 stubFetch ( {
3029 code : "ABCD-1234" ,
3130 verificationUrl : "https://app.kilo.ai/device-auth?code=ABCD-1234" ,
@@ -42,7 +41,24 @@ describe("startDeviceAuth", () => {
4241 expect ( result . expiresIn ) . toBe ( 600 ) ;
4342 } ) ;
4443
45- test ( "uses the caller-provided apiBase for the verification URL (dev loop)" , async ( ) => {
44+ test ( "preserves the server-provided origin, not apiBase, when they differ (prod)" , async ( ) => {
45+ // Regression: in production, apiBase is the API host (api.kilo.ai) but
46+ // the server builds verificationUrl from APP_URL (app.kilo.ai). Rebuilding
47+ // the link from apiBase would send users to a nonexistent endpoint.
48+ stubFetch ( {
49+ code : "QWER-7890" ,
50+ verificationUrl : "https://app.kilo.ai/device-auth?code=QWER-7890" ,
51+ expiresIn : 600 ,
52+ } ) ;
53+
54+ const result = await startDeviceAuth ( "https://api.kilo.ai" ) ;
55+
56+ expect ( result . verificationUrl ) . toBe (
57+ "https://app.kilo.ai/openclaw-advisor?code=QWER-7890" ,
58+ ) ;
59+ } ) ;
60+
61+ test ( "preserves the dev-loop origin (host.docker.internal / localhost)" , async ( ) => {
4662 stubFetch ( {
4763 code : "WXYZ-5678" ,
4864 verificationUrl :
@@ -57,19 +73,20 @@ describe("startDeviceAuth", () => {
5773 ) ;
5874 } ) ;
5975
60- test ( "url-encodes the code to defend against unexpected server responses " , async ( ) => {
61- // Defense-in-depth: even if the server returned a malformed code, we must
62- // not inject unescaped query chars into the verification URL .
76+ test ( "preserves the ? code= query verbatim from the server-provided URL " , async ( ) => {
77+ // The server is the source of truth for the query string. We only swap
78+ // the pathname; we never reconstruct the query from the bare `code` field .
6379 stubFetch ( {
64- code : "A&B=C D" ,
65- verificationUrl : "ignored" ,
80+ code : "UVWX-9999" ,
81+ verificationUrl :
82+ "https://app.kilo.ai/device-auth?code=UVWX-9999&state=extra" ,
6683 expiresIn : 600 ,
6784 } ) ;
6885
69- const result = await startDeviceAuth ( "https://app .kilo.ai" ) ;
86+ const result = await startDeviceAuth ( "https://api .kilo.ai" ) ;
7087
7188 expect ( result . verificationUrl ) . toBe (
72- "https://app.kilo.ai/openclaw-advisor?code=A%26B%3DC%20D " ,
89+ "https://app.kilo.ai/openclaw-advisor?code=UVWX-9999&state=extra " ,
7390 ) ;
7491 } ) ;
7592
0 commit comments