Skip to content

Commit 0504041

Browse files
authored
Cloud: support alternate auth token from environment (#5323)
1 parent 70726bc commit 0504041

File tree

12 files changed

+323
-46
lines changed

12 files changed

+323
-46
lines changed

packages/cloud/src/CloudService.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import type {
1010
import { TelemetryService } from "@roo-code/telemetry"
1111

1212
import { CloudServiceCallbacks } from "./types"
13-
import { AuthService } from "./AuthService"
13+
import type { AuthService } from "./auth"
14+
import { WebAuthService, StaticTokenAuthService } from "./auth"
1415
import { SettingsService } from "./SettingsService"
1516
import { TelemetryClient } from "./TelemetryClient"
1617
import { ShareService, TaskNotFoundError } from "./ShareService"
@@ -43,7 +44,13 @@ export class CloudService {
4344
}
4445

4546
try {
46-
this.authService = new AuthService(this.context, this.log)
47+
const cloudToken = process.env.ROO_CODE_CLOUD_TOKEN
48+
if (cloudToken && cloudToken.length > 0) {
49+
this.authService = new StaticTokenAuthService(this.context, cloudToken, this.log)
50+
} else {
51+
this.authService = new WebAuthService(this.context, this.log)
52+
}
53+
4754
await this.authService.initialize()
4855

4956
this.authService.on("attempting-session", this.authListener)

packages/cloud/src/SettingsService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from "@roo-code/types"
99

1010
import { getRooCodeApiUrl } from "./Config"
11-
import { AuthService } from "./AuthService"
11+
import type { AuthService } from "./auth"
1212
import { RefreshTimer } from "./RefreshTimer"
1313

1414
const ORGANIZATION_SETTINGS_CACHE_KEY = "organization-settings"

packages/cloud/src/ShareService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as vscode from "vscode"
22

33
import { shareResponseSchema } from "@roo-code/types"
44
import { getRooCodeApiUrl } from "./Config"
5-
import type { AuthService } from "./AuthService"
5+
import type { AuthService } from "./auth"
66
import type { SettingsService } from "./SettingsService"
77
import { getUserAgent } from "./utils"
88

packages/cloud/src/TelemetryClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
import { BaseTelemetryClient } from "@roo-code/telemetry"
88

99
import { getRooCodeApiUrl } from "./Config"
10-
import { AuthService } from "./AuthService"
10+
import type { AuthService } from "./auth"
1111
import { SettingsService } from "./SettingsService"
1212

1313
export class TelemetryClient extends BaseTelemetryClient {

packages/cloud/src/__tests__/CloudService.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as vscode from "vscode"
44
import type { ClineMessage } from "@roo-code/types"
55

66
import { CloudService } from "../CloudService"
7-
import { AuthService } from "../AuthService"
7+
import { WebAuthService } from "../auth/WebAuthService"
88
import { SettingsService } from "../SettingsService"
99
import { ShareService, TaskNotFoundError } from "../ShareService"
1010
import { TelemetryClient } from "../TelemetryClient"
@@ -27,7 +27,7 @@ vi.mock("vscode", () => ({
2727

2828
vi.mock("@roo-code/telemetry")
2929

30-
vi.mock("../AuthService")
30+
vi.mock("../auth/WebAuthService")
3131

3232
vi.mock("../SettingsService")
3333

@@ -149,7 +149,7 @@ describe("CloudService", () => {
149149
},
150150
}
151151

152-
vi.mocked(AuthService).mockImplementation(() => mockAuthService as unknown as AuthService)
152+
vi.mocked(WebAuthService).mockImplementation(() => mockAuthService as unknown as WebAuthService)
153153
vi.mocked(SettingsService).mockImplementation(() => mockSettingsService as unknown as SettingsService)
154154
vi.mocked(ShareService).mockImplementation(() => mockShareService as unknown as ShareService)
155155
vi.mocked(TelemetryClient).mockImplementation(() => mockTelemetryClient as unknown as TelemetryClient)
@@ -175,7 +175,7 @@ describe("CloudService", () => {
175175
const cloudService = await CloudService.createInstance(mockContext, callbacks)
176176

177177
expect(cloudService).toBeInstanceOf(CloudService)
178-
expect(AuthService).toHaveBeenCalledWith(mockContext, expect.any(Function))
178+
expect(WebAuthService).toHaveBeenCalledWith(mockContext, expect.any(Function))
179179
expect(SettingsService).toHaveBeenCalledWith(
180180
mockContext,
181181
mockAuthService,

packages/cloud/src/__tests__/ShareService.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { MockedFunction } from "vitest"
44
import * as vscode from "vscode"
55

66
import { ShareService, TaskNotFoundError } from "../ShareService"
7-
import type { AuthService } from "../AuthService"
7+
import type { AuthService } from "../auth"
88
import type { SettingsService } from "../SettingsService"
99

1010
// Mock fetch
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import { describe, it, expect, beforeEach, vi } from "vitest"
2+
import * as vscode from "vscode"
3+
4+
import { StaticTokenAuthService } from "../../auth/StaticTokenAuthService"
5+
6+
// Mock vscode
7+
vi.mock("vscode", () => ({
8+
window: {
9+
showInformationMessage: vi.fn(),
10+
},
11+
env: {
12+
openExternal: vi.fn(),
13+
uriScheme: "vscode",
14+
},
15+
Uri: {
16+
parse: vi.fn(),
17+
},
18+
}))
19+
20+
describe("StaticTokenAuthService", () => {
21+
let authService: StaticTokenAuthService
22+
let mockContext: vscode.ExtensionContext
23+
let mockLog: (...args: unknown[]) => void
24+
const testToken = "test-static-token"
25+
26+
beforeEach(() => {
27+
mockLog = vi.fn()
28+
29+
// Create a minimal mock that satisfies the constructor requirements
30+
const mockContextPartial = {
31+
extension: {
32+
packageJSON: {
33+
publisher: "TestPublisher",
34+
name: "test-extension",
35+
},
36+
},
37+
globalState: {
38+
get: vi.fn(),
39+
update: vi.fn(),
40+
},
41+
secrets: {
42+
get: vi.fn(),
43+
store: vi.fn(),
44+
delete: vi.fn(),
45+
onDidChange: vi.fn(),
46+
},
47+
subscriptions: [],
48+
}
49+
50+
// Use type assertion for test mocking
51+
mockContext = mockContextPartial as unknown as vscode.ExtensionContext
52+
53+
authService = new StaticTokenAuthService(mockContext, testToken, mockLog)
54+
})
55+
56+
afterEach(() => {
57+
vi.clearAllMocks()
58+
})
59+
60+
describe("constructor", () => {
61+
it("should create instance and log static token mode", () => {
62+
expect(authService).toBeInstanceOf(StaticTokenAuthService)
63+
expect(mockLog).toHaveBeenCalledWith("[auth] Using static token authentication mode")
64+
})
65+
66+
it("should use console.log as default logger", () => {
67+
const serviceWithoutLog = new StaticTokenAuthService(
68+
mockContext as unknown as vscode.ExtensionContext,
69+
testToken,
70+
)
71+
// Can't directly test console.log usage, but constructor should not throw
72+
expect(serviceWithoutLog).toBeInstanceOf(StaticTokenAuthService)
73+
})
74+
})
75+
76+
describe("initialize", () => {
77+
it("should start in active-session state", async () => {
78+
await authService.initialize()
79+
expect(authService.getState()).toBe("active-session")
80+
})
81+
82+
it("should emit active-session event on initialize", async () => {
83+
const spy = vi.fn()
84+
authService.on("active-session", spy)
85+
86+
await authService.initialize()
87+
88+
expect(spy).toHaveBeenCalledWith({ previousState: "initializing" })
89+
})
90+
91+
it("should log successful initialization", async () => {
92+
await authService.initialize()
93+
expect(mockLog).toHaveBeenCalledWith("[auth] Static token auth service initialized in active-session state")
94+
})
95+
})
96+
97+
describe("getSessionToken", () => {
98+
it("should return the provided token", () => {
99+
expect(authService.getSessionToken()).toBe(testToken)
100+
})
101+
102+
it("should return different token when constructed with different token", () => {
103+
const differentToken = "different-token"
104+
const differentService = new StaticTokenAuthService(mockContext, differentToken, mockLog)
105+
expect(differentService.getSessionToken()).toBe(differentToken)
106+
})
107+
})
108+
109+
describe("getUserInfo", () => {
110+
it("should return empty object", () => {
111+
expect(authService.getUserInfo()).toEqual({})
112+
})
113+
})
114+
115+
describe("getStoredOrganizationId", () => {
116+
it("should return null", () => {
117+
expect(authService.getStoredOrganizationId()).toBeNull()
118+
})
119+
})
120+
121+
describe("authentication state methods", () => {
122+
it("should always return true for isAuthenticated", () => {
123+
expect(authService.isAuthenticated()).toBe(true)
124+
})
125+
126+
it("should always return true for hasActiveSession", () => {
127+
expect(authService.hasActiveSession()).toBe(true)
128+
})
129+
130+
it("should always return true for hasOrIsAcquiringActiveSession", () => {
131+
expect(authService.hasOrIsAcquiringActiveSession()).toBe(true)
132+
})
133+
134+
it("should return active-session for getState", () => {
135+
expect(authService.getState()).toBe("active-session")
136+
})
137+
})
138+
139+
describe("disabled authentication methods", () => {
140+
const expectedErrorMessage = "Authentication methods are disabled in StaticTokenAuthService"
141+
142+
it("should throw error for login", async () => {
143+
await expect(authService.login()).rejects.toThrow(expectedErrorMessage)
144+
})
145+
146+
it("should throw error for logout", async () => {
147+
await expect(authService.logout()).rejects.toThrow(expectedErrorMessage)
148+
})
149+
150+
it("should throw error for handleCallback", async () => {
151+
await expect(authService.handleCallback("code", "state")).rejects.toThrow(expectedErrorMessage)
152+
})
153+
154+
it("should throw error for handleCallback with organization", async () => {
155+
await expect(authService.handleCallback("code", "state", "org_123")).rejects.toThrow(expectedErrorMessage)
156+
})
157+
})
158+
159+
describe("event emission", () => {
160+
it("should be able to register and emit events", async () => {
161+
const activeSessionSpy = vi.fn()
162+
const userInfoSpy = vi.fn()
163+
164+
authService.on("active-session", activeSessionSpy)
165+
authService.on("user-info", userInfoSpy)
166+
167+
await authService.initialize()
168+
169+
expect(activeSessionSpy).toHaveBeenCalledWith({ previousState: "initializing" })
170+
// user-info event is not emitted in static token mode
171+
expect(userInfoSpy).not.toHaveBeenCalled()
172+
})
173+
})
174+
})

0 commit comments

Comments
 (0)