@jbrowse/plugin-authentication 2.6.1 → 2.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DropboxOAuthModel/configSchema.js +0 -1
- package/dist/DropboxOAuthModel/index.js +0 -1
- package/dist/DropboxOAuthModel/model.js +0 -1
- package/dist/DropboxOAuthModel/util.js +0 -1
- package/dist/ExternalTokenModel/ExternalTokenEntryForm.js +0 -1
- package/dist/ExternalTokenModel/configSchema.js +0 -1
- package/dist/ExternalTokenModel/index.js +0 -1
- package/dist/ExternalTokenModel/model.js +0 -1
- package/dist/GoogleDriveOAuthModel/GoogleDriveFilehandle.js +0 -1
- package/dist/GoogleDriveOAuthModel/configSchema.js +0 -1
- package/dist/GoogleDriveOAuthModel/index.js +0 -1
- package/dist/GoogleDriveOAuthModel/model.js +0 -1
- package/dist/GoogleDriveOAuthModel/util.js +0 -1
- package/dist/HTTPBasicModel/HTTPBasicLoginForm.js +0 -1
- package/dist/HTTPBasicModel/configSchema.js +0 -1
- package/dist/HTTPBasicModel/index.js +0 -1
- package/dist/HTTPBasicModel/model.js +0 -1
- package/dist/OAuthModel/configSchema.js +0 -1
- package/dist/OAuthModel/index.js +0 -1
- package/dist/OAuthModel/model.js +0 -1
- package/dist/OAuthModel/util.js +0 -1
- package/dist/index.js +0 -1
- package/dist/util.js +0 -1
- package/esm/DropboxOAuthModel/configSchema.js +0 -1
- package/esm/DropboxOAuthModel/index.js +0 -1
- package/esm/DropboxOAuthModel/model.js +0 -1
- package/esm/DropboxOAuthModel/util.js +0 -1
- package/esm/ExternalTokenModel/ExternalTokenEntryForm.js +0 -1
- package/esm/ExternalTokenModel/configSchema.js +0 -1
- package/esm/ExternalTokenModel/index.js +0 -1
- package/esm/ExternalTokenModel/model.js +0 -1
- package/esm/GoogleDriveOAuthModel/GoogleDriveFilehandle.js +0 -1
- package/esm/GoogleDriveOAuthModel/configSchema.js +0 -1
- package/esm/GoogleDriveOAuthModel/index.js +0 -1
- package/esm/GoogleDriveOAuthModel/model.js +0 -1
- package/esm/GoogleDriveOAuthModel/util.js +0 -1
- package/esm/HTTPBasicModel/HTTPBasicLoginForm.js +0 -1
- package/esm/HTTPBasicModel/configSchema.js +0 -1
- package/esm/HTTPBasicModel/index.js +0 -1
- package/esm/HTTPBasicModel/model.js +0 -1
- package/esm/OAuthModel/configSchema.js +0 -1
- package/esm/OAuthModel/index.js +0 -1
- package/esm/OAuthModel/model.js +0 -1
- package/esm/OAuthModel/util.js +0 -1
- package/esm/index.js +0 -1
- package/esm/util.js +0 -1
- package/package.json +3 -4
- package/dist/DropboxOAuthModel/configSchema.js.map +0 -1
- package/dist/DropboxOAuthModel/index.js.map +0 -1
- package/dist/DropboxOAuthModel/model.js.map +0 -1
- package/dist/DropboxOAuthModel/util.js.map +0 -1
- package/dist/ExternalTokenModel/ExternalTokenEntryForm.js.map +0 -1
- package/dist/ExternalTokenModel/configSchema.js.map +0 -1
- package/dist/ExternalTokenModel/index.js.map +0 -1
- package/dist/ExternalTokenModel/model.js.map +0 -1
- package/dist/GoogleDriveOAuthModel/GoogleDriveFilehandle.js.map +0 -1
- package/dist/GoogleDriveOAuthModel/configSchema.js.map +0 -1
- package/dist/GoogleDriveOAuthModel/index.js.map +0 -1
- package/dist/GoogleDriveOAuthModel/model.js.map +0 -1
- package/dist/GoogleDriveOAuthModel/util.js.map +0 -1
- package/dist/HTTPBasicModel/HTTPBasicLoginForm.js.map +0 -1
- package/dist/HTTPBasicModel/configSchema.js.map +0 -1
- package/dist/HTTPBasicModel/index.js.map +0 -1
- package/dist/HTTPBasicModel/model.js.map +0 -1
- package/dist/OAuthModel/configSchema.js.map +0 -1
- package/dist/OAuthModel/index.js.map +0 -1
- package/dist/OAuthModel/model.js.map +0 -1
- package/dist/OAuthModel/util.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/util.js.map +0 -1
- package/esm/DropboxOAuthModel/configSchema.js.map +0 -1
- package/esm/DropboxOAuthModel/index.js.map +0 -1
- package/esm/DropboxOAuthModel/model.js.map +0 -1
- package/esm/DropboxOAuthModel/util.js.map +0 -1
- package/esm/ExternalTokenModel/ExternalTokenEntryForm.js.map +0 -1
- package/esm/ExternalTokenModel/configSchema.js.map +0 -1
- package/esm/ExternalTokenModel/index.js.map +0 -1
- package/esm/ExternalTokenModel/model.js.map +0 -1
- package/esm/GoogleDriveOAuthModel/GoogleDriveFilehandle.js.map +0 -1
- package/esm/GoogleDriveOAuthModel/configSchema.js.map +0 -1
- package/esm/GoogleDriveOAuthModel/index.js.map +0 -1
- package/esm/GoogleDriveOAuthModel/model.js.map +0 -1
- package/esm/GoogleDriveOAuthModel/util.js.map +0 -1
- package/esm/HTTPBasicModel/HTTPBasicLoginForm.js.map +0 -1
- package/esm/HTTPBasicModel/configSchema.js.map +0 -1
- package/esm/HTTPBasicModel/index.js.map +0 -1
- package/esm/HTTPBasicModel/model.js.map +0 -1
- package/esm/OAuthModel/configSchema.js.map +0 -1
- package/esm/OAuthModel/index.js.map +0 -1
- package/esm/OAuthModel/model.js.map +0 -1
- package/esm/OAuthModel/util.js.map +0 -1
- package/esm/index.js.map +0 -1
- package/esm/util.js.map +0 -1
- package/src/DropboxOAuthModel/configSchema.ts +0 -69
- package/src/DropboxOAuthModel/index.ts +0 -2
- package/src/DropboxOAuthModel/model.tsx +0 -122
- package/src/DropboxOAuthModel/util.ts +0 -36
- package/src/ExternalTokenModel/ExternalTokenEntryForm.tsx +0 -55
- package/src/ExternalTokenModel/configSchema.ts +0 -36
- package/src/ExternalTokenModel/index.ts +0 -2
- package/src/ExternalTokenModel/model.tsx +0 -70
- package/src/GoogleDriveOAuthModel/GoogleDriveFilehandle.ts +0 -38
- package/src/GoogleDriveOAuthModel/configSchema.ts +0 -61
- package/src/GoogleDriveOAuthModel/index.ts +0 -2
- package/src/GoogleDriveOAuthModel/model.tsx +0 -124
- package/src/GoogleDriveOAuthModel/util.ts +0 -29
- package/src/HTTPBasicModel/HTTPBasicLoginForm.tsx +0 -68
- package/src/HTTPBasicModel/configSchema.ts +0 -43
- package/src/HTTPBasicModel/index.ts +0 -2
- package/src/HTTPBasicModel/model.tsx +0 -85
- package/src/OAuthModel/configSchema.ts +0 -91
- package/src/OAuthModel/index.ts +0 -2
- package/src/OAuthModel/model.tsx +0 -430
- package/src/OAuthModel/util.ts +0 -33
- package/src/__snapshots__/index.test.js.snap +0 -8
- package/src/index.test.js +0 -96
- package/src/index.ts +0 -108
- package/src/util.ts +0 -25
package/src/OAuthModel/model.tsx
DELETED
|
@@ -1,430 +0,0 @@
|
|
|
1
|
-
import { ConfigurationReference, getConf } from '@jbrowse/core/configuration'
|
|
2
|
-
import { InternetAccount } from '@jbrowse/core/pluggableElementTypes/models'
|
|
3
|
-
import { isElectron, UriLocation } from '@jbrowse/core/util'
|
|
4
|
-
import { Instance, types } from 'mobx-state-tree'
|
|
5
|
-
|
|
6
|
-
// locals
|
|
7
|
-
import { OAuthInternetAccountConfigModel } from './configSchema'
|
|
8
|
-
import {
|
|
9
|
-
fixup,
|
|
10
|
-
generateChallenge,
|
|
11
|
-
processError,
|
|
12
|
-
processTokenResponse,
|
|
13
|
-
} from './util'
|
|
14
|
-
import { getResponseError } from '../util'
|
|
15
|
-
|
|
16
|
-
interface OAuthData {
|
|
17
|
-
client_id: string
|
|
18
|
-
redirect_uri: string
|
|
19
|
-
response_type: 'token' | 'code'
|
|
20
|
-
scope?: string
|
|
21
|
-
code_challenge?: string
|
|
22
|
-
code_challenge_method?: string
|
|
23
|
-
token_access_type?: string
|
|
24
|
-
state?: string
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* #stateModel OAuthInternetAccount
|
|
29
|
-
*/
|
|
30
|
-
const stateModelFactory = (configSchema: OAuthInternetAccountConfigModel) => {
|
|
31
|
-
return InternetAccount.named('OAuthInternetAccount')
|
|
32
|
-
.props({
|
|
33
|
-
/**
|
|
34
|
-
* #property
|
|
35
|
-
*/
|
|
36
|
-
type: types.literal('OAuthInternetAccount'),
|
|
37
|
-
/**
|
|
38
|
-
* #property
|
|
39
|
-
*/
|
|
40
|
-
configuration: ConfigurationReference(configSchema),
|
|
41
|
-
})
|
|
42
|
-
.views(() => {
|
|
43
|
-
let codeVerifier: string | undefined = undefined
|
|
44
|
-
return {
|
|
45
|
-
/**
|
|
46
|
-
* #getter
|
|
47
|
-
*/
|
|
48
|
-
get codeVerifierPKCE() {
|
|
49
|
-
if (codeVerifier) {
|
|
50
|
-
return codeVerifier
|
|
51
|
-
}
|
|
52
|
-
const array = new Uint8Array(32)
|
|
53
|
-
globalThis.crypto.getRandomValues(array)
|
|
54
|
-
codeVerifier = fixup(Buffer.from(array).toString('base64'))
|
|
55
|
-
return codeVerifier
|
|
56
|
-
},
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
.views(self => ({
|
|
60
|
-
/**
|
|
61
|
-
* #getter
|
|
62
|
-
*/
|
|
63
|
-
get authEndpoint(): string {
|
|
64
|
-
return getConf(self, 'authEndpoint')
|
|
65
|
-
},
|
|
66
|
-
/**
|
|
67
|
-
* #getter
|
|
68
|
-
*/
|
|
69
|
-
get tokenEndpoint(): string {
|
|
70
|
-
return getConf(self, 'tokenEndpoint')
|
|
71
|
-
},
|
|
72
|
-
/**
|
|
73
|
-
* #getter
|
|
74
|
-
*/
|
|
75
|
-
get needsPKCE(): boolean {
|
|
76
|
-
return getConf(self, 'needsPKCE')
|
|
77
|
-
},
|
|
78
|
-
/**
|
|
79
|
-
* #getter
|
|
80
|
-
*/
|
|
81
|
-
get clientId(): string {
|
|
82
|
-
return getConf(self, 'clientId')
|
|
83
|
-
},
|
|
84
|
-
/**
|
|
85
|
-
* #getter
|
|
86
|
-
*/
|
|
87
|
-
get scopes(): string {
|
|
88
|
-
return getConf(self, 'scopes')
|
|
89
|
-
},
|
|
90
|
-
/**
|
|
91
|
-
* #method
|
|
92
|
-
* OAuth state parameter:
|
|
93
|
-
* https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1
|
|
94
|
-
*
|
|
95
|
-
* Can override or extend if dynamic state is needed.
|
|
96
|
-
*/
|
|
97
|
-
state(): string | undefined {
|
|
98
|
-
return getConf(self, 'state')
|
|
99
|
-
},
|
|
100
|
-
/**
|
|
101
|
-
* #getter
|
|
102
|
-
*/
|
|
103
|
-
get responseType(): 'token' | 'code' {
|
|
104
|
-
return getConf(self, 'responseType')
|
|
105
|
-
},
|
|
106
|
-
/**
|
|
107
|
-
* #getter
|
|
108
|
-
*/
|
|
109
|
-
get refreshTokenKey() {
|
|
110
|
-
return `${self.internetAccountId}-refreshToken`
|
|
111
|
-
},
|
|
112
|
-
}))
|
|
113
|
-
|
|
114
|
-
.actions(self => ({
|
|
115
|
-
/**
|
|
116
|
-
* #action
|
|
117
|
-
*/
|
|
118
|
-
storeRefreshToken(refreshToken: string) {
|
|
119
|
-
localStorage.setItem(self.refreshTokenKey, refreshToken)
|
|
120
|
-
},
|
|
121
|
-
/**
|
|
122
|
-
* #action
|
|
123
|
-
*/
|
|
124
|
-
removeRefreshToken() {
|
|
125
|
-
localStorage.removeItem(self.refreshTokenKey)
|
|
126
|
-
},
|
|
127
|
-
/**
|
|
128
|
-
* #method
|
|
129
|
-
*/
|
|
130
|
-
retrieveRefreshToken() {
|
|
131
|
-
return localStorage.getItem(self.refreshTokenKey)
|
|
132
|
-
},
|
|
133
|
-
/**
|
|
134
|
-
* #action
|
|
135
|
-
*/
|
|
136
|
-
async exchangeAuthorizationForAccessToken(
|
|
137
|
-
token: string,
|
|
138
|
-
redirectUri: string,
|
|
139
|
-
): Promise<string> {
|
|
140
|
-
const params = new URLSearchParams(
|
|
141
|
-
Object.entries({
|
|
142
|
-
code: token,
|
|
143
|
-
grant_type: 'authorization_code',
|
|
144
|
-
client_id: self.clientId,
|
|
145
|
-
redirect_uri: redirectUri,
|
|
146
|
-
...(self.needsPKCE ? { code_verifier: self.codeVerifierPKCE } : {}),
|
|
147
|
-
}),
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
const response = await fetch(self.tokenEndpoint, {
|
|
151
|
-
method: 'POST',
|
|
152
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
153
|
-
body: params.toString(),
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
if (!response.ok) {
|
|
157
|
-
throw new Error(
|
|
158
|
-
await getResponseError({
|
|
159
|
-
response,
|
|
160
|
-
reason: 'Failed to obtain token',
|
|
161
|
-
}),
|
|
162
|
-
)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const data = await response.json()
|
|
166
|
-
return processTokenResponse(data, token =>
|
|
167
|
-
this.storeRefreshToken(token),
|
|
168
|
-
)
|
|
169
|
-
},
|
|
170
|
-
/**
|
|
171
|
-
* #action
|
|
172
|
-
*/
|
|
173
|
-
async exchangeRefreshForAccessToken(
|
|
174
|
-
refreshToken: string,
|
|
175
|
-
): Promise<string> {
|
|
176
|
-
const response = await fetch(self.tokenEndpoint, {
|
|
177
|
-
method: 'POST',
|
|
178
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
179
|
-
body: new URLSearchParams(
|
|
180
|
-
Object.entries({
|
|
181
|
-
grant_type: 'refresh_token',
|
|
182
|
-
refresh_token: refreshToken,
|
|
183
|
-
client_id: self.clientId,
|
|
184
|
-
}),
|
|
185
|
-
).toString(),
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
if (!response.ok) {
|
|
189
|
-
self.removeToken()
|
|
190
|
-
const text = await response.text()
|
|
191
|
-
throw new Error(
|
|
192
|
-
await getResponseError({
|
|
193
|
-
response,
|
|
194
|
-
statusText: processError(text, () => this.removeRefreshToken()),
|
|
195
|
-
}),
|
|
196
|
-
)
|
|
197
|
-
}
|
|
198
|
-
const data = await response.json()
|
|
199
|
-
return processTokenResponse(data, token =>
|
|
200
|
-
this.storeRefreshToken(token),
|
|
201
|
-
)
|
|
202
|
-
},
|
|
203
|
-
}))
|
|
204
|
-
.actions(self => {
|
|
205
|
-
let listener: (event: MessageEvent) => void | undefined
|
|
206
|
-
let exchangedTokenPromise: Promise<string> | undefined = undefined
|
|
207
|
-
return {
|
|
208
|
-
/**
|
|
209
|
-
* #action
|
|
210
|
-
* used to listen to child window for auth code/token
|
|
211
|
-
*/
|
|
212
|
-
addMessageChannel(
|
|
213
|
-
resolve: (token: string) => void,
|
|
214
|
-
reject: (error: Error) => void,
|
|
215
|
-
) {
|
|
216
|
-
listener = event => {
|
|
217
|
-
// this should probably get better handling, but ignored for now
|
|
218
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
219
|
-
this.finishOAuthWindow(event, resolve, reject)
|
|
220
|
-
}
|
|
221
|
-
window.addEventListener('message', listener)
|
|
222
|
-
},
|
|
223
|
-
/**
|
|
224
|
-
* #action
|
|
225
|
-
*/
|
|
226
|
-
deleteMessageChannel() {
|
|
227
|
-
window.removeEventListener('message', listener)
|
|
228
|
-
},
|
|
229
|
-
/**
|
|
230
|
-
* #action
|
|
231
|
-
*/
|
|
232
|
-
async finishOAuthWindow(
|
|
233
|
-
event: MessageEvent,
|
|
234
|
-
resolve: (token: string) => void,
|
|
235
|
-
reject: (error: Error) => void,
|
|
236
|
-
) {
|
|
237
|
-
if (
|
|
238
|
-
event.data.name !== `JBrowseAuthWindow-${self.internetAccountId}`
|
|
239
|
-
) {
|
|
240
|
-
return this.deleteMessageChannel()
|
|
241
|
-
}
|
|
242
|
-
const redirectUriWithInfo = event.data.redirectUri
|
|
243
|
-
const fixedQueryString = redirectUriWithInfo.replace('#', '?')
|
|
244
|
-
const redirectUrl = new URL(fixedQueryString)
|
|
245
|
-
const queryStringSearch = redirectUrl.search
|
|
246
|
-
const urlParams = new URLSearchParams(queryStringSearch)
|
|
247
|
-
if (urlParams.has('access_token')) {
|
|
248
|
-
const token = urlParams.get('access_token')
|
|
249
|
-
if (!token) {
|
|
250
|
-
return reject(new Error('Error with token endpoint'))
|
|
251
|
-
}
|
|
252
|
-
self.storeToken(token)
|
|
253
|
-
return resolve(token)
|
|
254
|
-
}
|
|
255
|
-
if (urlParams.has('code')) {
|
|
256
|
-
const code = urlParams.get('code')
|
|
257
|
-
if (!code) {
|
|
258
|
-
return reject(new Error('Error with authorization endpoint'))
|
|
259
|
-
}
|
|
260
|
-
try {
|
|
261
|
-
const token = await self.exchangeAuthorizationForAccessToken(
|
|
262
|
-
code,
|
|
263
|
-
redirectUrl.origin + redirectUrl.pathname,
|
|
264
|
-
)
|
|
265
|
-
self.storeToken(token)
|
|
266
|
-
return resolve(token)
|
|
267
|
-
} catch (e) {
|
|
268
|
-
return e instanceof Error
|
|
269
|
-
? reject(e)
|
|
270
|
-
: reject(new Error(String(e)))
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
if (redirectUriWithInfo.includes('access_denied')) {
|
|
274
|
-
return reject(new Error('OAuth flow was cancelled'))
|
|
275
|
-
}
|
|
276
|
-
if (redirectUriWithInfo.includes('error')) {
|
|
277
|
-
return reject(new Error('OAuth flow error: ' + queryStringSearch))
|
|
278
|
-
}
|
|
279
|
-
this.deleteMessageChannel()
|
|
280
|
-
},
|
|
281
|
-
/**
|
|
282
|
-
* #action
|
|
283
|
-
* opens external OAuth flow, popup for web and new browser window for
|
|
284
|
-
* desktop
|
|
285
|
-
*/
|
|
286
|
-
async useEndpointForAuthorization(
|
|
287
|
-
resolve: (token: string) => void,
|
|
288
|
-
reject: (error: Error) => void,
|
|
289
|
-
) {
|
|
290
|
-
const redirectUri = isElectron
|
|
291
|
-
? 'http://localhost/auth'
|
|
292
|
-
: window.location.origin + window.location.pathname
|
|
293
|
-
const data: OAuthData = {
|
|
294
|
-
client_id: self.clientId,
|
|
295
|
-
redirect_uri: redirectUri,
|
|
296
|
-
response_type: self.responseType || 'code',
|
|
297
|
-
token_access_type: 'offline',
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (self.state()) {
|
|
301
|
-
data.state = self.state()
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (self.scopes) {
|
|
305
|
-
data.scope = self.scopes
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
if (self.needsPKCE) {
|
|
309
|
-
data.code_challenge = await generateChallenge(self.codeVerifierPKCE)
|
|
310
|
-
data.code_challenge_method = 'S256'
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const params = new URLSearchParams(Object.entries(data))
|
|
314
|
-
|
|
315
|
-
const url = new URL(self.authEndpoint)
|
|
316
|
-
url.search = params.toString()
|
|
317
|
-
|
|
318
|
-
const eventName = `JBrowseAuthWindow-${self.internetAccountId}`
|
|
319
|
-
if (isElectron) {
|
|
320
|
-
const { ipcRenderer } = window.require('electron')
|
|
321
|
-
const redirectUri = await ipcRenderer.invoke('openAuthWindow', {
|
|
322
|
-
internetAccountId: self.internetAccountId,
|
|
323
|
-
data,
|
|
324
|
-
url: url.toString(),
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
const eventFromDesktop = new MessageEvent('message', {
|
|
328
|
-
data: { name: eventName, redirectUri: redirectUri },
|
|
329
|
-
})
|
|
330
|
-
// may want to improve handling
|
|
331
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
332
|
-
this.finishOAuthWindow(eventFromDesktop, resolve, reject)
|
|
333
|
-
} else {
|
|
334
|
-
window.open(url, eventName, `width=500,height=600,left=0,top=0`)
|
|
335
|
-
}
|
|
336
|
-
},
|
|
337
|
-
/**
|
|
338
|
-
* #action
|
|
339
|
-
*/
|
|
340
|
-
async getTokenFromUser(
|
|
341
|
-
resolve: (token: string) => void,
|
|
342
|
-
reject: (error: Error) => void,
|
|
343
|
-
) {
|
|
344
|
-
const refreshToken = self.retrieveRefreshToken()
|
|
345
|
-
let doUserFlow = true
|
|
346
|
-
|
|
347
|
-
// if there is a refresh token, then try it out, and only if that
|
|
348
|
-
// refresh token succeeds, set doUserFlow to false
|
|
349
|
-
if (refreshToken) {
|
|
350
|
-
try {
|
|
351
|
-
const token = await self.exchangeRefreshForAccessToken(
|
|
352
|
-
refreshToken,
|
|
353
|
-
)
|
|
354
|
-
resolve(token)
|
|
355
|
-
doUserFlow = false
|
|
356
|
-
} catch (e) {
|
|
357
|
-
console.error(e)
|
|
358
|
-
self.removeRefreshToken()
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
if (doUserFlow) {
|
|
362
|
-
this.addMessageChannel(resolve, reject)
|
|
363
|
-
// may want to improve handling
|
|
364
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
365
|
-
this.useEndpointForAuthorization(resolve, reject)
|
|
366
|
-
}
|
|
367
|
-
},
|
|
368
|
-
/**
|
|
369
|
-
* #action
|
|
370
|
-
*/
|
|
371
|
-
async validateToken(token: string, location: UriLocation) {
|
|
372
|
-
const newInit = self.addAuthHeaderToInit({ method: 'HEAD' }, token)
|
|
373
|
-
const response = await fetch(location.uri, newInit)
|
|
374
|
-
if (!response.ok) {
|
|
375
|
-
self.removeToken()
|
|
376
|
-
const refreshToken = self.retrieveRefreshToken()
|
|
377
|
-
if (refreshToken) {
|
|
378
|
-
try {
|
|
379
|
-
if (!exchangedTokenPromise) {
|
|
380
|
-
exchangedTokenPromise =
|
|
381
|
-
self.exchangeRefreshForAccessToken(refreshToken)
|
|
382
|
-
}
|
|
383
|
-
const newToken = await exchangedTokenPromise
|
|
384
|
-
exchangedTokenPromise = undefined
|
|
385
|
-
return newToken
|
|
386
|
-
} catch (err) {
|
|
387
|
-
console.error('Token could not be refreshed', err)
|
|
388
|
-
// let original error be thrown
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
throw new Error(
|
|
393
|
-
await getResponseError({
|
|
394
|
-
response,
|
|
395
|
-
reason: 'Error validating token',
|
|
396
|
-
}),
|
|
397
|
-
)
|
|
398
|
-
}
|
|
399
|
-
return token
|
|
400
|
-
},
|
|
401
|
-
}
|
|
402
|
-
})
|
|
403
|
-
.actions(self => {
|
|
404
|
-
const superGetFetcher = self.getFetcher
|
|
405
|
-
return {
|
|
406
|
-
/**
|
|
407
|
-
* #action
|
|
408
|
-
* Get a fetch method that will add any needed authentication headers to
|
|
409
|
-
* the request before sending it. If location is provided, it will be
|
|
410
|
-
* checked to see if it includes a token in it's pre-auth information.
|
|
411
|
-
*
|
|
412
|
-
* @param loc - UriLocation of the resource
|
|
413
|
-
* @returns A function that can be used to fetch
|
|
414
|
-
*/
|
|
415
|
-
getFetcher(loc?: UriLocation) {
|
|
416
|
-
const fetcher = superGetFetcher(loc)
|
|
417
|
-
return async (input: RequestInfo, init?: RequestInit) => {
|
|
418
|
-
if (loc) {
|
|
419
|
-
await self.validateToken(await self.getToken(loc), loc)
|
|
420
|
-
}
|
|
421
|
-
return fetcher(input, init)
|
|
422
|
-
}
|
|
423
|
-
},
|
|
424
|
-
}
|
|
425
|
-
})
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
export default stateModelFactory
|
|
429
|
-
export type OAuthStateModel = ReturnType<typeof stateModelFactory>
|
|
430
|
-
export type OAuthModel = Instance<OAuthStateModel>
|
package/src/OAuthModel/util.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
export function fixup(buf: string) {
|
|
2
|
-
return buf.replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '')
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export async function generateChallenge(val: string) {
|
|
6
|
-
const sha256 = await import('crypto-js/sha256').then(f => f.default)
|
|
7
|
-
const Base64 = await import('crypto-js/enc-base64')
|
|
8
|
-
return fixup(Base64.stringify(sha256(val)))
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// if response is JSON, checks if it needs to remove tokens in error, or just plain throw
|
|
12
|
-
export function processError(text: string, invalidErrorCb: () => void) {
|
|
13
|
-
try {
|
|
14
|
-
const obj = JSON.parse(text)
|
|
15
|
-
if (obj.error === 'invalid_grant') {
|
|
16
|
-
invalidErrorCb()
|
|
17
|
-
}
|
|
18
|
-
return obj?.error_description ?? text
|
|
19
|
-
} catch (e) {
|
|
20
|
-
/* response text is not json, just use original text as error */
|
|
21
|
-
}
|
|
22
|
-
return text
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function processTokenResponse(
|
|
26
|
-
data: { refresh_token?: string; access_token: string },
|
|
27
|
-
storeRefreshTokenCb: (str: string) => void,
|
|
28
|
-
) {
|
|
29
|
-
if (data.refresh_token) {
|
|
30
|
-
storeRefreshTokenCb(data.refresh_token)
|
|
31
|
-
}
|
|
32
|
-
return data.access_token
|
|
33
|
-
}
|
package/src/index.test.js
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import Plugin from '@jbrowse/core/Plugin'
|
|
2
|
-
import PluginManager from '@jbrowse/core/PluginManager'
|
|
3
|
-
import InternetAccountType from '@jbrowse/core/pluggableElementTypes/InternetAccountType'
|
|
4
|
-
import {
|
|
5
|
-
configSchema as OAuthConfigSchema,
|
|
6
|
-
modelFactory as OAuthInternetAccountModelFactory,
|
|
7
|
-
} from './OAuthModel'
|
|
8
|
-
import {
|
|
9
|
-
configSchema as ExternalTokenConfigSchema,
|
|
10
|
-
modelFactory as ExternalTokenInternetAccountModelFactory,
|
|
11
|
-
} from './ExternalTokenModel'
|
|
12
|
-
import {
|
|
13
|
-
configSchema as HTTPBasicConfigSchema,
|
|
14
|
-
modelFactory as HTTPBasicInternetAccountModelFactory,
|
|
15
|
-
} from './HTTPBasicModel'
|
|
16
|
-
import {
|
|
17
|
-
configSchema as DropboxOAuthConfigSchema,
|
|
18
|
-
modelFactory as DropboxOAuthInternetAccountModelFactory,
|
|
19
|
-
} from './DropboxOAuthModel'
|
|
20
|
-
import {
|
|
21
|
-
configSchema as GoogleDriveOAuthConfigSchema,
|
|
22
|
-
modelFactory as GoogleDriveOAuthInternetAccountModelFactory,
|
|
23
|
-
} from './GoogleDriveOAuthModel'
|
|
24
|
-
import { getSnapshot } from 'mobx-state-tree'
|
|
25
|
-
|
|
26
|
-
// mock warnings to avoid unnecessary outputs
|
|
27
|
-
beforeEach(() => {
|
|
28
|
-
jest.spyOn(console, 'warn').mockImplementation(() => {})
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
afterEach(() => {
|
|
32
|
-
console.warn.mockRestore()
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
class AuthenticationPlugin extends Plugin {
|
|
36
|
-
install(pluginManager) {
|
|
37
|
-
pluginManager.addInternetAccountType(() => {
|
|
38
|
-
return new InternetAccountType({
|
|
39
|
-
name: 'OAuthInternetAccount',
|
|
40
|
-
configSchema: OAuthConfigSchema,
|
|
41
|
-
stateModel: OAuthInternetAccountModelFactory(OAuthConfigSchema),
|
|
42
|
-
})
|
|
43
|
-
})
|
|
44
|
-
pluginManager.addInternetAccountType(() => {
|
|
45
|
-
return new InternetAccountType({
|
|
46
|
-
name: 'ExternalTokenInternetAccount',
|
|
47
|
-
configSchema: ExternalTokenConfigSchema,
|
|
48
|
-
stateModel: ExternalTokenInternetAccountModelFactory(
|
|
49
|
-
ExternalTokenConfigSchema,
|
|
50
|
-
),
|
|
51
|
-
})
|
|
52
|
-
})
|
|
53
|
-
pluginManager.addInternetAccountType(() => {
|
|
54
|
-
return new InternetAccountType({
|
|
55
|
-
name: 'HTTPBasicInternetAccount',
|
|
56
|
-
configSchema: HTTPBasicConfigSchema,
|
|
57
|
-
stateModel: HTTPBasicInternetAccountModelFactory(HTTPBasicConfigSchema),
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
pluginManager.addInternetAccountType(() => {
|
|
61
|
-
return new InternetAccountType({
|
|
62
|
-
name: 'DropboxOAuthInternetAccount',
|
|
63
|
-
configSchema: DropboxOAuthConfigSchema,
|
|
64
|
-
stateModel: DropboxOAuthInternetAccountModelFactory(
|
|
65
|
-
DropboxOAuthConfigSchema,
|
|
66
|
-
),
|
|
67
|
-
})
|
|
68
|
-
})
|
|
69
|
-
pluginManager.addInternetAccountType(() => {
|
|
70
|
-
return new InternetAccountType({
|
|
71
|
-
name: 'GoogleDriveOAuthInternetAccount',
|
|
72
|
-
configSchema: GoogleDriveOAuthConfigSchema,
|
|
73
|
-
stateModel: GoogleDriveOAuthInternetAccountModelFactory(
|
|
74
|
-
GoogleDriveOAuthConfigSchema,
|
|
75
|
-
),
|
|
76
|
-
})
|
|
77
|
-
})
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
test('initialized correctly', () => {
|
|
82
|
-
const pm = new PluginManager([
|
|
83
|
-
new AuthenticationPlugin(),
|
|
84
|
-
]).createPluggableElements()
|
|
85
|
-
|
|
86
|
-
expect(Object.values(pm.internetAccountTypes.registeredTypes).length).toEqual(
|
|
87
|
-
5,
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
const HTTPBasic = pm.getInternetAccountType('HTTPBasicInternetAccount')
|
|
91
|
-
const config = HTTPBasic.configSchema.create({
|
|
92
|
-
type: 'HTTPBasicInternetAccount',
|
|
93
|
-
internetAccountId: 'HTTPBasicTest',
|
|
94
|
-
})
|
|
95
|
-
expect(getSnapshot(config)).toMatchSnapshot()
|
|
96
|
-
})
|
package/src/index.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import Plugin from '@jbrowse/core/Plugin'
|
|
2
|
-
import PluginManager from '@jbrowse/core/PluginManager'
|
|
3
|
-
import InternetAccountType from '@jbrowse/core/pluggableElementTypes/InternetAccountType'
|
|
4
|
-
import {
|
|
5
|
-
configSchema as OAuthConfigSchema,
|
|
6
|
-
modelFactory as OAuthInternetAccountModelFactory,
|
|
7
|
-
} from './OAuthModel'
|
|
8
|
-
import {
|
|
9
|
-
configSchema as ExternalTokenConfigSchema,
|
|
10
|
-
modelFactory as ExternalTokenInternetAccountModelFactory,
|
|
11
|
-
} from './ExternalTokenModel'
|
|
12
|
-
import {
|
|
13
|
-
configSchema as HTTPBasicConfigSchema,
|
|
14
|
-
modelFactory as HTTPBasicInternetAccountModelFactory,
|
|
15
|
-
} from './HTTPBasicModel'
|
|
16
|
-
import {
|
|
17
|
-
configSchema as DropboxOAuthConfigSchema,
|
|
18
|
-
modelFactory as DropboxOAuthInternetAccountModelFactory,
|
|
19
|
-
} from './DropboxOAuthModel'
|
|
20
|
-
import {
|
|
21
|
-
configSchema as GoogleDriveOAuthConfigSchema,
|
|
22
|
-
modelFactory as GoogleDriveOAuthInternetAccountModelFactory,
|
|
23
|
-
} from './GoogleDriveOAuthModel'
|
|
24
|
-
|
|
25
|
-
export default class AuthenticationPlugin extends Plugin {
|
|
26
|
-
name = 'AuthenticationPlugin'
|
|
27
|
-
|
|
28
|
-
exports = {
|
|
29
|
-
OAuthConfigSchema,
|
|
30
|
-
OAuthInternetAccountModelFactory,
|
|
31
|
-
ExternalTokenConfigSchema,
|
|
32
|
-
ExternalTokenInternetAccountModelFactory,
|
|
33
|
-
HTTPBasicConfigSchema,
|
|
34
|
-
HTTPBasicInternetAccountModelFactory,
|
|
35
|
-
DropboxOAuthConfigSchema,
|
|
36
|
-
DropboxOAuthInternetAccountModelFactory,
|
|
37
|
-
GoogleDriveOAuthConfigSchema,
|
|
38
|
-
GoogleDriveOAuthInternetAccountModelFactory,
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
install(pluginManager: PluginManager) {
|
|
42
|
-
pluginManager.addInternetAccountType(() => {
|
|
43
|
-
return new InternetAccountType({
|
|
44
|
-
name: 'OAuthInternetAccount',
|
|
45
|
-
configSchema: OAuthConfigSchema,
|
|
46
|
-
stateModel: OAuthInternetAccountModelFactory(OAuthConfigSchema),
|
|
47
|
-
})
|
|
48
|
-
})
|
|
49
|
-
pluginManager.addInternetAccountType(() => {
|
|
50
|
-
return new InternetAccountType({
|
|
51
|
-
name: 'ExternalTokenInternetAccount',
|
|
52
|
-
configSchema: ExternalTokenConfigSchema,
|
|
53
|
-
stateModel: ExternalTokenInternetAccountModelFactory(
|
|
54
|
-
ExternalTokenConfigSchema,
|
|
55
|
-
),
|
|
56
|
-
})
|
|
57
|
-
})
|
|
58
|
-
pluginManager.addInternetAccountType(() => {
|
|
59
|
-
return new InternetAccountType({
|
|
60
|
-
name: 'HTTPBasicInternetAccount',
|
|
61
|
-
configSchema: HTTPBasicConfigSchema,
|
|
62
|
-
stateModel: HTTPBasicInternetAccountModelFactory(HTTPBasicConfigSchema),
|
|
63
|
-
})
|
|
64
|
-
})
|
|
65
|
-
pluginManager.addInternetAccountType(() => {
|
|
66
|
-
return new InternetAccountType({
|
|
67
|
-
name: 'DropboxOAuthInternetAccount',
|
|
68
|
-
configSchema: DropboxOAuthConfigSchema,
|
|
69
|
-
stateModel: DropboxOAuthInternetAccountModelFactory(
|
|
70
|
-
DropboxOAuthConfigSchema,
|
|
71
|
-
),
|
|
72
|
-
})
|
|
73
|
-
})
|
|
74
|
-
pluginManager.addInternetAccountType(() => {
|
|
75
|
-
return new InternetAccountType({
|
|
76
|
-
name: 'GoogleDriveOAuthInternetAccount',
|
|
77
|
-
configSchema: GoogleDriveOAuthConfigSchema,
|
|
78
|
-
stateModel: GoogleDriveOAuthInternetAccountModelFactory(
|
|
79
|
-
GoogleDriveOAuthConfigSchema,
|
|
80
|
-
),
|
|
81
|
-
})
|
|
82
|
-
})
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export {
|
|
87
|
-
configSchema as OAuthConfigSchema,
|
|
88
|
-
modelFactory as OAuthInternetAccountModelFactory,
|
|
89
|
-
} from './OAuthModel'
|
|
90
|
-
export {
|
|
91
|
-
configSchema as ExternalTokenConfigSchema,
|
|
92
|
-
modelFactory as ExternalTokenInternetAccountModelFactory,
|
|
93
|
-
} from './ExternalTokenModel'
|
|
94
|
-
|
|
95
|
-
export {
|
|
96
|
-
configSchema as HTTPBasicConfigSchema,
|
|
97
|
-
modelFactory as HTTPBasicInternetAccountModelFactory,
|
|
98
|
-
} from './HTTPBasicModel'
|
|
99
|
-
|
|
100
|
-
export {
|
|
101
|
-
configSchema as DropboxOAuthConfigSchema,
|
|
102
|
-
modelFactory as DropboxOAuthInternetAccountModelFactory,
|
|
103
|
-
} from './DropboxOAuthModel'
|
|
104
|
-
|
|
105
|
-
export {
|
|
106
|
-
configSchema as GoogleDriveOAuthConfigSchema,
|
|
107
|
-
modelFactory as GoogleDriveOAuthInternetAccountModelFactory,
|
|
108
|
-
} from './GoogleDriveOAuthModel'
|
package/src/util.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export async function getResponseError({
|
|
2
|
-
response,
|
|
3
|
-
reason,
|
|
4
|
-
statusText,
|
|
5
|
-
}: {
|
|
6
|
-
response: Response
|
|
7
|
-
reason?: string
|
|
8
|
-
statusText?: string
|
|
9
|
-
}) {
|
|
10
|
-
return [
|
|
11
|
-
`HTTP ${response.status}`,
|
|
12
|
-
reason,
|
|
13
|
-
statusText ?? (await getError(response)),
|
|
14
|
-
]
|
|
15
|
-
.filter(f => !!f)
|
|
16
|
-
.join(' - ')
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function getError(response: Response) {
|
|
20
|
-
try {
|
|
21
|
-
return response.text()
|
|
22
|
-
} catch (e) {
|
|
23
|
-
return response.statusText
|
|
24
|
-
}
|
|
25
|
-
}
|