Skip to content

Commit

Permalink
cody: fix sign in issues with App (#54106)
Browse files Browse the repository at this point in the history
  • Loading branch information
abeatrix committed Jun 26, 2023
1 parent a43046e commit 15841e5
Show file tree
Hide file tree
Showing 18 changed files with 202 additions and 114 deletions.
3 changes: 3 additions & 0 deletions client/cody/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Starting from `0.2.0`, Cody is using `major.EVEN_NUMBER.patch` for release versi
- Support switching between multiple instances with `Switch Account`. [pull/53434](https://github.com/sourcegraph/sourcegraph/pull/53434)
- Automate sign-in flow with Cody App. [pull/53908](https://github.com/sourcegraph/sourcegraph/pull/53908)
- Add a warning message to recipes when the selection gets truncated. [pull/54025](https://github.com/sourcegraph/sourcegraph/pull/54025)
- Start up loading screen. [pull/54106](https://github.com/sourcegraph/sourcegraph/pull/54106)

### Fixed

Expand All @@ -22,6 +23,8 @@ Starting from `0.2.0`, Cody is using `major.EVEN_NUMBER.patch` for release versi
- Autocomplete: Fix network issues when using remote VS Code setups. [pull/53956](https://github.com/sourcegraph/sourcegraph/pull/53956)
- Autocomplete: Fix an issue where the loading indicator would not reset when a network error ocurred. [pull/53956](https://github.com/sourcegraph/sourcegraph/pull/53956)
- Autocomplete: Improve local context performance. [pull/54124](https://github.com/sourcegraph/sourcegraph/pull/54124)
- Codebase index status does not get updated on workspace change. [pull/54106](https://github.com/sourcegraph/sourcegraph/pull/54106)
- Button for connect to App after user is signed out. [pull/54106](https://github.com/sourcegraph/sourcegraph/pull/54106)
- Fixes an issue with link formatting. [pull/54200](https://github.com/sourcegraph/sourcegraph/pull/54200)

### Changed
Expand Down
43 changes: 24 additions & 19 deletions client/cody/src/chat/ChatViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import { TestSupport } from '../test-support'

import { fastFilesExist } from './fastFileFinder'
import {
AuthStatus,
ConfigurationSubsetForWebview,
DOTCOM_URL,
ExtensionMessage,
Expand Down Expand Up @@ -117,6 +116,9 @@ export class ChatViewProvider implements vscode.WebviewViewProvider, vscode.Disp
this.disposables.push(
vscode.window.onDidChangeActiveTextEditor(async () => {
await this.updateCodebaseContext()
}),
vscode.workspace.onDidChangeWorkspaceFolders(async () => {
await this.updateCodebaseContext()
})
)

Expand Down Expand Up @@ -221,10 +223,10 @@ export class ChatViewProvider implements vscode.WebviewViewProvider, vscode.Disp
debug('ChatViewProvider:onDidReceiveMessage:initialized', '')
this.loadChatHistory()
this.publishContextStatus()
this.publishConfig()
this.sendTranscript()
this.sendChatHistory()
await this.loadRecentChat()
this.publishConfig()
break
case 'submit':
await this.onHumanMessageSubmitted(message.text, message.submitType)
Expand All @@ -242,6 +244,10 @@ export class ChatViewProvider implements vscode.WebviewViewProvider, vscode.Disp
await this.executeRecipe(message.recipe)
break
case 'auth':
if (message.type === 'app' && message.endpoint) {
await this.authProvider.appAuth(message.endpoint)
break
}
// cody.auth.signin or cody.auth.signout
await vscode.commands.executeCommand(`cody.auth.${message.type}`)
break
Expand Down Expand Up @@ -623,16 +629,28 @@ export class ChatViewProvider implements vscode.WebviewViewProvider, vscode.Disp
* Save, verify, and sync authStatus between extension host and webview
* activate extension when user has valid login
*/
public async syncAuthStatus(authStatus: AuthStatus): Promise<void> {
if (authStatus.isLoggedIn) {
void this.publishConfig()
}
public async syncAuthStatus(): Promise<void> {
const authStatus = this.authProvider.getAuthStatus()
await vscode.commands.executeCommand('setContext', 'cody.activated', authStatus.isLoggedIn)
this.setWebviewView(authStatus.isLoggedIn ? 'chat' : 'login')
await this.webview?.postMessage({ type: 'login', authStatus })
this.sendEvent('auth', authStatus.isLoggedIn ? 'successfully' : 'failed')
}

/**
* Display app state in webview view that is used during Signin flow
*/
private async sendLocalAppState(token: string | null): Promise<void> {
// Notify webview that app is installed
debug('ChatViewProvider:sendLocalAppState', 'isInstalled')
void this.webview?.postMessage({ type: 'app-state', isInstalled: true })
// Log user in if token is present and user is not logged in
if (token) {
debug('ChatViewProvider:sendLocalAppState', 'auth')
await this.authProvider.auth(LOCAL_APP_URL.href, token, this.config.customHeaders)
}
}

/**
* Delete history from current chat history and local storage
*/
Expand Down Expand Up @@ -774,19 +792,6 @@ export class ChatViewProvider implements vscode.WebviewViewProvider, vscode.Disp
}
}

/**
* Display app state in webview view that is used during Signin flow
*/
private async sendLocalAppState(token: string | null): Promise<void> {
// Notify webview that app is installed
debug('ChatViewProvider:sendLocalAppState', 'isInstalled')
void this.webview?.postMessage({ type: 'app-state', isInstalled: true })
// Log user in if token is present and user is not logged in
if (token) {
debug('ChatViewProvider:sendLocalAppState', 'auth')
await this.authProvider.auth(LOCAL_APP_URL.href, token, this.config.customHeaders)
}
}
/**
* Display error message in webview view as banner in chat view
* It does not display error message as assistant response
Expand Down
6 changes: 2 additions & 4 deletions client/cody/src/chat/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export type WebviewMessage =
| { command: 'openFile'; filePath: string }
| { command: 'edit'; text: string }
| { command: 'insert'; text: string }
| { command: 'auth'; type: 'signin' | 'signout' | 'support' }
| { command: 'auth'; type: 'signin' | 'signout' | 'support' | 'app'; endpoint?: string }
| { command: 'abort' }

/**
Expand Down Expand Up @@ -85,7 +85,7 @@ export interface AuthStatus {
}

export const defaultAuthStatus = {
endpoint: DOTCOM_URL.href,
endpoint: '',
isLoggedIn: false,
showInvalidAccessTokenError: false,
authenticated: false,
Expand Down Expand Up @@ -122,8 +122,6 @@ export interface LocalEnv {
hasAppJson: boolean
isAppInstalled: boolean
isAppRunning: boolean
// TODO: remove this once the experimental period for connect app is over
isAppConnectEnabled: boolean
}

export function isLoggedIn(authStatus: AuthStatus): boolean {
Expand Down
10 changes: 7 additions & 3 deletions client/cody/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Configuration, ConfigurationWithAccessToken } from '@sourcegraph/cody-s
import { SourcegraphNodeCompletionsClient } from '@sourcegraph/cody-shared/src/sourcegraph-api/completions/nodeClient'

import { ChatViewProvider } from './chat/ChatViewProvider'
import { AuthStatus, CODY_FEEDBACK_URL } from './chat/protocol'
import { CODY_FEEDBACK_URL } from './chat/protocol'
import { CodyCompletionItemProvider } from './completions'
import { CompletionsDocumentProvider } from './completions/docprovider'
import { History } from './completions/history'
Expand Down Expand Up @@ -105,7 +105,6 @@ const register = async (
// Could we use the `initialConfig` instead?
const workspaceConfig = vscode.workspace.getConfiguration()
const config = getConfiguration(workspaceConfig)
const authProvider = new AuthProvider(initialConfig, secretStorage, localStorage)

const {
intentDetector,
Expand All @@ -116,6 +115,8 @@ const register = async (
onConfigurationChange: externalServicesOnDidConfigurationChange,
} = await configureExternalServices(initialConfig, rgPath, editor)

const authProvider = new AuthProvider(initialConfig, secretStorage, localStorage)

// Create chat webview
const chatProvider = new ChatViewProvider(
context.extensionPath,
Expand Down Expand Up @@ -187,7 +188,7 @@ const register = async (
}
}),
// Auth
vscode.commands.registerCommand('cody.auth.sync', (state: AuthStatus) => chatProvider.syncAuthStatus(state)),
vscode.commands.registerCommand('cody.auth.sync', () => chatProvider.syncAuthStatus()),
vscode.commands.registerCommand('cody.auth.signin', () => authProvider.signinMenu()),
vscode.commands.registerCommand('cody.auth.signout', () => authProvider.signoutMenu()),
vscode.commands.registerCommand('cody.auth.support', () => showFeedbackSupportQuickPick()),
Expand Down Expand Up @@ -268,6 +269,9 @@ const register = async (
})
)

// Make sure all commands are registered before initializing the authProvider which sends info to webview.
await authProvider.init()

let completionsProvider: vscode.Disposable | null = null
if (initialConfig.autocomplete) {
completionsProvider = createCompletionsProvider(
Expand Down
38 changes: 32 additions & 6 deletions client/cody/src/services/AuthProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ export class AuthProvider {
private localStorage: LocalStorage
) {
this.loadEndpointHistory()
this.init(localStorage).catch(() => null)
}

// Sign into the last endpoint the user was signed into if any
private async init(localStorage: LocalStorage): Promise<void> {
const lastEndpoint = localStorage?.getEndpoint()
public async init(): Promise<void> {
const lastEndpoint = this.localStorage?.getEndpoint()
this.authStatus.endpoint = lastEndpoint
if (!lastEndpoint) {
return
}
Expand All @@ -50,12 +50,12 @@ export class AuthProvider {
if (!token) {
return
}
await this.auth(lastEndpoint, token || null)
await this.auth(lastEndpoint, token)
debug('AuthProvider:init:tokenFound', lastEndpoint)
}

// Display quickpick to select endpoint to sign in to
public async signinMenu(type?: 'enterprise' | 'dotcom' | 'token', uri?: string): Promise<void> {
public async signinMenu(type?: 'enterprise' | 'dotcom' | 'token' | 'app', uri?: string): Promise<void> {
const mode = this.authStatus.isLoggedIn ? 'switch' : 'signin'
debug('AuthProvider:signinMenu', mode)
logEvent('CodyVSCodeExtension:login:clicked')
Expand Down Expand Up @@ -86,9 +86,19 @@ export class AuthProvider {
await this.auth(input.endpoint, input.token)
break
}
case 'app': {
if (uri) {
await this.appAuth(uri)
}
break
}
default: {
// Auto log user if token for the selected instance was found in secret
const selectedEndpoint = item.uri
if (isLocalApp(selectedEndpoint)) {
await this.appAuth(selectedEndpoint)
return
}
const token = await this.secretStorage.get(selectedEndpoint)
const authState = await this.auth(selectedEndpoint, token || null)
if (!authState) {
Expand All @@ -108,6 +118,20 @@ export class AuthProvider {
}
}

public async appAuth(uri?: string): Promise<void> {
const token = await this.secretStorage.get('SOURCEGRAPH_CODY_APP')
if (token) {
const authStatus = await this.auth(LOCAL_APP_URL.href, token)
const isLoggedIn = authStatus?.isLoggedIn
if (isLoggedIn) {
return
}
}
if (uri) {
await vscode.env.openExternal(vscode.Uri.parse(uri))
}
}

// Display quickpick to select endpoint to sign out of
public async signoutMenu(): Promise<void> {
logEvent('CodyVSCodeExtension:logout:clicked')
Expand Down Expand Up @@ -248,10 +272,12 @@ export class AuthProvider {
return
}
await this.localStorage.saveEndpoint(endpoint)
await updateConfiguration('serverEndpoint', endpoint)
if (token) {
await this.secretStorage.storeToken(endpoint, token)
}
if (token && isLocalApp(endpoint)) {
await this.secretStorage.storeToken('SOURCEGRAPH_CODY_APP', token)
}
this.loadEndpointHistory()
debug('AuthProvider:storeAuthInfo:stored', endpoint || '')
}
Expand Down
8 changes: 2 additions & 6 deletions client/cody/src/services/LocalAppDetector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@ export class LocalAppDetector implements vscode.Disposable {
this.localAppMarkers = LOCAL_APP_LOCATIONS[this.localEnv.os]
// Only Mac is supported for now
this.isSupported = this.localEnv.os === 'darwin' && this.localEnv.homeDir !== undefined
const codyConfiguration = vscode.workspace.getConfiguration('cody')
// TODO: remove this once the experimental period for connect app is over
this.localEnv.isAppConnectEnabled = codyConfiguration.get<boolean>('experimental.app.connect') ?? false
void this.init()
}

Expand All @@ -43,7 +40,9 @@ export class LocalAppDetector implements vscode.Disposable {
await this.fetchServer()
return this.localEnv
}

private async init(): Promise<void> {
this.dispose()
debug('LocalAppDetector:init:', 'initializing')
const homeDir = this.localEnv.homeDir
// if conditions are not met, this will be a noop
Expand Down Expand Up @@ -185,7 +184,4 @@ const envInit = {
isAppInstalled: false,
isAppRunning: false,
hasAppJson: false,

// TODO: remove this once the experimental period for connect app is over
isAppConnectEnabled: false,
}
4 changes: 0 additions & 4 deletions client/cody/src/services/LocalAppFsPaths.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
export const LOCAL_APP_LOCATIONS: LocalAppPaths = {
darwin: [
{
dir: '/Applications/',
file: 'Sourcegraph.app',
},
{
dir: '/Applications/',
file: 'Cody.app',
Expand Down
6 changes: 3 additions & 3 deletions client/cody/test/e2e/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { test } from './helpers'

test('requires a valid auth token and allows logouts', async ({ page, sidebar }) => {
await expect(sidebar.getByText('Invalid credentials')).not.toBeVisible()
await sidebar.getByRole('button', { name: 'Continue with Access Token' }).click()
await sidebar.getByRole('button', { name: 'Other Sign In Options…' }).click()
await page.getByRole('option', { name: 'Sign in with URL and Access Token' }).click()
await page.getByRole('combobox', { name: 'input' }).fill(SERVER_URL)
await page.getByRole('combobox', { name: 'input' }).press('Enter')
Expand All @@ -15,7 +15,7 @@ test('requires a valid auth token and allows logouts', async ({ page, sidebar })

await expect(sidebar.getByText('Invalid credentials')).toBeVisible()

await sidebar.getByRole('button', { name: 'Continue with Access Token' }).click()
await sidebar.getByRole('button', { name: 'Other Sign In Options…' }).click()
await page.getByRole('option', { name: 'Sign in with URL and Access Token' }).click()
await page.getByRole('combobox', { name: 'input' }).fill(SERVER_URL)
await page.getByRole('combobox', { name: 'input' }).press('Enter')
Expand All @@ -35,6 +35,6 @@ test('requires a valid auth token and allows logouts', async ({ page, sidebar })
await sidebar.getByRole('button', { name: 'Sign Out' }).click()
await page.getByRole('combobox', { name: 'input' }).press('Enter')

await expect(sidebar.getByRole('button', { name: 'Continue with Access Token' })).toBeVisible()
await expect(sidebar.getByRole('button', { name: 'Other Sign In Options…' })).toBeVisible()
await expect(sidebar.getByText('Invalid credentials')).not.toBeVisible()
})
2 changes: 1 addition & 1 deletion client/cody/test/e2e/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { SERVER_URL, VALID_TOKEN } from '../fixtures/mock-server'

// Sign into Cody with valid auth from the sidebar
export const sidebarSignin = async (page: Page, sidebar: Frame): Promise<void> => {
await sidebar.getByRole('button', { name: 'Continue with Access Token' }).click()
await sidebar.getByRole('button', { name: 'Other Sign In Options…' }).click()
await page.getByRole('option', { name: 'Sign in with URL and Access Token' }).click()
await page.getByRole('combobox', { name: 'input' }).fill(SERVER_URL)
await page.getByRole('combobox', { name: 'input' }).press('Enter')
Expand Down
2 changes: 1 addition & 1 deletion client/cody/webviews/App.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ const dummyVSCodeAPI: VSCodeWrapper = {
arch: 'x64',
homeDir: '/home/user',
isAppInstalled: false,
isAppConnectEnabled: false,
isAppRunning: false,
hasAppJson: false,
extensionVersion: '0.0.0',
},
authStatus: {
...defaultAuthStatus,
isLoggedIn: true,
authenticated: true,
hasVerifiedEmail: true,
requiresVerifiedEmail: false,
Expand Down

0 comments on commit 15841e5

Please sign in to comment.