Skip to content
This repository was archived by the owner on Mar 10, 2022. It is now read-only.

Commit b3e093b

Browse files
committed
fix: allow unauthenticated users
I had assumed that users needed an access token to use the GraphQL API but the API seems to work without an access token. The extension now allows users to use the extension without an access token. We still need to handle the case when users have provided an invalid access token.
1 parent 683c504 commit b3e093b

3 files changed

Lines changed: 74 additions & 51 deletions

File tree

src/queries/graphqlQuery.ts

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,70 @@ import { CancellationToken } from 'vscode'
33
import { log } from '../log'
44
import { debugEnabledSetting } from '../settings/debugEnabledSetting'
55
import { endpointHostnameSetting, endpointSetting } from '../settings/endpointSetting'
6-
import { accessTokenSetting, promptUserForAccessTokenSetting } from '../settings/accessTokenSetting'
6+
import {
7+
accessTokenSetting,
8+
deleteAccessTokenSetting,
9+
promptUserForAccessTokenSetting,
10+
} from '../settings/accessTokenSetting'
711

812
export function graphqlQuery<A, B>(query: string, variables: A, token: CancellationToken): Promise<B | undefined> {
9-
return accessTokenSetting().then(accessToken => graphqlQueryWithAccessToken(query, variables, token, accessToken))
13+
return graphqlQueryWithAccessToken(query, variables, token, accessTokenSetting())
1014
}
1115

