Compare commits

...

1 Commits

Author SHA1 Message Date
abeatrix f2dc9c8482 fix: extension auth flow
- Remove provider parameter from handleAuthCallback methods across auth services and controller.
- Accepts Firebase JWT token that can be refreshed instead of oauth token from provider
2025-08-02 17:54:58 -07:00
5 changed files with 16 additions and 56 deletions
+2 -2
View File
@@ -342,9 +342,9 @@ export class Controller {
}
}
async handleAuthCallback(customToken: string, provider: string | null = null) {
async handleAuthCallback(firebaseJwtToken: string) {
try {
await this.authService.handleAuthCallback(customToken, provider ? provider : "google")
await this.authService.handleAuthCallback(firebaseJwtToken)
const clineProvider: ApiProvider = "cline"
+2 -2
View File
@@ -225,13 +225,13 @@ export class AuthService {
}
}
async handleAuthCallback(token: string, provider: string): Promise<void> {
async handleAuthCallback(token: string): Promise<void> {
if (!this._provider) {
throw new Error("Auth provider is not set")
}
try {
this._clineAuthInfo = await this._provider.provider.signIn(this._controller, token, provider)
this._clineAuthInfo = await this._provider.provider.signIn(this._controller, token)
this._authenticated = true
if (this._clineAuthInfo) {
+2 -2
View File
@@ -102,7 +102,7 @@ export class AuthServiceMock extends AuthService {
console.log(`Successfully authenticated with mock server as ${userData.displayName} (${userData.email})`)
const visibleWebview = WebviewProvider.getVisibleInstance()
await visibleWebview?.controller.handleAuthCallback(testToken, "mock")
await visibleWebview?.controller.handleAuthCallback(testToken)
} catch (error) {
console.error("Error signing in with mock server:", error)
this._authenticated = false
@@ -113,7 +113,7 @@ export class AuthServiceMock extends AuthService {
return String.create({ value: authUrlString })
}
override async handleAuthCallback(_token: string, _provider: string): Promise<void> {
override async handleAuthCallback(_token: string): Promise<void> {
try {
this._authenticated = true
await this.sendAuthStatusUpdate()
@@ -1,13 +1,9 @@
import { getSecret, storeSecret } from "@/core/storage/state"
import { ErrorService } from "@/services/error/ErrorService"
import axios from "axios"
import { initializeApp } from "firebase/app"
import { GithubAuthProvider, GoogleAuthProvider, User, getAuth, signInWithCredential } from "firebase/auth"
import { ExtensionContext } from "vscode"
import { ClineAccountUserInfo, ClineAuthInfo } from "../AuthService"
import { jwtDecode } from "jwt-decode"
import { clineEnvConfig } from "@/config"
import { Controller } from "@/core/controller"
import type { Controller } from "@/core/controller"
import { ErrorService } from "@/services/error/ErrorService"
import type { ClineAccountUserInfo, ClineAuthInfo } from "../AuthService"
export class FirebaseAuthProvider {
private _config: any
@@ -39,7 +35,7 @@ export class FirebaseAuthProvider {
/**
* Restores the authentication token using a provided token.
* @param token - The authentication token to restore.
* @returns {Promise<User>} A promise that resolves with the authenticated user.
* @returns {Promise<ClineAuthInfo | null>} A promise that resolves with the authenticated user.
* @throws {Error} Throws an error if the restoration fails.
*/
async retrieveClineAuthInfo(controller: Controller): Promise<ClineAuthInfo | null> {
@@ -49,23 +45,8 @@ export class FirebaseAuthProvider {
return null
}
try {
// Exchange refresh token for new access token using Firebase's secure token endpoint
// https://stackoverflow.com/questions/38233687/how-to-use-the-firebase-refreshtoken-to-reauthenticate/57119131#57119131
const firebaseApiKey = this._config.apiKey
const googleAccessTokenResponse = await axios.post(
`https://securetoken.googleapis.com/v1/token?key=${firebaseApiKey}`,
`grant_type=refresh_token&refresh_token=${encodeURIComponent(userRefreshToken)}`,
{
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
},
)
// console.log("googleAccessTokenResponse", googleAccessTokenResponse)
// This returns an object with access_token, expires_in (3600), id_token (can be used as bearer token to authenticate requests, we'll use this in the future instead of firebase but need to be aware of how we use firebase sdk for e.g. user info like the profile image), project_id, refresh_token, token_type (always Bearer), and user_id
const idToken = googleAccessTokenResponse.data.id_token
const idToken = userRefreshToken
// const idTokenExpirationDate = new Date(Date.now() + googleAccessTokenResponse.data.expires_in * 1000)
// Now retrieve the user info from the backend (this was an easy solution to keep providing user profile details like name and email, but we should move to using the fetchMe() function instead)
@@ -98,40 +79,20 @@ export class FirebaseAuthProvider {
/**
* Signs in the user using Firebase authentication with a custom token.
* @returns {Promise<User>} A promise that resolves with the authenticated user.
* @returns {Promise<ClineAuthInfo | null>} A promise that resolves with the authenticated user.
* @throws {Error} Throws an error if the sign-in fails.
*/
async signIn(controller: Controller, token: string, provider: string): Promise<ClineAuthInfo | null> {
async signIn(controller: Controller, token: string): Promise<ClineAuthInfo | null> {
try {
let credential
switch (provider) {
case "google":
credential = GoogleAuthProvider.credential(token)
break
case "github":
credential = GithubAuthProvider.credential(token)
break
default:
throw new Error(`Unsupported provider: ${provider}`)
}
// we've received the short-lived tokens from google/github, now we need to sign in to firebase with them
const firebaseConfig = Object.assign({}, this._config)
const app = initializeApp(firebaseConfig)
const auth = getAuth(app)
// this signs the user into firebase sdk internally
const userCredential = (await signInWithCredential(auth, credential)).user
// const userRefreshToken = await userCredential.getIdToken()
// store the long-lived refresh token in secret storage
try {
controller.cacheService.setSecret("clineAccountId", userCredential.refreshToken)
controller.cacheService.setSecret("clineAccountId", token)
} catch (error) {
ErrorService.logMessage("Firebase store token error", "error")
ErrorService.logException(error)
throw error
}
// userCredential = await this._signInWithCredential(context, credential)
return await this.retrieveClineAuthInfo(controller)
} catch (error) {
ErrorService.logMessage("Firebase sign-in error", "error")
+2 -3
View File
@@ -38,13 +38,12 @@ export class SharedUriHandler {
return false
}
case "/auth": {
console.log("SharedUriHandler: Auth callback received:", { path: uri.path, provider: query.get("provider") })
console.log("SharedUriHandler: Auth callback received:", { path: uri.path })
const token = query.get("idToken")
const provider = query.get("provider")
if (token) {
await visibleWebview.controller.handleAuthCallback(token, provider)
await visibleWebview.controller.handleAuthCallback(token)
return true
}
console.warn("SharedUriHandler: Missing idToken parameter for auth callback")