Skip to content

Commit

Permalink
Auth: Add input validation for instance URL (#3156)
Browse files Browse the repository at this point in the history
This commit adds validation for sourcegraph tokens in the AuthMenus and
AuthProvider files. The showInstanceURLInputBox function in AuthMenus
now checks if the user is entering a token as a URL and displays an
error message if so.
Similarly, the formatURL function in AuthProvider now throws an error if
the URI is a sourcegraph token, and return `null`.

Additionally, the LocalStorageProvider file now ignores and clears the
last used endpoint if the provided endpoint is a sourcegraph token.


![image](https://github.com/sourcegraph/cody/assets/68532117/c77005be-edd9-4018-9c42-4655baff2782)

Update placeholder value for URL to always starts with `https://` to
make it clear that the field is for URL input:


![image](https://github.com/sourcegraph/cody/assets/68532117/cea4f78a-d39e-4819-bc97-a59f6af4c369)



## Test plan

<!-- Required. See
https://sourcegraph.com/docs/dev/background-information/testing_principles.
-->




https://github.com/sourcegraph/cody/assets/68532117/9d44427a-3b8b-4c35-812d-aa5b22120d6d
  • Loading branch information
abeatrix committed Feb 13, 2024
1 parent 9d21d40 commit fe26c6d
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 14 deletions.
15 changes: 15 additions & 0 deletions vscode/src/chat/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,3 +315,18 @@ export function isLoggedIn(authStatus: AuthStatus): boolean {
}

export type AuthMethod = 'dotcom' | 'github' | 'gitlab' | 'google'

// Provide backward compatibility for the old token regex
// Details: https://docs.sourcegraph.com/dev/security/secret_formats
const sourcegraphTokenRegex =
/(sgp_(?:[a-fA-F0-9]{16}|local)|sgp_)?[a-fA-F0-9]{40}|(sgd|slk|sgs)_[a-fA-F0-9]{64}/

/**
* Checks if the given text matches the regex for a Sourcegraph access token.
*
* @param text - The text to check against the regex.
* @returns Whether the text matches the Sourcegraph token regex.
*/
export function isSourcegraphToken(text: string): boolean {
return sourcegraphTokenRegex.test(text)
}
21 changes: 20 additions & 1 deletion vscode/src/services/AuthMenus.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as vscode from 'vscode'

import { isDotCom, LOCAL_APP_URL } from '@sourcegraph/cody-shared'
import { isSourcegraphToken } from '../chat/protocol'

interface LoginMenuItem {
id: string
Expand Down Expand Up @@ -56,10 +57,28 @@ export const AuthMenu = async (
export async function showInstanceURLInputBox(title: string): Promise<string | undefined> {
const result = await vscode.window.showInputBox({
title,
prompt: 'Enter the URL of the Sourcegraph instance',
prompt: 'Enter the URL of the Sourcegraph instance. For example, https://sourcegraph.example.com.',
placeHolder: 'https://sourcegraph.example.com',
value: 'https://',
password: false,
ignoreFocusOut: true,
// valide input to ensure the user is not entering a token as URL
validateInput: (value: string) => {
// ignore empty value
if (!value) {
return null
}
if (isSourcegraphToken(value)) {
return 'Please enter a valid URL'
}
if (value.length > 4 && !value.startsWith('http')) {
return 'URL must start with http or https'
}
if (!/([.]|^https?:\/\/)/.test(value)) {
return 'Please enter a valid URL'
}
return null
},
})

if (typeof result === 'string') {
Expand Down
30 changes: 19 additions & 11 deletions vscode/src/services/AuthProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
networkErrorAuthStatus,
unauthenticatedStatus,
type AuthStatus,
isSourcegraphToken,
} from '../chat/protocol'
import { newAuthStatus } from '../chat/utils'
import { getFullConfig } from '../configuration'
Expand Down Expand Up @@ -447,21 +448,28 @@ export function isNetworkError(error: Error): boolean {
}

function formatURL(uri: string): string | null {
if (!uri) {
return null
}
// Check if the URI is in the correct URL format
// Add missing https:// if needed
if (!uri.startsWith('http')) {
uri = `https://${uri}`
}
try {
if (!uri) {
return null
}

// Check if the URI is a sourcegraph token
if (isSourcegraphToken(uri)) {
throw new Error('Access Token is not a valid URL')
}

// Check if the URI is in the correct URL format
// Add missing https:// if needed
if (!uri.startsWith('http')) {
uri = `https://${uri}`
}

const endpointUri = new URL(uri)
return endpointUri.href
} catch {
console.error('Invalid URL')
} catch (error) {
console.error('Invalid URL: ', error)
return null
}
return null
}

async function showAuthResultMessage(
Expand Down
20 changes: 18 additions & 2 deletions vscode/src/services/LocalStorageProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Memento } from 'vscode'

import type { ChatHistory, ChatInputHistory, UserLocalHistory } from '@sourcegraph/cody-shared'

import type { AuthStatus } from '../chat/protocol'
import { isSourcegraphToken, type AuthStatus } from '../chat/protocol'

type ChatHistoryKey = `${string}-${string}`
type AccountKeyedChatHistory = {
Expand Down Expand Up @@ -55,14 +55,25 @@ class LocalStorage {
}

public getEndpoint(): string | null {
return this.storage.get<string | null>(this.LAST_USED_ENDPOINT, null)
const endpoint = this.storage.get<string | null>(this.LAST_USED_ENDPOINT, null)
// Clear last used endpoint if it is a Sourcegraph token
if (endpoint && isSourcegraphToken(endpoint)) {
this.deleteEndpoint()
return null
}
return endpoint
}

public async saveEndpoint(endpoint: string): Promise<void> {
if (!endpoint) {
return
}
try {
// Do not save sourcegraph tokens as the last used endpoint
if (isSourcegraphToken(endpoint)) {
return
}

const uri = new URL(endpoint).href
await this.storage.update(this.LAST_USED_ENDPOINT, uri)
await this.addEndpointHistory(uri)
Expand All @@ -84,6 +95,11 @@ class LocalStorage {
}

private async addEndpointHistory(endpoint: string): Promise<void> {
// Do not save sourcegraph tokens as endpoint
if (isSourcegraphToken(endpoint)) {
return
}

const history = this.storage.get<string[] | null>(this.CODY_ENDPOINT_HISTORY, null)
const historySet = new Set(history)
historySet.delete(endpoint)
Expand Down

0 comments on commit fe26c6d

Please sign in to comment.