Skip to content

Commit

Permalink
Refactor errors localtion
Browse files Browse the repository at this point in the history
  • Loading branch information
pkukielka committed Jan 27, 2025
1 parent 70a27b7 commit c08e156
Show file tree
Hide file tree
Showing 17 changed files with 164 additions and 142 deletions.
97 changes: 2 additions & 95 deletions lib/shared/src/auth/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { isDotCom } from '../sourcegraph-api/environments'
import type { AuthError } from '../sourcegraph-api/errors'
import type { UserProductSubscription } from '../sourcegraph-api/userProductSubscription'

/**
Expand Down Expand Up @@ -45,104 +46,10 @@ export interface AuthenticatedAuthStatus {
export interface UnauthenticatedAuthStatus {
endpoint: string
authenticated: false
error?: AuthenticationError
error?: AuthError
pendingValidation: boolean
}

export class AuthenticationError extends Error {
public title: string
public showTryAgain = false

constructor(title: string, message: string) {
super(message)
this.title = title
}
}

/**
* An error representing the condition where the endpoint is not available due to lack of network
* connectivity, server downtime, or other configuration issues *unrelated to* the validity of the
* credentials.
*/
export class AvailabilityError extends AuthenticationError {
constructor() {
super('Network Error', 'Sourcegraph is unreachable')
// 'Network issues prevented Cody from signing in.'
this.showTryAgain = true
}
}

export class InvalidAccessTokenError extends AuthenticationError {
// type: 'invalid-access-token'
constructor() {
super('Invalid Access Token', 'The access token is invalid or has expired')
//'Your authentication has expired.\nSign in again to continue using Cody.',
}
}

export class EnterpriseUserDotComError extends AuthenticationError {
// type: 'enterprise-user-logged-into-dotcom'
constructor(enterprise: string) {
super(
'Enterprise User Authentication Error',
'Based on your email address we think you may be an employee of ' +
`${enterprise}. To get access to all your features please sign ` +
"in through your organization's enterprise instance instead. If you need assistance " +
'please contact your Sourcegraph admin.'
)
}
}

export class AuthConfigError extends AuthenticationError {
// type: 'auth-config-error'
constructor(message: string) {
super('Auth Config Error', message)
}
}

export class ExternalAuthProviderError extends AuthenticationError {
// public title = 'External Auth Provider Error'
constructor(message: string) {
super('External Auth Provider Error', message)
}
}

/**
* An error indicating that the user needs to complete an authentication challenge.
*/
export class NeedsAuthChallengeError extends AuthenticationError {
constructor() {
// See
// https://linear.app/sourcegraph/issue/CODY-4695/handle-customer-proxy-re-auth-response-by-retrying-not-prompting-user
// for an explanation of this message. If you need to change it to something more general,
// consult the customers mentioned in that issue.
super(
'Tap Your YubiKey to Authenticate',
`Your device's authentication expired and must be renewed to access Sourcegraph on your organization's network.`
)
}
}

export function isExternalProviderAuthError(error: unknown): error is ExternalAuthProviderError {
return error instanceof ExternalAuthProviderError
}

export function isNeedsAuthChallengeError(error: unknown): error is NeedsAuthChallengeError {
return error instanceof NeedsAuthChallengeError
}

export function isAvailabilityError(error: unknown): error is AvailabilityError {
return error instanceof AvailabilityError
}

export function isInvalidAccessTokenError(error: unknown): error is InvalidAccessTokenError {
return error instanceof InvalidAccessTokenError
}

export function isEnterpriseUserDotComError(error: unknown): error is EnterpriseUserDotComError {
return error instanceof EnterpriseUserDotComError
}

export const AUTH_STATUS_FIXTURE_AUTHED: AuthenticatedAuthStatus = {
endpoint: 'https://example.com',
authenticated: true,
Expand Down
2 changes: 1 addition & 1 deletion lib/shared/src/configuration/auth-resolver.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Subject } from 'observable-fns'
import { ExternalAuthProviderError } from '..'
import type {
AuthCredentials,
ClientConfiguration,
ExternalAuthCommand,
ExternalAuthProvider,
} from '../configuration'
import { logError } from '../logger'
import { ExternalAuthProviderError } from '../sourcegraph-api/errors'
import type { ClientSecrets } from './resolver'

export const externalAuthRefresh = new Subject<void>()
Expand Down
14 changes: 13 additions & 1 deletion lib/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,14 +241,26 @@ export {
} from './sourcegraph-api/environments'
export {
AbortError,
AuthConfigError,
AuthError,
AvailabilityError,
EnterpriseUserDotComError,
ExternalAuthProviderError,
InvalidAccessTokenError,
NeedsAuthChallengeError,
NetworkError,
RateLimitError,
TimeoutError,
TracedError,
isAbortError,
isAbortErrorOrSocketHangUp,
isContextWindowLimitError,
isAuthError,
isAvailabilityError,
isContextWindowLimitError,
isEnterpriseUserDotComError,
isExternalProviderAuthError,
isInvalidAccessTokenError,
isNeedsAuthChallengeError,
isNetworkError,
isNetworkLikeError,
isRateLimitError,
Expand Down
95 changes: 92 additions & 3 deletions lib/shared/src/sourcegraph-api/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { differenceInDays, format, formatDistanceStrict, formatRelative } from '

import { isError } from '../utils'

import { AuthenticationError } from '../auth/types'
import type { BrowserOrNodeResponse } from './graphql/client'

function formatRetryAfterDate(retryAfterDate: Date): string {
Expand Down Expand Up @@ -89,10 +88,10 @@ export function isNetworkError(error: Error): error is NetworkError {
return error instanceof NetworkError
}

export function isAuthError(error: unknown): error is AuthenticationError | NetworkError {
export function isAuthError(error: unknown): error is AuthError | NetworkError {
return (
(error instanceof NetworkError && (error.status === 401 || error.status === 403)) ||
error instanceof AuthenticationError
error instanceof AuthError
)
}

Expand Down Expand Up @@ -139,3 +138,93 @@ export function isNetworkLikeError(error: Error): boolean {
message.includes('SELF_SIGNED_CERT_IN_CHAIN')
)
}

export class AuthError extends Error {
public title: string
public content: string
public showTryAgain = false

constructor(title: string, content: string) {
super(`${title}: ${content}`)
this.content = content
this.title = title
}
}

/**
* An error representing the condition where the endpoint is not available due to lack of network
* connectivity, server downtime, or other configuration issues *unrelated to* the validity of the
* credentials.
*/
export class AvailabilityError extends AuthError {
constructor() {
super('Network Error', 'Sourcegraph is unreachable')
this.showTryAgain = true
}
}

export class InvalidAccessTokenError extends AuthError {
constructor() {
super('Invalid Access Token', 'The access token is invalid or has expired')
}
}

export class EnterpriseUserDotComError extends AuthError {
constructor(enterprise: string) {
super(
'Enterprise User Authentication Error',
'Based on your email address we think you may be an employee of ' +
`${enterprise}. To get access to all your features please sign ` +
"in through your organization's enterprise instance instead. If you need assistance " +
'please contact your Sourcegraph admin.'
)
}
}

export class AuthConfigError extends AuthError {
constructor(message: string) {
super('Auth Config Error', message)
}
}

export class ExternalAuthProviderError extends AuthError {
constructor(message: string) {
super('External Auth Provider Error', message)
}
}

/**
* An error indicating that the user needs to complete an authentication challenge.
*/
export class NeedsAuthChallengeError extends AuthError {
constructor() {
// See
// https://linear.app/sourcegraph/issue/CODY-4695/handle-customer-proxy-re-auth-response-by-retrying-not-prompting-user
// for an explanation of this message. If you need to change it to something more general,
// consult the customers mentioned in that issue.
super(
'Tap Your YubiKey to Authenticate',
`Your device's authentication expired and must be renewed to access Sourcegraph on your organization's network.`
)
}
}

export function isExternalProviderAuthError(error: unknown): error is ExternalAuthProviderError {
return error instanceof ExternalAuthProviderError
}

export function isNeedsAuthChallengeError(error: unknown): error is NeedsAuthChallengeError {
return error instanceof NeedsAuthChallengeError
}

export function isAvailabilityError(error: unknown): error is AvailabilityError {
return error instanceof AvailabilityError
}

export function isInvalidAccessTokenError(error: unknown): error is InvalidAccessTokenError {
return error instanceof InvalidAccessTokenError
}

export function isEnterpriseUserDotComError(error: unknown): error is EnterpriseUserDotComError {
return error instanceof EnterpriseUserDotComError
}
3 changes: 2 additions & 1 deletion lib/shared/src/sourcegraph-api/graphql/client.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, it, vi } from 'vitest'
import { NeedsAuthChallengeError, SourcegraphGraphQLAPIClient } from '../..'
import { SourcegraphGraphQLAPIClient } from '../..'
import * as fetchModule from '../../fetch'
import { NeedsAuthChallengeError } from '../errors'

vi.mocked(fetchModule)

Expand Down
3 changes: 1 addition & 2 deletions lib/shared/src/sourcegraph-api/graphql/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@ import isEqual from 'lodash/isEqual'
import omit from 'lodash/omit'
import { Observable } from 'observable-fns'
import semver from 'semver'
import { NeedsAuthChallengeError, isNeedsAuthChallengeError } from '../..'
import { dependentAbortController, onAbort } from '../../common/abortController'
import { type PickResolvedConfiguration, resolvedConfig } from '../../configuration/resolver'
import { logError } from '../../logger'
import { distinctUntilChanged, firstValueFrom } from '../../misc/observable'
import { addTraceparent, wrapInActiveSpan } from '../../tracing'
import { isError } from '../../utils'
import { addCodyClientIdentificationHeaders } from '../client-name-version'
import { isAbortError } from '../errors'
import { NeedsAuthChallengeError, isAbortError, isNeedsAuthChallengeError } from '../errors'
import { addAuthHeaders } from '../utils'
import { type GraphQLResultCache, ObservableInvalidatedGraphQLResultCacheFactory } from './cache'
import {
Expand Down
8 changes: 5 additions & 3 deletions vscode/src/auth/auth.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import {
type AuthStatus,
AvailabilityError,
CLIENT_CAPABILITIES_FIXTURE,
InvalidAccessTokenError,
NeedsAuthChallengeError,
SourcegraphGraphQLAPIClient,
mockAuthStatus,
mockClientCapabilities,
mockResolvedConfig,
} from '@sourcegraph/cody-shared'
import {
AvailabilityError,
InvalidAccessTokenError,
NeedsAuthChallengeError,
} from '@sourcegraph/cody-shared/src/sourcegraph-api/errors'
import { afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest'
import { type MockInstance, vi } from 'vitest'
import type * as vscode from 'vscode'
Expand Down
21 changes: 11 additions & 10 deletions vscode/src/auth/auth.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import * as vscode from 'vscode'

import {
AuthConfigError,
type AuthStatus,
AvailabilityError,
ClientConfigSingleton,
type CodyClientConfig,
DOTCOM_URL,
EnterpriseUserDotComError,
ExternalAuthProviderError,
type GraphQLAPIClientConfig,
InvalidAccessTokenError,
NeedsAuthChallengeError,
type PickResolvedConfiguration,
SourcegraphGraphQLAPIClient,
type UnauthenticatedAuthStatus,
Expand All @@ -23,14 +17,21 @@ import {
graphqlClient,
isDotCom,
isError,
isExternalProviderAuthError,
isInvalidAccessTokenError,
isNeedsAuthChallengeError,
isNetworkLikeError,
isWorkspaceInstance,
telemetryRecorder,
} from '@sourcegraph/cody-shared'
import { resolveAuth } from '@sourcegraph/cody-shared/src/configuration/auth-resolver'
import {
AuthConfigError,
AvailabilityError,
EnterpriseUserDotComError,
InvalidAccessTokenError,
NeedsAuthChallengeError,
isExternalProviderAuthError,
isInvalidAccessTokenError,
isNeedsAuthChallengeError,
} from '@sourcegraph/cody-shared/src/sourcegraph-api/errors'
import { isSourcegraphToken } from '../chat/protocol'
import { newAuthStatus } from '../chat/utils'
import { logDebug } from '../output-channel-logger'
Expand Down Expand Up @@ -493,7 +494,7 @@ export async function validateCredentials(
logDebug('auth', userInfo.message)
return {
authenticated: false,
error: new ExternalAuthProviderError(userInfo.message),
error: userInfo,
endpoint: config.auth.serverEndpoint,
pendingValidation: false,
}
Expand Down
3 changes: 2 additions & 1 deletion vscode/src/chat/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest'

import { type AuthStatus, DOTCOM_URL, InvalidAccessTokenError } from '@sourcegraph/cody-shared'
import { type AuthStatus, DOTCOM_URL } from '@sourcegraph/cody-shared'
import { InvalidAccessTokenError } from '@sourcegraph/cody-shared/src/sourcegraph-api/errors'
import { newAuthStatus } from './utils'

describe('validateAuthStatus', () => {
Expand Down
10 changes: 4 additions & 6 deletions vscode/src/chat/utils.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { type AuthStatus, type AuthenticatedAuthStatus, isDotCom } from '@sourcegraph/cody-shared'
import {
type AuthStatus,
type AuthenticatedAuthStatus,
type AuthError,
InvalidAccessTokenError,
isDotCom,
} from '@sourcegraph/cody-shared'
import type { AuthenticationError } from '@sourcegraph/cody-shared'
} from '@sourcegraph/cody-shared/src/sourcegraph-api/errors'
import type { CurrentUserInfo } from '@sourcegraph/cody-shared/src/sourcegraph-api/graphql/client'

type NewAuthStatusOptions = { endpoint: string } & (
| { authenticated: false; error?: AuthenticationError }
| { authenticated: false; error?: AuthError }
| (Pick<
AuthenticatedAuthStatus,
'authenticated' | 'username' | 'hasVerifiedEmail' | 'displayName' | 'avatarURL'
Expand Down
Loading

0 comments on commit c08e156

Please sign in to comment.