Skip to content
This repository was archived by the owner on Jan 22, 2019. It is now read-only.

Commit afe3ea3

Browse files
authored
fix: access token logic (#267)
This commit fixes access token usage by: - Not setting the `authorization` header when requesting from the options page. Chrome suddenly becan rejecting the request in pre-flight checks from hte options page. - _Only_ creating access tokens when the server connection is checked in the options page. - Save the access token ID so we can ensure it still exists on the server.
1 parent 189cf30 commit afe3ea3

File tree

6 files changed

+115
-43
lines changed

6 files changed

+115
-43
lines changed

src/browser/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,14 @@ export const featureFlagDefaults: FeatureFlags = {
2020
newInject: false,
2121
}
2222

23+
export interface AccessToken {
24+
id: string
25+
token: string
26+
}
27+
2328
/** A map where the key is the server URL and the value is the token. */
2429
export interface AccessTokens {
25-
[url: string]: string
30+
[url: string]: AccessToken
2631
}
2732

2833
// TODO(chris) Switch to Partial<StorageItems> to eliminate bugs caused by

src/extension/scripts/background.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,19 @@ storage.addSyncMigration((items, set, remove) => {
166166
set({ accessTokens: {} })
167167
}
168168

169+
if (items.accessTokens) {
170+
const accessTokens = {}
171+
172+
for (const url of Object.keys(items.accessTokens)) {
173+
const token = items.accessTokens[url]
174+
if (typeof token !== 'string' && token.id && token.token) {
175+
accessTokens[url] = token
176+
}
177+
}
178+
179+
set({ accessTokens })
180+
}
181+
169182
if (items.phabricatorURL) {
170183
remove('phabricatorURL')
171184

src/shared/auth/access_token.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,21 @@ import { omit } from 'lodash'
22
import { Observable } from 'rxjs'
33
import { switchMap } from 'rxjs/operators'
44
import storage from '../../browser/storage'
5+
import { AccessToken } from '../../browser/types'
56

6-
export const getAccessToken = (url: string): Observable<string | undefined> =>
7+
export const getAccessToken = (url: string): Observable<AccessToken | undefined> =>
78
new Observable(observer => {
89
storage.getSync(items => {
910
observer.next(items.accessTokens[url])
1011
observer.complete()
1112
})
1213
})
1314

14-
export const setAccessToken = (url: string) => (tokens: Observable<string>): Observable<string> =>
15+
export const setAccessToken = (url: string) => (tokens: Observable<AccessToken>): Observable<AccessToken> =>
1516
tokens.pipe(
1617
switchMap(
1718
token =>
18-
new Observable<string>(observer => {
19+
new Observable(observer => {
1920
storage.getSync(({ accessTokens }) =>
2021
storage.setSync({ accessTokens: { ...accessTokens, [url]: token } }, () => {
2122
observer.next(token)

src/shared/backend/auth.ts

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,70 @@
1+
import { Observable } from 'rxjs'
12
import { map } from 'rxjs/operators'
3+
import { AccessToken } from '../../browser/types'
24
import { GQL } from '../../types/gqlschema'
35
import { getPlatformName } from '../util/context'
46
import { memoizeObservable } from '../util/memoize'
57
import { getContext } from './context'
68
import { createAggregateError } from './errors'
7-
import { mutateGraphQL } from './graphql'
9+
import { mutateGraphQL, queryGraphQL } from './graphql'
810

911
/**
1012
* Create an access token for the current user on the currently configured
1113
* sourcegraph instance.
1214
*/
13-
export const createAccessToken = memoizeObservable((userID: GQL.ID) =>
14-
mutateGraphQL({
15-
ctx: getContext({ repoKey: '' }),
16-
request: `
15+
export const createAccessToken = memoizeObservable(
16+
(userID: GQL.ID): Observable<AccessToken> =>
17+
mutateGraphQL({
18+
ctx: getContext({ repoKey: '' }),
19+
request: `
1720
mutation CreateAccessToken($userID: ID!, $scopes: [String!]!, $note: String!) {
1821
createAccessToken(user: $userID, scopes: $scopes, note: $note) {
1922
id
2023
token
2124
}
2225
}
2326
`,
24-
variables: { userID, scopes: ['user:all'], note: `sourcegraph-${getPlatformName()}` },
25-
useAccessToken: false,
26-
}).pipe(
27-
map(({ data, errors }) => {
28-
if (!data || !data.createAccessToken || (errors && errors.length > 0)) {
29-
throw createAggregateError(errors)
27+
variables: { userID, scopes: ['user:all'], note: `sourcegraph-${getPlatformName()}` },
28+
useAccessToken: false,
29+
}).pipe(
30+
map(({ data, errors }) => {
31+
if (!data || !data.createAccessToken || (errors && errors.length > 0)) {
32+
throw createAggregateError(errors)
33+
}
34+
return data.createAccessToken
35+
})
36+
)
37+
)
38+
39+
export const fetchAccessTokenIDs = memoizeObservable(
40+
(userID: GQL.ID): Observable<Pick<AccessToken, 'id'>[]> =>
41+
queryGraphQL({
42+
ctx: getContext({ repoKey: '' }),
43+
request: `
44+
query AccessTokenIDs {
45+
currentUser {
46+
accessTokens {
47+
nodes {
48+
id
49+
}
50+
}
51+
}
3052
}
31-
return data.createAccessToken.token
32-
})
33-
)
53+
`,
54+
variables: { userID, scopes: ['user:all'], note: `sourcegraph-${getPlatformName()}` },
55+
useAccessToken: false,
56+
}).pipe(
57+
map(({ data, errors }) => {
58+
if (
59+
!data ||
60+
!data.currentUser ||
61+
!data.currentUser.accessTokens ||
62+
!data.currentUser.accessTokens.nodes ||
63+
(errors && errors.length > 0)
64+
) {
65+
throw createAggregateError(errors)
66+
}
67+
return data.currentUser.accessTokens.nodes
68+
})
69+
)
3470
)

src/shared/backend/headers.tsx

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,9 @@
11
import { Observable, of } from 'rxjs'
2-
import { filter, map, switchMap } from 'rxjs/operators'
2+
import { map, switchMap } from 'rxjs/operators'
33

4-
import { isDefined } from '@sourcegraph/codeintellify/lib/helpers'
5-
import { getAccessToken, setAccessToken } from '../auth/access_token'
6-
import { isInPage, isPhabricator } from '../context'
4+
import { getAccessToken } from '../auth/access_token'
5+
import { isContent, isInPage, isPhabricator } from '../context'
76
import { getExtensionVersionSync, getPlatformName, isSourcegraphDotCom } from '../util/context'
8-
import { createAccessToken } from './auth'
9-
import { fetchCurrentUser } from './server'
10-
11-
const withAccessToken = (url: string) =>
12-
getAccessToken(url).pipe(
13-
switchMap(token => {
14-
if (token) {
15-
return of(token)
16-
}
17-
18-
return fetchCurrentUser(false).pipe(
19-
filter(isDefined),
20-
switchMap(user => createAccessToken(user.id).pipe(setAccessToken(url)))
21-
)
22-
})
23-
)
247

258
/**
269
* getHeaders emits the required headers for making requests to Sourcegraph server instances.
@@ -41,11 +24,11 @@ export function getHeaders(
4124
}
4225

4326
return of(url).pipe(
44-
switchMap(url => (useToken && !isSourcegraphDotCom(url) ? withAccessToken(url) : of(undefined))),
27+
switchMap(url => (isContent && useToken && !isSourcegraphDotCom(url) ? getAccessToken(url) : of(undefined))),
4528
map(accessToken => {
4629
const headers = new Headers()
4730
if (accessToken) {
48-
headers.append('Authorization', `token ${accessToken}`)
31+
headers.append('Authorization', `token ${accessToken.token}`)
4932
}
5033

5134
return headers

src/shared/components/options/ConnectionCard.tsx

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { propertyIsDefined } from '@sourcegraph/codeintellify/lib/helpers'
12
import * as React from 'react'
23
import {
34
Alert,
@@ -14,12 +15,15 @@ import {
1415
ListGroupItemHeading,
1516
Row,
1617
} from 'reactstrap'
17-
import { Subscription } from 'rxjs'
18+
import { of, Subscription } from 'rxjs'
19+
import { filter, map, switchMap } from 'rxjs/operators'
1820
import * as permissions from '../../../browser/permissions'
1921
import storage from '../../../browser/storage'
2022
import { StorageItems } from '../../../browser/types'
2123
import { GQL } from '../../../types/gqlschema'
22-
import { fetchSite } from '../../backend/server'
24+
import { getAccessToken, setAccessToken } from '../../auth/access_token'
25+
import { createAccessToken, fetchAccessTokenIDs } from '../../backend/auth'
26+
import { fetchCurrentUser, fetchSite } from '../../backend/server'
2327
import { DEFAULT_SOURCEGRAPH_URL, isSourcegraphDotCom, setSourcegraphUrl, sourcegraphUrl } from '../../util/context'
2428

2529
interface Props {
@@ -232,8 +236,10 @@ export class ConnectionCard extends React.Component<Props, State> {
232236
}
233237

234238
private checkConnection = (): void => {
239+
const fetchingSite = fetchSite()
240+
235241
this.subscriptions.add(
236-
fetchSite().subscribe(
242+
fetchingSite.subscribe(
237243
site => {
238244
this.setState({ site })
239245
},
@@ -242,6 +248,34 @@ export class ConnectionCard extends React.Component<Props, State> {
242248
}
243249
)
244250
)
251+
252+
this.subscriptions.add(
253+
// Ensure the site is valid.
254+
fetchingSite
255+
.pipe(
256+
// Get the access token for this server if we have it.
257+
switchMap(() => getAccessToken(sourcegraphUrl)),
258+
switchMap(token => fetchCurrentUser(false).pipe(map(user => ({ user, token })))),
259+
filter(propertyIsDefined('user')),
260+
// Get the IDs for all access tokens for the user.
261+
switchMap(({ token, user }) =>
262+
fetchAccessTokenIDs(user.id).pipe(map(usersTokenIDs => ({ usersTokenIDs, user, token })))
263+
),
264+
// Make sure the token still exists on the server. If it
265+
// does exits, use it, otherwise create a new one.
266+
switchMap(({ user, token, usersTokenIDs }) => {
267+
const tokenExists = token && usersTokenIDs.map(({ id }) => id).includes(token.id)
268+
269+
return token && tokenExists
270+
? of(token)
271+
: createAccessToken(user.id).pipe(setAccessToken(sourcegraphUrl))
272+
})
273+
)
274+
.subscribe(() => {
275+
// We don't need to do anything with the token now. We just
276+
// needed to ensure we had one saved.
277+
})
278+
)
245279
}
246280

247281
public render(): JSX.Element | null {

0 commit comments

Comments
 (0)