mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
Scope dev RCC credentials by dev url (#4922)
This commit is contained in:
parent
68e38e8db8
commit
b31250d128
2 changed files with 154 additions and 9 deletions
|
|
@ -6,7 +6,7 @@ import { z } from "zod"
|
|||
|
||||
import type { CloudUserInfo, CloudOrganizationMembership } from "@roo-code/types"
|
||||
|
||||
import { getClerkBaseUrl, getRooCodeApiUrl } from "./Config"
|
||||
import { getClerkBaseUrl, getRooCodeApiUrl, PRODUCTION_CLERK_BASE_URL } from "./Config"
|
||||
import { RefreshTimer } from "./RefreshTimer"
|
||||
import { getUserAgent } from "./utils"
|
||||
|
||||
|
|
@ -24,7 +24,6 @@ const authCredentialsSchema = z.object({
|
|||
|
||||
type AuthCredentials = z.infer<typeof authCredentialsSchema>
|
||||
|
||||
const AUTH_CREDENTIALS_KEY = "clerk-auth-credentials"
|
||||
const AUTH_STATE_KEY = "clerk-auth-state"
|
||||
|
||||
type AuthState = "initializing" | "logged-out" | "active-session" | "inactive-session"
|
||||
|
|
@ -89,6 +88,7 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
|
|||
private timer: RefreshTimer
|
||||
private state: AuthState = "initializing"
|
||||
private log: (...args: unknown[]) => void
|
||||
private readonly authCredentialsKey: string
|
||||
|
||||
private credentials: AuthCredentials | null = null
|
||||
private sessionToken: string | null = null
|
||||
|
|
@ -100,6 +100,14 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
|
|||
this.context = context
|
||||
this.log = log || console.log
|
||||
|
||||
// Calculate auth credentials key based on Clerk base URL
|
||||
const clerkBaseUrl = getClerkBaseUrl()
|
||||
if (clerkBaseUrl !== PRODUCTION_CLERK_BASE_URL) {
|
||||
this.authCredentialsKey = `clerk-auth-credentials-${clerkBaseUrl}`
|
||||
} else {
|
||||
this.authCredentialsKey = "clerk-auth-credentials"
|
||||
}
|
||||
|
||||
this.timer = new RefreshTimer({
|
||||
callback: async () => {
|
||||
await this.refreshSession()
|
||||
|
|
@ -180,7 +188,7 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
|
|||
|
||||
this.context.subscriptions.push(
|
||||
this.context.secrets.onDidChange((e) => {
|
||||
if (e.key === AUTH_CREDENTIALS_KEY) {
|
||||
if (e.key === this.authCredentialsKey) {
|
||||
this.handleCredentialsChange()
|
||||
}
|
||||
}),
|
||||
|
|
@ -188,11 +196,11 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
|
|||
}
|
||||
|
||||
private async storeCredentials(credentials: AuthCredentials): Promise<void> {
|
||||
await this.context.secrets.store(AUTH_CREDENTIALS_KEY, JSON.stringify(credentials))
|
||||
await this.context.secrets.store(this.authCredentialsKey, JSON.stringify(credentials))
|
||||
}
|
||||
|
||||
private async loadCredentials(): Promise<AuthCredentials | null> {
|
||||
const credentialsJson = await this.context.secrets.get(AUTH_CREDENTIALS_KEY)
|
||||
const credentialsJson = await this.context.secrets.get(this.authCredentialsKey)
|
||||
if (!credentialsJson) return null
|
||||
|
||||
try {
|
||||
|
|
@ -209,7 +217,7 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
|
|||
}
|
||||
|
||||
private async clearCredentials(): Promise<void> {
|
||||
await this.context.secrets.delete(AUTH_CREDENTIALS_KEY)
|
||||
await this.context.secrets.delete(this.authCredentialsKey)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -99,8 +99,8 @@ describe("AuthService", () => {
|
|||
}
|
||||
vi.mocked(RefreshTimer).mockImplementation(() => mockTimer as unknown as RefreshTimer)
|
||||
|
||||
// Setup config mocks
|
||||
vi.mocked(Config.getClerkBaseUrl).mockReturnValue("https://clerk.test.com")
|
||||
// Setup config mocks - use production URL by default to maintain existing test behavior
|
||||
vi.mocked(Config.getClerkBaseUrl).mockReturnValue("https://clerk.roocode.com")
|
||||
vi.mocked(Config.getRooCodeApiUrl).mockReturnValue("https://api.test.com")
|
||||
|
||||
// Setup utils mock
|
||||
|
|
@ -377,7 +377,7 @@ describe("AuthService", () => {
|
|||
expect(mockContext.secrets.delete).toHaveBeenCalledWith("clerk-auth-credentials")
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith("clerk-auth-state", undefined)
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
"https://clerk.test.com/v1/client/sessions/test-session/remove",
|
||||
"https://clerk.roocode.com/v1/client/sessions/test-session/remove",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: expect.objectContaining({
|
||||
|
|
@ -812,4 +812,141 @@ describe("AuthService", () => {
|
|||
expect(mockTimer.start).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("auth credentials key scoping", () => {
|
||||
it("should use default key when getClerkBaseUrl returns production URL", async () => {
|
||||
// Mock getClerkBaseUrl to return production URL
|
||||
vi.mocked(Config.getClerkBaseUrl).mockReturnValue("https://clerk.roocode.com")
|
||||
|
||||
const service = new AuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
|
||||
const credentials = { clientToken: "test-token", sessionId: "test-session" }
|
||||
|
||||
await service.initialize()
|
||||
await service["storeCredentials"](credentials)
|
||||
|
||||
expect(mockContext.secrets.store).toHaveBeenCalledWith(
|
||||
"clerk-auth-credentials",
|
||||
JSON.stringify(credentials),
|
||||
)
|
||||
})
|
||||
|
||||
it("should use scoped key when getClerkBaseUrl returns custom URL", async () => {
|
||||
const customUrl = "https://custom.clerk.com"
|
||||
// Mock getClerkBaseUrl to return custom URL
|
||||
vi.mocked(Config.getClerkBaseUrl).mockReturnValue(customUrl)
|
||||
|
||||
const service = new AuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
|
||||
const credentials = { clientToken: "test-token", sessionId: "test-session" }
|
||||
|
||||
await service.initialize()
|
||||
await service["storeCredentials"](credentials)
|
||||
|
||||
expect(mockContext.secrets.store).toHaveBeenCalledWith(
|
||||
`clerk-auth-credentials-${customUrl}`,
|
||||
JSON.stringify(credentials),
|
||||
)
|
||||
})
|
||||
|
||||
it("should load credentials using scoped key", async () => {
|
||||
const customUrl = "https://custom.clerk.com"
|
||||
vi.mocked(Config.getClerkBaseUrl).mockReturnValue(customUrl)
|
||||
|
||||
const service = new AuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
|
||||
const credentials = { clientToken: "test-token", sessionId: "test-session" }
|
||||
mockContext.secrets.get.mockResolvedValue(JSON.stringify(credentials))
|
||||
|
||||
await service.initialize()
|
||||
const loadedCredentials = await service["loadCredentials"]()
|
||||
|
||||
expect(mockContext.secrets.get).toHaveBeenCalledWith(`clerk-auth-credentials-${customUrl}`)
|
||||
expect(loadedCredentials).toEqual(credentials)
|
||||
})
|
||||
|
||||
it("should clear credentials using scoped key", async () => {
|
||||
const customUrl = "https://custom.clerk.com"
|
||||
vi.mocked(Config.getClerkBaseUrl).mockReturnValue(customUrl)
|
||||
|
||||
const service = new AuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
|
||||
|
||||
await service.initialize()
|
||||
await service["clearCredentials"]()
|
||||
|
||||
expect(mockContext.secrets.delete).toHaveBeenCalledWith(`clerk-auth-credentials-${customUrl}`)
|
||||
})
|
||||
|
||||
it("should listen for changes on scoped key", async () => {
|
||||
const customUrl = "https://custom.clerk.com"
|
||||
vi.mocked(Config.getClerkBaseUrl).mockReturnValue(customUrl)
|
||||
|
||||
let onDidChangeCallback: (e: { key: string }) => void
|
||||
|
||||
mockContext.secrets.onDidChange.mockImplementation((callback: (e: { key: string }) => void) => {
|
||||
onDidChangeCallback = callback
|
||||
return { dispose: vi.fn() }
|
||||
})
|
||||
|
||||
const service = new AuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
|
||||
await service.initialize()
|
||||
|
||||
// Simulate credentials change event with scoped key
|
||||
const newCredentials = { clientToken: "new-token", sessionId: "new-session" }
|
||||
mockContext.secrets.get.mockResolvedValue(JSON.stringify(newCredentials))
|
||||
|
||||
const inactiveSessionSpy = vi.fn()
|
||||
service.on("inactive-session", inactiveSessionSpy)
|
||||
|
||||
onDidChangeCallback!({ key: `clerk-auth-credentials-${customUrl}` })
|
||||
await new Promise((resolve) => setTimeout(resolve, 0)) // Wait for async handling
|
||||
|
||||
expect(inactiveSessionSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should not respond to changes on different scoped keys", async () => {
|
||||
const customUrl = "https://custom.clerk.com"
|
||||
vi.mocked(Config.getClerkBaseUrl).mockReturnValue(customUrl)
|
||||
|
||||
let onDidChangeCallback: (e: { key: string }) => void
|
||||
|
||||
mockContext.secrets.onDidChange.mockImplementation((callback: (e: { key: string }) => void) => {
|
||||
onDidChangeCallback = callback
|
||||
return { dispose: vi.fn() }
|
||||
})
|
||||
|
||||
const service = new AuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
|
||||
await service.initialize()
|
||||
|
||||
const inactiveSessionSpy = vi.fn()
|
||||
service.on("inactive-session", inactiveSessionSpy)
|
||||
|
||||
// Simulate credentials change event with different scoped key
|
||||
onDidChangeCallback!({ key: "clerk-auth-credentials-https://other.clerk.com" })
|
||||
await new Promise((resolve) => setTimeout(resolve, 0)) // Wait for async handling
|
||||
|
||||
expect(inactiveSessionSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should not respond to changes on default key when using scoped key", async () => {
|
||||
const customUrl = "https://custom.clerk.com"
|
||||
vi.mocked(Config.getClerkBaseUrl).mockReturnValue(customUrl)
|
||||
|
||||
let onDidChangeCallback: (e: { key: string }) => void
|
||||
|
||||
mockContext.secrets.onDidChange.mockImplementation((callback: (e: { key: string }) => void) => {
|
||||
onDidChangeCallback = callback
|
||||
return { dispose: vi.fn() }
|
||||
})
|
||||
|
||||
const service = new AuthService(mockContext as unknown as vscode.ExtensionContext, mockLog)
|
||||
await service.initialize()
|
||||
|
||||
const inactiveSessionSpy = vi.fn()
|
||||
service.on("inactive-session", inactiveSessionSpy)
|
||||
|
||||
// Simulate credentials change event with default key
|
||||
onDidChangeCallback!({ key: "clerk-auth-credentials" })
|
||||
await new Promise((resolve) => setTimeout(resolve, 0)) // Wait for async handling
|
||||
|
||||
expect(inactiveSessionSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue