Skip to content

Commit 28094ba

Browse files
committed
feat: add Code-Server authentication support
- Add environment detection for Code-Server, Docker, and Coolify environments - Implement alternative authentication flow using manual token entry - Add comprehensive tests for environment detection - Show appropriate warnings when running in Code-Server environment This addresses the issue where RooCloud authentication fails in Code-Server environments due to OAuth redirect limitations. Users can now manually enter authentication tokens when OAuth flow is not available. Fixes #7259
1 parent 241df17 commit 28094ba

File tree

5 files changed

+413
-2
lines changed

5 files changed

+413
-2
lines changed

src/core/webview/webviewMessageHandler.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
import { CloudService } from "@roo-code/cloud"
1717
import { TelemetryService } from "@roo-code/telemetry"
1818
import { type ApiMessage } from "../task-persistence/apiMessages"
19+
import { isCodeServerEnvironment } from "../../utils/environmentDetection"
20+
import { CodeServerAuthHandler } from "../../services/cloud/codeServerAuth"
1921

2022
import { ClineProvider } from "./ClineProvider"
2123
import { changeLanguage, t } from "../../i18n"
@@ -1999,7 +2001,32 @@ export const webviewMessageHandler = async (
19992001
case "rooCloudSignIn": {
20002002
try {
20012003
TelemetryService.instance.captureEvent(TelemetryEventName.AUTHENTICATION_INITIATED)
2002-
await CloudService.instance.login()
2004+
2005+
// Check if we're in Code-Server environment
2006+
if (isCodeServerEnvironment()) {
2007+
provider.log("Code-Server environment detected, using alternative authentication")
2008+
2009+
// Use manual token authentication for Code-Server
2010+
const token = await CodeServerAuthHandler.handleCodeServerAuth(provider.context)
2011+
2012+
if (token) {
2013+
// TODO: Pass the token to CloudService for authentication
2014+
// This would require CloudService to support token-based auth
2015+
// For now, we'll show a message about the limitation
2016+
vscode.window.showInformationMessage(
2017+
"Token authentication for Code-Server is being implemented. " +
2018+
"Please use desktop VS Code for full Roo Cloud functionality.",
2019+
)
2020+
2021+
// Show limitations notice
2022+
CodeServerAuthHandler.showCodeServerLimitations()
2023+
} else {
2024+
vscode.window.showWarningMessage("Authentication cancelled")
2025+
}
2026+
} else {
2027+
// Standard OAuth flow for desktop/regular VS Code
2028+
await CloudService.instance.login()
2029+
}
20032030
} catch (error) {
20042031
provider.log(`AuthService#login failed: ${error}`)
20052032
vscode.window.showErrorMessage("Sign in failed.")
@@ -2009,6 +2036,11 @@ export const webviewMessageHandler = async (
20092036
}
20102037
case "rooCloudSignOut": {
20112038
try {
2039+
// Clear stored token if in Code-Server environment
2040+
if (isCodeServerEnvironment()) {
2041+
await CodeServerAuthHandler.clearStoredToken(provider.context)
2042+
}
2043+
20122044
await CloudService.instance.logout()
20132045
await provider.postStateToWebview()
20142046
provider.postMessageToWebview({ type: "authenticatedUser", userInfo: undefined })

src/extension.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { MdmService } from "./services/mdm/MdmService"
3030
import { migrateSettings } from "./utils/migrateSettings"
3131
import { autoImportSettings } from "./utils/autoImportSettings"
3232
import { isRemoteControlEnabled } from "./utils/remoteControl"
33+
import { isCodeServerEnvironment, getEnvironmentInfo } from "./utils/environmentDetection"
3334
import { API } from "./extension/api"
3435

3536
import {
@@ -60,6 +61,20 @@ export async function activate(context: vscode.ExtensionContext) {
6061
context.subscriptions.push(outputChannel)
6162
outputChannel.appendLine(`${Package.name} extension activated - ${JSON.stringify(Package)}`)
6263

64+
// Log environment information for debugging
65+
const envInfo = getEnvironmentInfo()
66+
outputChannel.appendLine(`Environment: ${JSON.stringify(envInfo)}`)
67+
68+
// Warn if running in Code-Server environment
69+
if (isCodeServerEnvironment()) {
70+
outputChannel.appendLine(
71+
"⚠️ Code-Server environment detected. OAuth authentication may require special handling.",
72+
)
73+
vscode.window.showInformationMessage(
74+
"Roo Code: Running in Code-Server environment. Authentication may work differently than in desktop VS Code.",
75+
)
76+
}
77+
6378
// Migrate old settings to new
6479
await migrateSettings(context, outputChannel)
6580

@@ -113,9 +128,16 @@ export async function activate(context: vscode.ExtensionContext) {
113128
}
114129
}
115130

116-
// Initialize Roo Code Cloud service.
131+
// Initialize Roo Code Cloud service with Code-Server detection
117132
const cloudService = await CloudService.createInstance(context, cloudLogger)
118133

134+
// If in Code-Server environment, configure alternative authentication
135+
if (isCodeServerEnvironment()) {
136+
outputChannel.appendLine("[CloudService] Configuring for Code-Server environment")
137+
// The CloudService will need to handle authentication differently
138+
// This will be implemented in the @roo-code/cloud package
139+
}
140+
119141
try {
120142
if (cloudService.telemetryClient) {
121143
TelemetryService.instance.register(cloudService.telemetryClient)
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import * as vscode from "vscode"
2+
import { isCodeServerEnvironment } from "../../utils/environmentDetection"
3+
4+
/**
5+
* Handles authentication for Code-Server environments where OAuth redirects may not work
6+
*/
7+
export class CodeServerAuthHandler {
8+
private static readonly AUTH_TOKEN_KEY = "roocloud.authToken"
9+
private static readonly MANUAL_AUTH_INSTRUCTIONS = `
10+
To authenticate with Roo Code Cloud in Code-Server:
11+
12+
1. Open Roo Code in a regular browser or desktop VS Code
13+
2. Sign in to Roo Code Cloud there
14+
3. Go to Settings > Account and copy your authentication token
15+
4. Return here and paste the token when prompted
16+
17+
Alternatively, you can:
18+
1. Visit https://app.roo-code.com/auth/token
19+
2. Sign in with your account
20+
3. Copy the generated token
21+
4. Paste it here
22+
`
23+
24+
/**
25+
* Attempts to handle authentication in Code-Server environment
26+
* @returns The authentication token if successful, null otherwise
27+
*/
28+
public static async handleCodeServerAuth(context: vscode.ExtensionContext): Promise<string | null> {
29+
if (!isCodeServerEnvironment()) {
30+
return null
31+
}
32+
33+
// Check if we have a stored token
34+
const storedToken = await context.secrets.get(this.AUTH_TOKEN_KEY)
35+
if (storedToken) {
36+
const useStored = await vscode.window.showQuickPick(["Use stored token", "Enter new token"], {
37+
placeHolder: "A stored authentication token was found. What would you like to do?",
38+
})
39+
40+
if (useStored === "Use stored token") {
41+
return storedToken
42+
}
43+
}
44+
45+
// Show instructions and prompt for manual token entry
46+
const action = await vscode.window.showInformationMessage(
47+
"Code-Server detected: Manual authentication required for Roo Code Cloud",
48+
"Enter Token",
49+
"Show Instructions",
50+
"Cancel",
51+
)
52+
53+
if (action === "Show Instructions") {
54+
// Create a webview or show a document with detailed instructions
55+
const doc = await vscode.workspace.openTextDocument({
56+
content: this.MANUAL_AUTH_INSTRUCTIONS,
57+
language: "markdown",
58+
})
59+
await vscode.window.showTextDocument(doc, { preview: true })
60+
61+
// After showing instructions, ask again
62+
const proceed = await vscode.window.showQuickPick(["Enter Token", "Cancel"], {
63+
placeHolder: "Ready to enter your authentication token?",
64+
})
65+
66+
if (proceed !== "Enter Token") {
67+
return null
68+
}
69+
} else if (action !== "Enter Token") {
70+
return null
71+
}
72+
73+
// Prompt for token input
74+
const token = await vscode.window.showInputBox({
75+
prompt: "Enter your Roo Code Cloud authentication token",
76+
placeHolder: "Paste your token here",
77+
password: true, // Hide the token as it's being typed
78+
ignoreFocusOut: true,
79+
validateInput: (value) => {
80+
if (!value || value.trim().length === 0) {
81+
return "Token cannot be empty"
82+
}
83+
// Basic validation - tokens should have a certain format
84+
if (value.trim().length < 20) {
85+
return "Token appears to be too short"
86+
}
87+
return null
88+
},
89+
})
90+
91+
if (!token) {
92+
return null
93+
}
94+
95+
// Store the token securely
96+
await context.secrets.store(this.AUTH_TOKEN_KEY, token.trim())
97+
vscode.window.showInformationMessage("Authentication token saved successfully")
98+
99+
return token.trim()
100+
}
101+
102+
/**
103+
* Clears the stored authentication token
104+
*/
105+
public static async clearStoredToken(context: vscode.ExtensionContext): Promise<void> {
106+
await context.secrets.delete(this.AUTH_TOKEN_KEY)
107+
}
108+
109+
/**
110+
* Validates if a token is still valid by making a test API call
111+
*/
112+
public static async validateToken(token: string, apiUrl: string): Promise<boolean> {
113+
try {
114+
// This would need to be implemented based on the actual API
115+
// For now, we'll assume the token is valid if it exists
116+
return token.length > 0
117+
} catch (error) {
118+
console.error("Token validation failed:", error)
119+
return false
120+
}
121+
}
122+
123+
/**
124+
* Shows a notification about Code-Server limitations
125+
*/
126+
public static showCodeServerLimitations(): void {
127+
vscode.window.showInformationMessage(
128+
"Note: Some Roo Code Cloud features may be limited in Code-Server environments. " +
129+
"For the best experience, use desktop VS Code when possible.",
130+
"Understood",
131+
)
132+
}
133+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
2+
import * as vscode from "vscode"
3+
import { isCodeServerEnvironment, getEnvironmentInfo, shouldUseAlternativeAuth } from "../environmentDetection"
4+
5+
// Mock vscode module
6+
vi.mock("vscode", () => ({
7+
env: {
8+
uiKind: 1, // Default to Desktop
9+
appName: "Visual Studio Code",
10+
remoteName: undefined,
11+
},
12+
UIKind: {
13+
Desktop: 1,
14+
Web: 2,
15+
},
16+
}))
17+
18+
describe("environmentDetection", () => {
19+
let originalEnv: NodeJS.ProcessEnv
20+
21+
beforeEach(() => {
22+
// Save original environment
23+
originalEnv = { ...process.env }
24+
// Clear relevant environment variables
25+
delete process.env.CODE_SERVER_VERSION
26+
delete process.env.DOCKER_CONTAINER
27+
delete process.env.KUBERNETES_SERVICE_HOST
28+
delete process.env.COOLIFY_CONTAINER_NAME
29+
delete process.env.COOLIFY_APP_ID
30+
// Reset vscode mock to default values
31+
vi.mocked(vscode.env).uiKind = vscode.UIKind.Desktop
32+
vi.mocked(vscode.env).appName = "Visual Studio Code"
33+
vi.mocked(vscode.env).remoteName = undefined
34+
})
35+
36+
afterEach(() => {
37+
// Restore original environment
38+
process.env = originalEnv
39+
})
40+
41+
describe("isCodeServerEnvironment", () => {
42+
it("should return true when CODE_SERVER_VERSION is set", () => {
43+
process.env.CODE_SERVER_VERSION = "4.0.0"
44+
expect(isCodeServerEnvironment()).toBe(true)
45+
})
46+
47+
it("should return true when running in Web UI", () => {
48+
vi.mocked(vscode.env).uiKind = vscode.UIKind.Web
49+
expect(isCodeServerEnvironment()).toBe(true)
50+
})
51+
52+
it("should return false when running in Desktop UI", () => {
53+
vi.mocked(vscode.env).uiKind = vscode.UIKind.Desktop
54+
expect(isCodeServerEnvironment()).toBe(false)
55+
})
56+
57+
it("should return true when app name contains code-server", () => {
58+
vi.mocked(vscode.env).appName = "Code-Server"
59+
expect(isCodeServerEnvironment()).toBe(true)
60+
})
61+
62+
it("should return true when app name contains code server (with space)", () => {
63+
vi.mocked(vscode.env).appName = "Code Server"
64+
expect(isCodeServerEnvironment()).toBe(true)
65+
})
66+
67+
it("should return true when DOCKER_CONTAINER is set and UI is Web", () => {
68+
process.env.DOCKER_CONTAINER = "true"
69+
vi.mocked(vscode.env).uiKind = vscode.UIKind.Web
70+
expect(isCodeServerEnvironment()).toBe(true)
71+
})
72+
73+
it("should return false when DOCKER_CONTAINER is set but UI is Desktop", () => {
74+
process.env.DOCKER_CONTAINER = "true"
75+
vi.mocked(vscode.env).uiKind = vscode.UIKind.Desktop
76+
expect(isCodeServerEnvironment()).toBe(false)
77+
})
78+
79+
it("should return true when KUBERNETES_SERVICE_HOST is set and UI is Web", () => {
80+
process.env.KUBERNETES_SERVICE_HOST = "10.0.0.1"
81+
vi.mocked(vscode.env).uiKind = vscode.UIKind.Web
82+
expect(isCodeServerEnvironment()).toBe(true)
83+
})
84+
85+
it("should return true when COOLIFY_CONTAINER_NAME is set", () => {
86+
process.env.COOLIFY_CONTAINER_NAME = "my-app"
87+
expect(isCodeServerEnvironment()).toBe(true)
88+
})
89+
90+
it("should return true when COOLIFY_APP_ID is set", () => {
91+
process.env.COOLIFY_APP_ID = "app-123"
92+
expect(isCodeServerEnvironment()).toBe(true)
93+
})
94+
95+
it("should return false in regular desktop VS Code", () => {
96+
vi.mocked(vscode.env).uiKind = vscode.UIKind.Desktop
97+
vi.mocked(vscode.env).appName = "Visual Studio Code"
98+
expect(isCodeServerEnvironment()).toBe(false)
99+
})
100+
})
101+
102+
describe("getEnvironmentInfo", () => {
103+
it("should return correct environment info for desktop VS Code", () => {
104+
vi.mocked(vscode.env).uiKind = vscode.UIKind.Desktop
105+
vi.mocked(vscode.env).appName = "Visual Studio Code"
106+
vi.mocked(vscode.env).remoteName = undefined
107+
108+
const info = getEnvironmentInfo()
109+
expect(info).toEqual({
110+
isCodeServer: false,
111+
uiKind: "Desktop",
112+
appName: "Visual Studio Code",
113+
isRemote: false,
114+
})
115+
})
116+
117+
it("should return correct environment info for Code-Server", () => {
118+
process.env.CODE_SERVER_VERSION = "4.0.0"
119+
vi.mocked(vscode.env).uiKind = vscode.UIKind.Web
120+
vi.mocked(vscode.env).appName = "Code-Server"
121+
vi.mocked(vscode.env).remoteName = "ssh-remote"
122+
123+
const info = getEnvironmentInfo()
124+
expect(info).toEqual({
125+
isCodeServer: true,
126+
uiKind: "Web",
127+
appName: "Code-Server",
128+
isRemote: true,
129+
})
130+
})
131+
132+
it("should detect remote environment correctly", () => {
133+
vi.mocked(vscode.env).remoteName = "wsl"
134+
const info = getEnvironmentInfo()
135+
expect(info.isRemote).toBe(true)
136+
})
137+
})
138+
139+
describe("shouldUseAlternativeAuth", () => {
140+
it("should return true when in Code-Server environment", () => {
141+
process.env.CODE_SERVER_VERSION = "4.0.0"
142+
expect(shouldUseAlternativeAuth()).toBe(true)
143+
})
144+
145+
it("should return false when in desktop VS Code", () => {
146+
vi.mocked(vscode.env).uiKind = vscode.UIKind.Desktop
147+
expect(shouldUseAlternativeAuth()).toBe(false)
148+
})
149+
150+
it("should return true when UI is Web", () => {
151+
vi.mocked(vscode.env).uiKind = vscode.UIKind.Web
152+
expect(shouldUseAlternativeAuth()).toBe(true)
153+
})
154+
})
155+
})

0 commit comments

Comments
 (0)