@jbrowse/plugin-authentication 2.5.0 → 2.6.1
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.d.ts +3 -14
- package/dist/DropboxOAuthModel/configSchema.js +0 -8
- package/dist/DropboxOAuthModel/configSchema.js.map +1 -1
- package/dist/DropboxOAuthModel/model.d.ts +35 -33
- package/dist/DropboxOAuthModel/model.js +26 -35
- package/dist/DropboxOAuthModel/model.js.map +1 -1
- package/dist/DropboxOAuthModel/util.d.ts +1 -0
- package/dist/DropboxOAuthModel/util.js +28 -0
- package/dist/DropboxOAuthModel/util.js.map +1 -0
- package/dist/ExternalTokenModel/ExternalTokenEntryForm.d.ts +2 -1
- package/dist/ExternalTokenModel/ExternalTokenEntryForm.js +10 -11
- package/dist/ExternalTokenModel/ExternalTokenEntryForm.js.map +1 -1
- package/dist/GoogleDriveOAuthModel/GoogleDriveFilehandle.d.ts +15 -0
- package/dist/GoogleDriveOAuthModel/GoogleDriveFilehandle.js +20 -0
- package/dist/GoogleDriveOAuthModel/GoogleDriveFilehandle.js.map +1 -0
- package/dist/GoogleDriveOAuthModel/configSchema.d.ts +0 -5
- package/dist/GoogleDriveOAuthModel/model.d.ts +36 -121
- package/dist/GoogleDriveOAuthModel/model.js +38 -51
- package/dist/GoogleDriveOAuthModel/model.js.map +1 -1
- package/dist/GoogleDriveOAuthModel/util.d.ts +1 -0
- package/dist/GoogleDriveOAuthModel/util.js +17 -0
- package/dist/GoogleDriveOAuthModel/util.js.map +1 -0
- package/dist/HTTPBasicModel/HTTPBasicLoginForm.d.ts +2 -1
- package/dist/HTTPBasicModel/HTTPBasicLoginForm.js +17 -21
- package/dist/HTTPBasicModel/HTTPBasicLoginForm.js.map +1 -1
- package/dist/HTTPBasicModel/model.d.ts +36 -4
- package/dist/HTTPBasicModel/model.js +24 -9
- package/dist/HTTPBasicModel/model.js.map +1 -1
- package/dist/OAuthModel/configSchema.d.ts +0 -8
- package/dist/OAuthModel/configSchema.js +1 -9
- package/dist/OAuthModel/configSchema.js.map +1 -1
- package/dist/OAuthModel/model.d.ts +103 -18
- package/dist/OAuthModel/model.js +165 -113
- package/dist/OAuthModel/model.js.map +1 -1
- package/dist/OAuthModel/util.d.ts +7 -0
- package/dist/OAuthModel/util.js +60 -0
- package/dist/OAuthModel/util.js.map +1 -0
- package/dist/index.d.ts +7 -433
- package/dist/util.d.ts +6 -0
- package/dist/util.js +23 -0
- package/dist/util.js.map +1 -0
- package/esm/DropboxOAuthModel/configSchema.d.ts +3 -14
- package/esm/DropboxOAuthModel/configSchema.js +0 -8
- package/esm/DropboxOAuthModel/configSchema.js.map +1 -1
- package/esm/DropboxOAuthModel/model.d.ts +35 -33
- package/esm/DropboxOAuthModel/model.js +26 -35
- package/esm/DropboxOAuthModel/model.js.map +1 -1
- package/esm/DropboxOAuthModel/util.d.ts +1 -0
- package/esm/DropboxOAuthModel/util.js +24 -0
- package/esm/DropboxOAuthModel/util.js.map +1 -0
- package/esm/ExternalTokenModel/ExternalTokenEntryForm.d.ts +2 -1
- package/esm/ExternalTokenModel/ExternalTokenEntryForm.js +10 -11
- package/esm/ExternalTokenModel/ExternalTokenEntryForm.js.map +1 -1
- package/esm/GoogleDriveOAuthModel/GoogleDriveFilehandle.d.ts +15 -0
- package/esm/GoogleDriveOAuthModel/GoogleDriveFilehandle.js +16 -0
- package/esm/GoogleDriveOAuthModel/GoogleDriveFilehandle.js.map +1 -0
- package/esm/GoogleDriveOAuthModel/configSchema.d.ts +0 -5
- package/esm/GoogleDriveOAuthModel/model.d.ts +36 -121
- package/esm/GoogleDriveOAuthModel/model.js +37 -49
- package/esm/GoogleDriveOAuthModel/model.js.map +1 -1
- package/esm/GoogleDriveOAuthModel/util.d.ts +1 -0
- package/esm/GoogleDriveOAuthModel/util.js +13 -0
- package/esm/GoogleDriveOAuthModel/util.js.map +1 -0
- package/esm/HTTPBasicModel/HTTPBasicLoginForm.d.ts +2 -1
- package/esm/HTTPBasicModel/HTTPBasicLoginForm.js +18 -22
- package/esm/HTTPBasicModel/HTTPBasicLoginForm.js.map +1 -1
- package/esm/HTTPBasicModel/model.d.ts +36 -4
- package/esm/HTTPBasicModel/model.js +24 -9
- package/esm/HTTPBasicModel/model.js.map +1 -1
- package/esm/OAuthModel/configSchema.d.ts +0 -8
- package/esm/OAuthModel/configSchema.js +1 -9
- package/esm/OAuthModel/configSchema.js.map +1 -1
- package/esm/OAuthModel/model.d.ts +103 -18
- package/esm/OAuthModel/model.js +164 -86
- package/esm/OAuthModel/model.js.map +1 -1
- package/esm/OAuthModel/util.d.ts +7 -0
- package/esm/OAuthModel/util.js +30 -0
- package/esm/OAuthModel/util.js.map +1 -0
- package/esm/index.d.ts +7 -433
- package/esm/util.d.ts +6 -0
- package/esm/util.js +18 -0
- package/esm/util.js.map +1 -0
- package/package.json +3 -4
- package/src/DropboxOAuthModel/configSchema.ts +0 -8
- package/src/DropboxOAuthModel/model.tsx +35 -54
- package/src/DropboxOAuthModel/util.ts +36 -0
- package/src/ExternalTokenModel/ExternalTokenEntryForm.tsx +39 -41
- package/src/GoogleDriveOAuthModel/GoogleDriveFilehandle.ts +38 -0
- package/src/GoogleDriveOAuthModel/model.tsx +54 -104
- package/src/GoogleDriveOAuthModel/util.ts +29 -0
- package/src/HTTPBasicModel/HTTPBasicLoginForm.tsx +53 -56
- package/src/HTTPBasicModel/model.tsx +26 -11
- package/src/OAuthModel/configSchema.ts +2 -9
- package/src/OAuthModel/model.tsx +190 -108
- package/src/OAuthModel/util.ts +33 -0
- package/src/util.ts +25 -0
package/src/OAuthModel/model.tsx
CHANGED
|
@@ -2,10 +2,16 @@ import { ConfigurationReference, getConf } from '@jbrowse/core/configuration'
|
|
|
2
2
|
import { InternetAccount } from '@jbrowse/core/pluggableElementTypes/models'
|
|
3
3
|
import { isElectron, UriLocation } from '@jbrowse/core/util'
|
|
4
4
|
import { Instance, types } from 'mobx-state-tree'
|
|
5
|
-
import jwtDecode, { JwtPayload } from 'jwt-decode'
|
|
6
5
|
|
|
7
6
|
// locals
|
|
8
7
|
import { OAuthInternetAccountConfigModel } from './configSchema'
|
|
8
|
+
import {
|
|
9
|
+
fixup,
|
|
10
|
+
generateChallenge,
|
|
11
|
+
processError,
|
|
12
|
+
processTokenResponse,
|
|
13
|
+
} from './util'
|
|
14
|
+
import { getResponseError } from '../util'
|
|
9
15
|
|
|
10
16
|
interface OAuthData {
|
|
11
17
|
client_id: string
|
|
@@ -18,19 +24,27 @@ interface OAuthData {
|
|
|
18
24
|
state?: string
|
|
19
25
|
}
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
/**
|
|
28
|
+
* #stateModel OAuthInternetAccount
|
|
29
|
+
*/
|
|
25
30
|
const stateModelFactory = (configSchema: OAuthInternetAccountConfigModel) => {
|
|
26
31
|
return InternetAccount.named('OAuthInternetAccount')
|
|
27
32
|
.props({
|
|
33
|
+
/**
|
|
34
|
+
* #property
|
|
35
|
+
*/
|
|
28
36
|
type: types.literal('OAuthInternetAccount'),
|
|
37
|
+
/**
|
|
38
|
+
* #property
|
|
39
|
+
*/
|
|
29
40
|
configuration: ConfigurationReference(configSchema),
|
|
30
41
|
})
|
|
31
42
|
.views(() => {
|
|
32
43
|
let codeVerifier: string | undefined = undefined
|
|
33
44
|
return {
|
|
45
|
+
/**
|
|
46
|
+
* #getter
|
|
47
|
+
*/
|
|
34
48
|
get codeVerifierPKCE() {
|
|
35
49
|
if (codeVerifier) {
|
|
36
50
|
return codeVerifier
|
|
@@ -43,61 +57,95 @@ const stateModelFactory = (configSchema: OAuthInternetAccountConfigModel) => {
|
|
|
43
57
|
}
|
|
44
58
|
})
|
|
45
59
|
.views(self => ({
|
|
60
|
+
/**
|
|
61
|
+
* #getter
|
|
62
|
+
*/
|
|
46
63
|
get authEndpoint(): string {
|
|
47
64
|
return getConf(self, 'authEndpoint')
|
|
48
65
|
},
|
|
66
|
+
/**
|
|
67
|
+
* #getter
|
|
68
|
+
*/
|
|
49
69
|
get tokenEndpoint(): string {
|
|
50
70
|
return getConf(self, 'tokenEndpoint')
|
|
51
71
|
},
|
|
72
|
+
/**
|
|
73
|
+
* #getter
|
|
74
|
+
*/
|
|
52
75
|
get needsPKCE(): boolean {
|
|
53
76
|
return getConf(self, 'needsPKCE')
|
|
54
77
|
},
|
|
78
|
+
/**
|
|
79
|
+
* #getter
|
|
80
|
+
*/
|
|
55
81
|
get clientId(): string {
|
|
56
82
|
return getConf(self, 'clientId')
|
|
57
83
|
},
|
|
84
|
+
/**
|
|
85
|
+
* #getter
|
|
86
|
+
*/
|
|
58
87
|
get scopes(): string {
|
|
59
88
|
return getConf(self, 'scopes')
|
|
60
89
|
},
|
|
61
90
|
/**
|
|
62
|
-
*
|
|
91
|
+
* #method
|
|
92
|
+
* OAuth state parameter:
|
|
93
|
+
* https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1
|
|
94
|
+
*
|
|
63
95
|
* Can override or extend if dynamic state is needed.
|
|
64
96
|
*/
|
|
65
97
|
state(): string | undefined {
|
|
66
|
-
return getConf(self, 'state')
|
|
98
|
+
return getConf(self, 'state')
|
|
67
99
|
},
|
|
100
|
+
/**
|
|
101
|
+
* #getter
|
|
102
|
+
*/
|
|
68
103
|
get responseType(): 'token' | 'code' {
|
|
69
104
|
return getConf(self, 'responseType')
|
|
70
105
|
},
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
106
|
+
/**
|
|
107
|
+
* #getter
|
|
108
|
+
*/
|
|
74
109
|
get refreshTokenKey() {
|
|
75
110
|
return `${self.internetAccountId}-refreshToken`
|
|
76
111
|
},
|
|
77
112
|
}))
|
|
113
|
+
|
|
78
114
|
.actions(self => ({
|
|
115
|
+
/**
|
|
116
|
+
* #action
|
|
117
|
+
*/
|
|
79
118
|
storeRefreshToken(refreshToken: string) {
|
|
80
119
|
localStorage.setItem(self.refreshTokenKey, refreshToken)
|
|
81
120
|
},
|
|
121
|
+
/**
|
|
122
|
+
* #action
|
|
123
|
+
*/
|
|
82
124
|
removeRefreshToken() {
|
|
83
125
|
localStorage.removeItem(self.refreshTokenKey)
|
|
84
126
|
},
|
|
127
|
+
/**
|
|
128
|
+
* #method
|
|
129
|
+
*/
|
|
85
130
|
retrieveRefreshToken() {
|
|
86
131
|
return localStorage.getItem(self.refreshTokenKey)
|
|
87
132
|
},
|
|
133
|
+
/**
|
|
134
|
+
* #action
|
|
135
|
+
*/
|
|
88
136
|
async exchangeAuthorizationForAccessToken(
|
|
89
137
|
token: string,
|
|
90
138
|
redirectUri: string,
|
|
91
139
|
): Promise<string> {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
+
)
|
|
101
149
|
|
|
102
150
|
const response = await fetch(self.tokenEndpoint, {
|
|
103
151
|
method: 'POST',
|
|
@@ -106,74 +154,61 @@ const stateModelFactory = (configSchema: OAuthInternetAccountConfigModel) => {
|
|
|
106
154
|
})
|
|
107
155
|
|
|
108
156
|
if (!response.ok) {
|
|
109
|
-
let errorMessage
|
|
110
|
-
try {
|
|
111
|
-
errorMessage = await response.text()
|
|
112
|
-
} catch (error) {
|
|
113
|
-
errorMessage = ''
|
|
114
|
-
}
|
|
115
157
|
throw new Error(
|
|
116
|
-
|
|
117
|
-
response
|
|
118
|
-
|
|
158
|
+
await getResponseError({
|
|
159
|
+
response,
|
|
160
|
+
reason: 'Failed to obtain token',
|
|
161
|
+
}),
|
|
119
162
|
)
|
|
120
163
|
}
|
|
121
164
|
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
this.storeRefreshToken(
|
|
125
|
-
|
|
126
|
-
return accessToken.access_token
|
|
165
|
+
const data = await response.json()
|
|
166
|
+
return processTokenResponse(data, token =>
|
|
167
|
+
this.storeRefreshToken(token),
|
|
168
|
+
)
|
|
127
169
|
},
|
|
170
|
+
/**
|
|
171
|
+
* #action
|
|
172
|
+
*/
|
|
128
173
|
async exchangeRefreshForAccessToken(
|
|
129
174
|
refreshToken: string,
|
|
130
175
|
): Promise<string> {
|
|
131
|
-
const data = {
|
|
132
|
-
grant_type: 'refresh_token',
|
|
133
|
-
refresh_token: refreshToken,
|
|
134
|
-
client_id: self.clientId,
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const params = new URLSearchParams(Object.entries(data))
|
|
138
|
-
|
|
139
176
|
const response = await fetch(self.tokenEndpoint, {
|
|
140
177
|
method: 'POST',
|
|
141
178
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
142
|
-
body:
|
|
179
|
+
body: new URLSearchParams(
|
|
180
|
+
Object.entries({
|
|
181
|
+
grant_type: 'refresh_token',
|
|
182
|
+
refresh_token: refreshToken,
|
|
183
|
+
client_id: self.clientId,
|
|
184
|
+
}),
|
|
185
|
+
).toString(),
|
|
143
186
|
})
|
|
144
187
|
|
|
145
188
|
if (!response.ok) {
|
|
146
189
|
self.removeToken()
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
const obj = JSON.parse(text)
|
|
150
|
-
if (obj.error === 'invalid_grant') {
|
|
151
|
-
this.removeRefreshToken()
|
|
152
|
-
}
|
|
153
|
-
text = obj?.error_description ?? text
|
|
154
|
-
} catch (e) {
|
|
155
|
-
/* just use original text as error */
|
|
156
|
-
}
|
|
157
|
-
|
|
190
|
+
const text = await response.text()
|
|
158
191
|
throw new Error(
|
|
159
|
-
|
|
160
|
-
response
|
|
161
|
-
|
|
192
|
+
await getResponseError({
|
|
193
|
+
response,
|
|
194
|
+
statusText: processError(text, () => this.removeRefreshToken()),
|
|
195
|
+
}),
|
|
162
196
|
)
|
|
163
197
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
return accessToken.access_token
|
|
198
|
+
const data = await response.json()
|
|
199
|
+
return processTokenResponse(data, token =>
|
|
200
|
+
this.storeRefreshToken(token),
|
|
201
|
+
)
|
|
170
202
|
},
|
|
171
203
|
}))
|
|
172
204
|
.actions(self => {
|
|
173
|
-
let listener: (event: MessageEvent) => void
|
|
174
|
-
let
|
|
205
|
+
let listener: (event: MessageEvent) => void | undefined
|
|
206
|
+
let exchangedTokenPromise: Promise<string> | undefined = undefined
|
|
175
207
|
return {
|
|
176
|
-
|
|
208
|
+
/**
|
|
209
|
+
* #action
|
|
210
|
+
* used to listen to child window for auth code/token
|
|
211
|
+
*/
|
|
177
212
|
addMessageChannel(
|
|
178
213
|
resolve: (token: string) => void,
|
|
179
214
|
reject: (error: Error) => void,
|
|
@@ -185,9 +220,15 @@ const stateModelFactory = (configSchema: OAuthInternetAccountConfigModel) => {
|
|
|
185
220
|
}
|
|
186
221
|
window.addEventListener('message', listener)
|
|
187
222
|
},
|
|
223
|
+
/**
|
|
224
|
+
* #action
|
|
225
|
+
*/
|
|
188
226
|
deleteMessageChannel() {
|
|
189
227
|
window.removeEventListener('message', listener)
|
|
190
228
|
},
|
|
229
|
+
/**
|
|
230
|
+
* #action
|
|
231
|
+
*/
|
|
191
232
|
async finishOAuthWindow(
|
|
192
233
|
event: MessageEvent,
|
|
193
234
|
resolve: (token: string) => void,
|
|
@@ -223,21 +264,25 @@ const stateModelFactory = (configSchema: OAuthInternetAccountConfigModel) => {
|
|
|
223
264
|
)
|
|
224
265
|
self.storeToken(token)
|
|
225
266
|
return resolve(token)
|
|
226
|
-
} catch (
|
|
227
|
-
return
|
|
228
|
-
? reject(
|
|
229
|
-
: reject(new Error(String(
|
|
267
|
+
} catch (e) {
|
|
268
|
+
return e instanceof Error
|
|
269
|
+
? reject(e)
|
|
270
|
+
: reject(new Error(String(e)))
|
|
230
271
|
}
|
|
231
272
|
}
|
|
232
273
|
if (redirectUriWithInfo.includes('access_denied')) {
|
|
233
274
|
return reject(new Error('OAuth flow was cancelled'))
|
|
234
275
|
}
|
|
235
276
|
if (redirectUriWithInfo.includes('error')) {
|
|
236
|
-
return reject(new Error('
|
|
277
|
+
return reject(new Error('OAuth flow error: ' + queryStringSearch))
|
|
237
278
|
}
|
|
238
279
|
this.deleteMessageChannel()
|
|
239
280
|
},
|
|
240
|
-
|
|
281
|
+
/**
|
|
282
|
+
* #action
|
|
283
|
+
* opens external OAuth flow, popup for web and new browser window for
|
|
284
|
+
* desktop
|
|
285
|
+
*/
|
|
241
286
|
async useEndpointForAuthorization(
|
|
242
287
|
resolve: (token: string) => void,
|
|
243
288
|
reject: (error: Error) => void,
|
|
@@ -249,6 +294,7 @@ const stateModelFactory = (configSchema: OAuthInternetAccountConfigModel) => {
|
|
|
249
294
|
client_id: self.clientId,
|
|
250
295
|
redirect_uri: redirectUri,
|
|
251
296
|
response_type: self.responseType || 'code',
|
|
297
|
+
token_access_type: 'offline',
|
|
252
298
|
}
|
|
253
299
|
|
|
254
300
|
if (self.state()) {
|
|
@@ -260,21 +306,10 @@ const stateModelFactory = (configSchema: OAuthInternetAccountConfigModel) => {
|
|
|
260
306
|
}
|
|
261
307
|
|
|
262
308
|
if (self.needsPKCE) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const sha256 = await import('crypto-js/sha256').then(f => f.default)
|
|
266
|
-
const Base64 = await import('crypto-js/enc-base64')
|
|
267
|
-
const codeChallenge = fixup(
|
|
268
|
-
Base64.stringify(sha256(codeVerifierPKCE)),
|
|
269
|
-
)
|
|
270
|
-
data.code_challenge = codeChallenge
|
|
309
|
+
data.code_challenge = await generateChallenge(self.codeVerifierPKCE)
|
|
271
310
|
data.code_challenge_method = 'S256'
|
|
272
311
|
}
|
|
273
312
|
|
|
274
|
-
if (self.hasRefreshToken) {
|
|
275
|
-
data.token_access_type = 'offline'
|
|
276
|
-
}
|
|
277
|
-
|
|
278
313
|
const params = new URLSearchParams(Object.entries(data))
|
|
279
314
|
|
|
280
315
|
const url = new URL(self.authEndpoint)
|
|
@@ -296,51 +331,98 @@ const stateModelFactory = (configSchema: OAuthInternetAccountConfigModel) => {
|
|
|
296
331
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
297
332
|
this.finishOAuthWindow(eventFromDesktop, resolve, reject)
|
|
298
333
|
} else {
|
|
299
|
-
|
|
300
|
-
window.open(url, eventName, options)
|
|
334
|
+
window.open(url, eventName, `width=500,height=600,left=0,top=0`)
|
|
301
335
|
}
|
|
302
336
|
},
|
|
337
|
+
/**
|
|
338
|
+
* #action
|
|
339
|
+
*/
|
|
303
340
|
async getTokenFromUser(
|
|
304
341
|
resolve: (token: string) => void,
|
|
305
342
|
reject: (error: Error) => void,
|
|
306
|
-
)
|
|
307
|
-
const refreshToken =
|
|
308
|
-
|
|
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
|
|
309
349
|
if (refreshToken) {
|
|
310
|
-
|
|
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)
|
|
311
366
|
}
|
|
312
|
-
this.addMessageChannel(resolve, reject)
|
|
313
|
-
// may want to improve handling
|
|
314
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
315
|
-
this.useEndpointForAuthorization(resolve, reject)
|
|
316
367
|
},
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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()
|
|
325
377
|
if (refreshToken) {
|
|
326
378
|
try {
|
|
327
|
-
if (!
|
|
328
|
-
|
|
379
|
+
if (!exchangedTokenPromise) {
|
|
380
|
+
exchangedTokenPromise =
|
|
329
381
|
self.exchangeRefreshForAccessToken(refreshToken)
|
|
330
382
|
}
|
|
331
|
-
const newToken = await
|
|
332
|
-
|
|
383
|
+
const newToken = await exchangedTokenPromise
|
|
384
|
+
exchangedTokenPromise = undefined
|
|
385
|
+
return newToken
|
|
333
386
|
} catch (err) {
|
|
334
|
-
|
|
387
|
+
console.error('Token could not be refreshed', err)
|
|
388
|
+
// let original error be thrown
|
|
335
389
|
}
|
|
336
390
|
}
|
|
337
|
-
|
|
338
|
-
|
|
391
|
+
|
|
392
|
+
throw new Error(
|
|
393
|
+
await getResponseError({
|
|
394
|
+
response,
|
|
395
|
+
reason: 'Error validating token',
|
|
396
|
+
}),
|
|
397
|
+
)
|
|
339
398
|
}
|
|
340
399
|
return token
|
|
341
400
|
},
|
|
342
401
|
}
|
|
343
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
|
+
})
|
|
344
426
|
}
|
|
345
427
|
|
|
346
428
|
export default stateModelFactory
|
|
@@ -0,0 +1,33 @@
|
|
|
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/util.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
}
|