mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 15:51:43 +00:00
Fix(search): auth issues with Sourcegraph VSCode extension (#63175)
Fixes the issues requiring the workaround described in [this video](https://www.loom.com/share/10a4a66a19b548c7b0866fe2cc358daa). Closes #60710 No more manual editing of `settings.json`. The endpoint URL and access code can now all be managed from the UI <!-- 💡 To write a useful PR description, make sure that your description covers: - WHAT this PR is changing: - How was it PREVIOUSLY. - How it will be from NOW on. - WHY this PR is needed. - CONTEXT, i.e. to which initiative, project or RFC it belongs. The structure of the description doesn't matter as much as covering these points, so use your best judgement based on your context. Learn how to write good pull request description: https://www.notion.so/sourcegraph/Write-a-good-pull-request-description-610a7fd3e613496eb76f450db5a49b6e?pvs=4 --> ## Test plan ### First Build and run locally. ``` git switch peterguy/vscode-sourcegraph-extension-fix-auth cd client/vscode pnpm run build ``` ### Then Launch extension in VSCode: open the `Run and Debug` sidebar view in VS Code, then select `Launch VS Code Extension` from the dropdown menu. Click on `Have an account?` to open the login dialog. Enter an access token and the URL of the Sourcegraph instance to which you would like to connect. Click `Authenticate account`. In the Help and Feedback section, click your username to open the logout panel, then log out. Repeat the login process. You can check `settings.json` if you'd like to confirm that it's no longer being used. If you're logging in to dotcom, you'll probably se a SQL error. The login process still works; the SQL error does not have long to live. ## Changelog - Entering the URL and access token in the UI now works - no more manual editing of `settings.json`
This commit is contained in:
parent
e0e234c509
commit
9d82cd17eb
@ -142,8 +142,6 @@ ts_project(
|
||||
"src/settings/accessTokenSetting.ts",
|
||||
"src/settings/displayWarnings.ts",
|
||||
"src/settings/endpointSetting.ts",
|
||||
"src/settings/invalidation.ts",
|
||||
"src/settings/readConfiguration.ts",
|
||||
"src/settings/recommendations.ts",
|
||||
"src/settings/uninstall.ts",
|
||||
"src/state.ts",
|
||||
|
||||
@ -13,6 +13,7 @@ The Sourcegraph extension uses major.EVEN_NUMBER.patch (eg. 2.0.1) for release v
|
||||
### Fixes
|
||||
|
||||
- Various UI fixes for dark and light themes [pull/50598](https://github.com/sourcegraph/sourcegraph/pull/50598)
|
||||
- Fix authentication so it works through the UI instead of requiring manual modification of `settings.json` [pull/63175](https://github.com/sourcegraph/sourcegraph/pull/63175)
|
||||
|
||||
## 2.2.15
|
||||
|
||||
|
||||
@ -8,29 +8,16 @@ import { endpointRequestHeadersSetting, endpointSetting } from '../settings/endp
|
||||
|
||||
import { fetch, getProxyAgent, Headers, type HeadersInit } from './fetch'
|
||||
|
||||
let invalidated = false
|
||||
|
||||
/**
|
||||
* To be called when Sourcegraph URL changes.
|
||||
*/
|
||||
export function invalidateClient(): void {
|
||||
invalidated = true
|
||||
}
|
||||
|
||||
export const requestGraphQLFromVSCode = async <R, V = object>(
|
||||
request: string,
|
||||
variables: V,
|
||||
overrideAccessToken?: string,
|
||||
overrideSourcegraphURL?: string
|
||||
): Promise<GraphQLResult<R>> => {
|
||||
if (invalidated) {
|
||||
throw new Error(
|
||||
'Sourcegraph GraphQL Client has been invalidated due to instance URL change. Restart VS Code to fix.'
|
||||
)
|
||||
}
|
||||
const session = await authentication.getSession(endpointSetting(), [], { createIfNone: false })
|
||||
const sourcegraphURL = overrideSourcegraphURL || endpointSetting()
|
||||
const accessToken = overrideAccessToken || session?.accessToken
|
||||
const accessToken =
|
||||
overrideAccessToken ||
|
||||
(await authentication.getSession(sourcegraphURL, [], { createIfNone: false }))?.accessToken
|
||||
const nameMatch = request.match(/^\s*(?:query|mutation)\s+(\w+)/)
|
||||
const apiURL = `${GRAPHQL_URI}${nameMatch ? '?' + nameMatch[1] : ''}`
|
||||
const customHeaders = endpointRequestHeadersSetting()
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import vscode, { env } from 'vscode'
|
||||
|
||||
import { endpointSetting } from '../settings/endpointSetting'
|
||||
|
||||
import { getSourcegraphFileUrl, repoInfo } from './git-helpers'
|
||||
import { generateSourcegraphBlobLink } from './initialize'
|
||||
|
||||
@ -12,8 +14,7 @@ export async function browserActions(action: string, logRedirectEvent: (uri: str
|
||||
throw new Error('No active editor')
|
||||
}
|
||||
const uri = editor.document.uri
|
||||
const instanceUrl =
|
||||
vscode.workspace.getConfiguration('sourcegraph').get<string>('url') || 'https://sourcegraph.com/'
|
||||
const instanceUrl = endpointSetting()
|
||||
let sourcegraphUrl = ''
|
||||
// check if the current file is a remote file or not
|
||||
if (uri.scheme === 'sourcegraph') {
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import vscode, { env } from 'vscode'
|
||||
|
||||
import { endpointSetting } from '../settings/endpointSetting'
|
||||
|
||||
import { generateSourcegraphBlobLink } from './initialize'
|
||||
|
||||
/**
|
||||
@ -12,8 +14,8 @@ export async function browserActions(action: string, logRedirectEvent: (uri: str
|
||||
throw new Error('No active editor')
|
||||
}
|
||||
const uri = editor.document.uri
|
||||
const instanceUrl = vscode.workspace.getConfiguration('sourcegraph').get('url')
|
||||
let sourcegraphUrl = String()
|
||||
const instanceUrl = endpointSetting()
|
||||
let sourcegraphUrl = ''
|
||||
// check if the current file is a remote file or not
|
||||
if (uri.scheme === 'sourcegraph') {
|
||||
sourcegraphUrl = generateSourcegraphBlobLink(
|
||||
|
||||
@ -5,6 +5,7 @@ import type { EventSource } from '@sourcegraph/shared/src/graphql-operations'
|
||||
import { version } from '../../package.json'
|
||||
import { logEvent } from '../backend/eventLogger'
|
||||
import { SourcegraphUri } from '../file-system/SourcegraphUri'
|
||||
import { endpointSetting } from '../settings/endpointSetting'
|
||||
import { type LocalStorageService, ANONYMOUS_USER_ID_KEY } from '../settings/LocalStorageService'
|
||||
|
||||
import { browserActions } from './browserActionsNode'
|
||||
@ -29,8 +30,7 @@ export function initializeCodeSharingCommands(
|
||||
// Search Selected Text in Sourcegraph Search Tab
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('sourcegraph.selectionSearchWeb', async () => {
|
||||
const instanceUrl =
|
||||
vscode.workspace.getConfiguration('sourcegraph').get<string>('url') || 'https://sourcegraph.com'
|
||||
const instanceUrl = endpointSetting()
|
||||
const editor = vscode.window.activeTextEditor
|
||||
const selectedQuery = editor?.document.getText(editor.selection)
|
||||
if (!editor || !selectedQuery) {
|
||||
@ -73,9 +73,7 @@ export function generateSourcegraphBlobLink(
|
||||
endLine: number,
|
||||
endChar: number
|
||||
): string {
|
||||
const instanceUrl = new URL(
|
||||
vscode.workspace.getConfiguration('sourcegraph').get<string>('url') || 'https://sourcegraph.com'
|
||||
)
|
||||
const instanceUrl = new URL(endpointSetting())
|
||||
// Using SourcegraphUri.parse to properly decode repo revision
|
||||
const decodedUri = SourcegraphUri.parse(uri.toString())
|
||||
const finalUri = new URL(decodedUri.uri)
|
||||
|
||||
@ -21,35 +21,35 @@ import { SourcegraphUri } from './file-system/SourcegraphUri'
|
||||
import type { Event } from './graphql-operations'
|
||||
import { accessTokenSetting, processOldToken } from './settings/accessTokenSetting'
|
||||
import { endpointRequestHeadersSetting, endpointSetting } from './settings/endpointSetting'
|
||||
import { invalidateContextOnSettingsChange } from './settings/invalidation'
|
||||
import { LocalStorageService, SELECTED_SEARCH_CONTEXT_SPEC_KEY } from './settings/LocalStorageService'
|
||||
import { watchUninstall } from './settings/uninstall'
|
||||
import { createVSCEStateMachine, type VSCEQueryState } from './state'
|
||||
import { copySourcegraphLinks, focusSearchPanel, openSourcegraphLinks, registerWebviews } from './webview/commands'
|
||||
import { secretTokenKey, SourcegraphAuthActions, SourcegraphAuthProvider } from './webview/platform/AuthProvider'
|
||||
|
||||
export let extensionContext: vscode.ExtensionContext
|
||||
/**
|
||||
* See CONTRIBUTING docs for the Architecture Diagram
|
||||
*/
|
||||
export async function activate(context: vscode.ExtensionContext): Promise<void> {
|
||||
extensionContext = context
|
||||
const initialInstanceURL = endpointSetting()
|
||||
const secretStorage = context.secrets
|
||||
// Register SourcegraphAuthProvider
|
||||
context.subscriptions.push(
|
||||
vscode.authentication.registerAuthenticationProvider(
|
||||
endpointSetting(),
|
||||
initialInstanceURL,
|
||||
secretTokenKey,
|
||||
new SourcegraphAuthProvider(secretStorage)
|
||||
)
|
||||
)
|
||||
await processOldToken(secretStorage)
|
||||
const initialInstanceURL = endpointSetting()
|
||||
const initialAccessToken = await secretStorage.get(secretTokenKey)
|
||||
const createIfNone = initialAccessToken ? { createIfNone: true } : { createIfNone: false }
|
||||
const session = await vscode.authentication.getSession(endpointSetting(), [], createIfNone)
|
||||
const session = await vscode.authentication.getSession(initialInstanceURL, [], createIfNone)
|
||||
const authenticatedUser = observeAuthenticatedUser(secretStorage)
|
||||
const localStorageService = new LocalStorageService(context.globalState)
|
||||
const stateMachine = createVSCEStateMachine({ localStorageService })
|
||||
invalidateContextOnSettingsChange({ context, stateMachine })
|
||||
initializeSearchContexts({ localStorageService, stateMachine, context })
|
||||
const sourcegraphSettings = initializeSourcegraphSettings({ context })
|
||||
const editorTheme = vscode.ColorThemeKind[vscode.window.activeColorTheme.kind]
|
||||
|
||||
@ -4,7 +4,6 @@ import { isOlderThan, observeInstanceVersionNumber } from '../backend/instanceVe
|
||||
import { secretTokenKey } from '../webview/platform/AuthProvider'
|
||||
|
||||
import { endpointHostnameSetting, endpointProtocolSetting } from './endpointSetting'
|
||||
import { readConfiguration } from './readConfiguration'
|
||||
|
||||
// IMPORTANT: Call this function only once when extention is first activated
|
||||
export async function processOldToken(secretStorage: vscode.SecretStorage): Promise<void> {
|
||||
@ -25,8 +24,12 @@ export async function accessTokenSetting(secretStorage: vscode.SecretStorage): P
|
||||
}
|
||||
|
||||
export async function removeOldAccessTokenSetting(): Promise<void> {
|
||||
await readConfiguration().update('accessToken', undefined, vscode.ConfigurationTarget.Global)
|
||||
await readConfiguration().update('accessToken', undefined, vscode.ConfigurationTarget.Workspace)
|
||||
await vscode.workspace
|
||||
.getConfiguration()
|
||||
.update('sourcegraph.accessToken', undefined, vscode.ConfigurationTarget.Global)
|
||||
await vscode.workspace
|
||||
.getConfiguration()
|
||||
.update('sourcegraph.accessToken', undefined, vscode.ConfigurationTarget.Workspace)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -1,20 +1,75 @@
|
||||
import * as vscode from 'vscode'
|
||||
|
||||
import { readConfiguration } from './readConfiguration'
|
||||
import { extensionContext } from '../extension'
|
||||
import { secretTokenKey, SourcegraphAuthProvider } from '../webview/platform/AuthProvider'
|
||||
|
||||
const defaultEndpointURL = 'https://sourcegraph.com'
|
||||
|
||||
const endpointKey = 'sourcegraph.url'
|
||||
|
||||
async function removeOldEndpointURLSetting(): Promise<void> {
|
||||
await vscode.workspace.getConfiguration().update(endpointKey, undefined, vscode.ConfigurationTarget.Global)
|
||||
await vscode.workspace.getConfiguration().update(endpointKey, undefined, vscode.ConfigurationTarget.Workspace)
|
||||
return
|
||||
}
|
||||
|
||||
export function endpointSetting(): string {
|
||||
const url = vscode.workspace.getConfiguration().get<string>('sourcegraph.url') || 'https://sourcegraph.com'
|
||||
// get the URl from either, 1. extension local storage (new)
|
||||
let url = extensionContext?.globalState.get<string>(endpointKey)
|
||||
if (!url) {
|
||||
// 2. settings.json (old)
|
||||
url = vscode.workspace.getConfiguration().get<string>(endpointKey)
|
||||
if (url) {
|
||||
// if settings.json, migrate to extension local storage
|
||||
extensionContext?.globalState.update(endpointKey, url).then(
|
||||
() => {
|
||||
void removeOldEndpointURLSetting()
|
||||
},
|
||||
error => {
|
||||
console.error(error)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
// or, 3. default value
|
||||
url = defaultEndpointURL
|
||||
}
|
||||
}
|
||||
return removeEndingSlash(url)
|
||||
}
|
||||
|
||||
export async function setEndpoint(newEndpoint: string): Promise<void> {
|
||||
const newEndpointURL = newEndpoint ? removeEndingSlash(newEndpoint) : 'https://sourcegraph.com'
|
||||
export function setEndpoint(newEndpoint: string | undefined): void {
|
||||
const newEndpointURL = newEndpoint ? removeEndingSlash(newEndpoint) : defaultEndpointURL
|
||||
const currentEndpointHostname = new URL(endpointSetting()).hostname
|
||||
const newEndpointHostname = new URL(newEndpointURL).hostname
|
||||
if (currentEndpointHostname !== newEndpointHostname) {
|
||||
await readConfiguration().update('url', newEndpointURL)
|
||||
extensionContext?.globalState.update(endpointKey, newEndpointURL).then(
|
||||
() => {
|
||||
// after changing the endpoint URL, register an authentication provder for it.
|
||||
// trying and erroring (because one already exists) is probably just as cheap/expensive
|
||||
// as trying `vscode.authentication.getSession(newEndpointURL, [], { createIfNone: false })`,
|
||||
// catching an error and registering an auth provider.
|
||||
try {
|
||||
const provider = vscode.authentication.registerAuthenticationProvider(
|
||||
newEndpointURL,
|
||||
secretTokenKey,
|
||||
new SourcegraphAuthProvider(extensionContext?.secrets)
|
||||
)
|
||||
extensionContext?.subscriptions.push(provider)
|
||||
} catch (error) {
|
||||
// unsetting the endpoint reverts to the default,
|
||||
// which probably already has an auth provider,
|
||||
// which would cause an error,
|
||||
// so ignore it.
|
||||
if (!(error as Error).message.includes('is already registered')) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
},
|
||||
error => {
|
||||
console.error(error)
|
||||
}
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
export function endpointHostnameSetting(): string {
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
import * as vscode from 'vscode'
|
||||
|
||||
import { invalidateClient } from '../backend/requestGraphQl'
|
||||
import type { VSCEStateMachine } from '../state'
|
||||
|
||||
/**
|
||||
* Listens for Sourcegraph URL and invalidates the GraphQL client
|
||||
* to prevent data "contamination" (e.g. sending private repo names to Cloud instance).
|
||||
*/
|
||||
export function invalidateContextOnSettingsChange({
|
||||
context,
|
||||
stateMachine,
|
||||
}: {
|
||||
context: vscode.ExtensionContext
|
||||
stateMachine: VSCEStateMachine
|
||||
}): void {
|
||||
function disposeAllResources(): void {
|
||||
for (const subscription of context.subscriptions) {
|
||||
subscription.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidChangeConfiguration(async config => {
|
||||
if (config.affectsConfiguration('sourcegraph.url')) {
|
||||
invalidateClient()
|
||||
disposeAllResources()
|
||||
stateMachine.emit({ type: 'sourcegraph_url_change' })
|
||||
// Swallow errors since if `showInformationMessage` fails, we assume that something is wrong
|
||||
// with the VS Code extension host and don't retry.
|
||||
await vscode.window.showInformationMessage(
|
||||
'Restart VS Code to use the Sourcegraph extension after URL change.'
|
||||
)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
import * as vscode from 'vscode'
|
||||
|
||||
export function readConfiguration(): vscode.WorkspaceConfiguration {
|
||||
return vscode.workspace.getConfiguration('sourcegraph')
|
||||
}
|
||||
@ -99,7 +99,7 @@ function createInitialState({ localStorageService }: { localStorageService: Loca
|
||||
|
||||
// Temporary placeholder events. We will replace these with the actual events as we implement the webviews.
|
||||
|
||||
export type VSCEEvent = SearchEvent | TabsEvent | SettingsEvent
|
||||
export type VSCEEvent = SearchEvent | TabsEvent
|
||||
|
||||
type SearchEvent =
|
||||
| { type: 'set_query_state' }
|
||||
@ -118,10 +118,6 @@ type TabsEvent =
|
||||
| { type: 'remote_file_focused' }
|
||||
| { type: 'remote_file_unfocused' }
|
||||
|
||||
interface SettingsEvent {
|
||||
type: 'sourcegraph_url_change'
|
||||
}
|
||||
|
||||
export function createVSCEStateMachine({
|
||||
localStorageService,
|
||||
}: {
|
||||
@ -135,15 +131,6 @@ export function createVSCEStateMachine({
|
||||
return state
|
||||
}
|
||||
|
||||
// Events with the same behavior regardless of current state
|
||||
if (event.type === 'sourcegraph_url_change') {
|
||||
return {
|
||||
status: 'context-invalidated',
|
||||
context: {
|
||||
...createInitialState({ localStorageService }).context,
|
||||
},
|
||||
}
|
||||
}
|
||||
if (event.type === 'set_selected_search_context_spec') {
|
||||
return {
|
||||
...state,
|
||||
|
||||
@ -138,7 +138,7 @@ export class SourcegraphAuthActions {
|
||||
try {
|
||||
await this.secretStorage.store(secretTokenKey, newtoken)
|
||||
if (this.currentEndpoint !== newuri) {
|
||||
await setEndpoint(newuri)
|
||||
setEndpoint(newuri)
|
||||
}
|
||||
return
|
||||
} catch (error) {
|
||||
@ -147,6 +147,7 @@ export class SourcegraphAuthActions {
|
||||
}
|
||||
|
||||
public async logout(): Promise<void> {
|
||||
setEndpoint(undefined)
|
||||
await this.secretStorage.delete(secretTokenKey)
|
||||
await commands.executeCommand('workbench.action.reloadWindow')
|
||||
return
|
||||
|
||||
@ -280,8 +280,7 @@ export const AuthSidebarView: React.FunctionComponent<React.PropsWithChildren<Au
|
||||
</VSCodeButton>
|
||||
{state === 'failure' && (
|
||||
<Alert variant="danger" className={classNames(styles.ctaParagraph, 'my-1')}>
|
||||
Unable to verify your access token for {hostname}. Please try again with a new access token or
|
||||
restart VS Code if the instance URL has been updated.
|
||||
Unable to verify your access token for {hostname}. Please try again with a new access token.
|
||||
</Alert>
|
||||
)}
|
||||
<Text className="my-0">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user