1216
export function graphqlQueryWithAccessToken<A, B>(
1317
query: string,
1418
variables: A,
1519
token: CancellationToken,
16-
accessToken: string
20+
accessToken?: string
1721
): Promise<B | undefined> {
18-
accessToken = accessToken.trim()
22+
if (accessToken) {
23+
accessToken = accessToken.trim()
24+
}
1925
return new Promise<B | undefined>((resolve, reject) => {
2026
const data = JSON.stringify({
2127
query,
2228
variables,
2329
})
30+
const headers: any = {
31+
'Content-Length': data.length,
32+
}
33+
log.appendLine(`token: ${accessToken || 'undefined'}`)
34+
if (accessToken) {
35+
headers.Authorization = `token ${accessToken}`
36+
}
2437
const options: RequestOptions = {
2538
hostname: endpointHostnameSetting(),
2639
port: 443,
2740
path: '/.api/graphql',
2841
method: 'POST',
29-
headers: {
30-
Authorization: `token ${accessToken}`,
31-
'Content-Length': data.length,
32-
},
42+
headers,
3343
}
3444
const curlCommand = (): string => {
3545
const data: string = JSON.stringify({ query: query.replace(/\s+/g, ' '), variables })
36-
return `curl -H 'Authorization: token ${accessToken}' -d '${data}' ${endpointSetting()}/.api/graphql`
46+
const authorization = accessToken ? `-H 'Authorization: token ${accessToken}' ` : ''
47+
return `curl ${authorization}-d '${data}' ${endpointSetting()}/.api/graphql`
3748
}
3849
const onReject = async (error: any) => {
3950
if (error === 'Invalid access token.\n') {
4051
// Prompt the user to update the access token setting and try again with the new setting.
4152
try {
42-
const newToken = await promptUserForAccessTokenSetting('Invalid Sourcegraph Access Token')
43-
const newResult = await graphqlQueryWithAccessToken<A, B>(query, variables, token, newToken)
53+
if (accessToken) {
54+
await deleteAccessTokenSetting(accessToken)
55+
}
56+
const toFixThisProblem =
57+
process.env.SRC_ACCESS_TOKEN === accessToken
58+
? '. To fix this problem, remove the environment variables SRC_ACCESS_TOKEN and reload VS Code.'
59+
: ''
60+
const newAccessToken = await promptUserForAccessTokenSetting(
61+
'Invalid Sourcegraph Access Token',
62+
`The server at ${endpointHostnameSetting()} is unable to use the access token ${accessToken}.` +
63+
toFixThisProblem
64+
)
65+
log.appendLine(`re-trying GraphQL query with new access token '${newAccessToken}'`)
66+
const newResult = await graphqlQueryWithAccessToken<A, B>(query, variables, token, newAccessToken)
4467
resolve(newResult)
4568
} catch (newError) {
69+
log.error('failed to get valid access token', error)
4670
reject(newError)
4771
}
4872
} else {

src/search/SourcegraphNotebookSerializer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ export class SourcegraphNotebookSerializer implements vscode.NotebookSerializer
4545
): Promise<void> {
4646
for (const cell of cells) {
4747
const execution = controller.createNotebookCellExecution(cell)
48+
execution.token.onCancellationRequested(() => {
49+
log.appendLine(`cancelled execution ${cell.index}`)
50+
execution.end(false)
51+
})
4852
execution.executionOrder = ++this.order
4953
try {
5054
execution.start(Date.now())

src/settings/accessTokenSetting.ts

Lines changed: 35 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,82 +5,76 @@ import { currentUserQuery } from '../queries/currentUserQuery'
55
import { endpointSetting } from './endpointSetting'
66
import { readConfiguration } from './readConfiguration'
77

8-
let cachedAccessToken: Promise<string> | undefined
8+
let cachedAccessToken: Promise<string | undefined> | undefined
99
const invalidAccessTokens = new Set<string>()
1010

11-
export function accessTokenSetting(): Promise<string> {
12-
const fromSettings = readConfiguration().get<string>('accessToken', '')
11+
export function accessTokenSetting(): string | undefined {
12+
const fromSettings = readConfiguration().get<string>('accessToken')
1313
if (fromSettings) {
14-
return Promise.resolve(fromSettings)
14+
return fromSettings
1515
}
1616

1717
const environmentVariable = process.env.SRC_ACCESS_TOKEN
1818
if (environmentVariable && !invalidAccessTokens.has(environmentVariable)) {
19-
return Promise.resolve(environmentVariable)
19+
return environmentVariable
2020
}
2121

22-
return promptUserForAccessTokenSetting()
22+
return undefined
2323
}
2424

2525
export async function deleteAccessTokenSetting(tokenValueToDelete: string): Promise<void> {
2626
invalidAccessTokens.add(tokenValueToDelete)
2727
const currentValue = readConfiguration().get<string>('accessToken')
2828
if (currentValue === tokenValueToDelete) {
2929
cachedAccessToken = undefined
30-
await readConfiguration().update('accessToken', undefined)
30+
await readConfiguration().update('accessToken', undefined, vscode.ConfigurationTarget.Global)
31+
} else {
32+
log.appendLine(
33+
`can't delete access token '${tokenValueToDelete}' because it doesn't match ` +
34+
`existing configuration value '${currentValue || 'undefined'}'`
35+
)
3136
}
3237
}
3338

34-
export async function promptUserForAccessTokenSetting(title?: string): Promise<string> {
39+
export function promptUserForAccessTokenSetting(title: string, detail: string): Promise<string | undefined> {
3540
if (!cachedAccessToken) {
36-
cachedAccessToken = unconditionallyPromptUserForAccessTokenSetting(title)
41+
cachedAccessToken = unconditionallyPromptUserForAccessTokenSetting(title, detail)
3742
cachedAccessToken.then(
38-
() => {},
43+
token => {
44+
log.appendLine(`new access token from user: ${token || 'undefined'}`)
45+
},
3946
error => {
40-
log.error('askUserToCreateAccessToken', error)
47+
log.error('promptUserForAccessTokenSetting', error)
4148
cachedAccessToken = undefined
4249
}
4350
)
4451
}
4552
return cachedAccessToken
4653
}
47-
async function unconditionallyPromptUserForAccessTokenSetting(title?: string): Promise<string> {
54+
55+
async function unconditionallyPromptUserForAccessTokenSetting(
56+
title: string,
57+
detail: string
58+
): Promise<string | undefined> {
4859
const openBrowserMessage = 'Open browser to create an access token'
49-
const learnMore = 'Learn more about access tokens'
50-
const pasteAccessToken = 'Paste existing access token'
51-
const userChoice = await vscode.window.showErrorMessage(
52-
title || 'Missing Sourcegraph Access Token',
53-
{
54-
modal: true,
55-
detail: 'An access token is required to use the Sourcegraph extension. To fix this problem, create a new access token on the Sourcegraph website or set the $SRC_ACCESS_TOKEN environment variable and restart VS Code.',
56-
},
57-
openBrowserMessage,
58-
learnMore,
59-
pasteAccessToken
60-
)
61-
if (userChoice) {
62-
const openUrl =
63-
userChoice === openBrowserMessage
64-
? `${endpointSetting()}/user/settings/tokens`
65-
: userChoice === learnMore
66-
? 'https://docs.sourcegraph.com/cli/how-tos/creating_an_access_token'
67-
: undefined
68-
if (openUrl) {
69-
await open(openUrl)
70-
}
71-
const token = await vscode.window.showInputBox({
60+
const logout = 'Continue without an access token'
61+
const userChoice = await vscode.window.showErrorMessage(title, { modal: true, detail }, openBrowserMessage, logout)
62+
63+
if (userChoice === openBrowserMessage) {
64+
await open(`${endpointSetting()}/user/settings/tokens`)
65+
const newToken = await vscode.window.showInputBox({
7266
title: 'Paste your Sourcegraph access token here',
7367
ignoreFocusOut: true,
7468
})
75-
if (token) {
69+
if (newToken) {
7670
try {
77-
const currentUser = await currentUserQuery(token)
71+
const currentUser = await currentUserQuery(newToken)
72+
await readConfiguration().update('accessToken', newToken, vscode.ConfigurationTarget.Global)
7873
const successMessage = `Successfully logged into Sourcegraph as user '${currentUser}'`
7974
log.appendLine(successMessage)
8075
await vscode.window.showInformationMessage(successMessage)
81-
await readConfiguration().update('accessToken', token, vscode.ConfigurationTarget.Global)
8276
cachedAccessToken = undefined
83-
return token
77+
return newToken
8478
} catch {
8579
await vscode.window.showErrorMessage(
8680
"Invalid Access Token. To fix this problem, update the 'sourcegraph.accessToken' setting and try again"
@@ -92,7 +86,8 @@ async function unconditionallyPromptUserForAccessTokenSetting(title?: string): P
9286
} else {
9387
log.error('askUserToCreateAccessToken - The user decided not to open the browser')
9488
}
95-
throw new Error('No access token')
89+
90+
return undefined
9691
}
9792

9893
export async function updateAccessTokenSetting(newValue?: string): Promise<void> {

0 commit comments

Comments
 (0)