@jbrowse/plugin-authentication 4.3.0 → 5.0.0-beta.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/esm/DropboxOAuthModel/DropboxIcon.d.ts +1 -1
- package/esm/DropboxOAuthModel/configSchema.d.ts +110 -82
- package/esm/DropboxOAuthModel/configSchema.js +32 -2
- package/esm/DropboxOAuthModel/index.js +2 -2
- package/esm/DropboxOAuthModel/model.d.ts +235 -201
- package/esm/DropboxOAuthModel/model.js +46 -21
- package/esm/DropboxOAuthModel/util.d.ts +1 -1
- package/esm/DropboxOAuthModel/util.js +9 -12
- package/esm/ExternalTokenModel/ExternalTokenEntryForm.d.ts +1 -1
- package/esm/ExternalTokenModel/ExternalTokenEntryForm.js +11 -15
- package/esm/ExternalTokenModel/configSchema.d.ts +43 -24
- package/esm/ExternalTokenModel/configSchema.js +22 -1
- package/esm/ExternalTokenModel/index.js +2 -2
- package/esm/ExternalTokenModel/model.d.ts +47 -71
- package/esm/ExternalTokenModel/model.js +11 -54
- package/esm/GoogleDriveOAuthModel/GoogleDriveFilehandle.d.ts +2 -9
- package/esm/GoogleDriveOAuthModel/GoogleDriveFilehandle.js +27 -6
- package/esm/GoogleDriveOAuthModel/GoogleDriveIcon.d.ts +1 -1
- package/esm/GoogleDriveOAuthModel/configSchema.d.ts +109 -82
- package/esm/GoogleDriveOAuthModel/configSchema.js +32 -3
- package/esm/GoogleDriveOAuthModel/index.js +2 -2
- package/esm/GoogleDriveOAuthModel/model.d.ts +230 -202
- package/esm/GoogleDriveOAuthModel/model.js +45 -22
- package/esm/GoogleDriveOAuthModel/util.d.ts +1 -1
- package/esm/GoogleDriveOAuthModel/util.js +2 -11
- package/esm/HTTPBasicModel/HTTPBasicLoginForm.d.ts +1 -1
- package/esm/HTTPBasicModel/HTTPBasicLoginForm.js +17 -22
- package/esm/HTTPBasicModel/configSchema.d.ts +51 -28
- package/esm/HTTPBasicModel/configSchema.js +26 -1
- package/esm/HTTPBasicModel/index.js +2 -2
- package/esm/HTTPBasicModel/model.d.ts +55 -76
- package/esm/HTTPBasicModel/model.js +21 -51
- package/esm/OAuthModel/OAuthLoginPrompt.d.ts +4 -0
- package/esm/OAuthModel/OAuthLoginPrompt.js +12 -0
- package/esm/OAuthModel/configSchema.d.ts +79 -52
- package/esm/OAuthModel/configSchema.js +30 -1
- package/esm/OAuthModel/index.js +2 -2
- package/esm/OAuthModel/model.d.ts +218 -114
- package/esm/OAuthModel/model.js +361 -217
- package/esm/OAuthModel/util.d.ts +46 -7
- package/esm/OAuthModel/util.js +159 -16
- package/esm/index.d.ts +295 -981
- package/esm/index.js +29 -45
- package/esm/lazyLoginForms.d.ts +18 -0
- package/esm/lazyLoginForms.js +25 -0
- package/esm/tokenEntryModelFactory.d.ts +125 -0
- package/esm/tokenEntryModelFactory.js +85 -0
- package/esm/util.d.ts +10 -0
- package/esm/util.js +24 -2
- package/package.json +10 -13
package/esm/OAuthModel/util.d.ts
CHANGED
|
@@ -1,7 +1,46 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
export
|
|
1
|
+
export interface OAuthWindowParams {
|
|
2
|
+
internetAccountId: string;
|
|
3
|
+
expectedState: string | undefined;
|
|
4
|
+
exchangeAuthorizationCode: (code: string, redirectUri: string) => Promise<string>;
|
|
5
|
+
storeToken: (token: string) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function parseOAuthError(text: string): {
|
|
8
|
+
isInvalidGrant: boolean;
|
|
9
|
+
statusText: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Completes the flow from the URL the provider redirected back to. Returns the
|
|
13
|
+
* token, or undefined if the redirect carries neither a token, a code, nor an
|
|
14
|
+
* error. Throws on any OAuth error.
|
|
15
|
+
*/
|
|
16
|
+
export declare function finishOAuthRedirect(redirectUri: string, params: OAuthWindowParams): Promise<string | undefined>;
|
|
17
|
+
/**
|
|
18
|
+
* The redirect URI a message event carries, if it is this account's popup
|
|
19
|
+
* reporting back, and undefined otherwise. Synchronous, so a caller can tell a
|
|
20
|
+
* relevant message from an unrelated one before finishing it — finishing can
|
|
21
|
+
* take a round trip of its own.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getOAuthRedirectUri(event: MessageEvent, internetAccountId: string): string | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Parses an OAuth redirect message event. Returns the token if the event
|
|
26
|
+
* completes the flow, undefined if the event isn't this account's redirect.
|
|
27
|
+
* Throws on any OAuth error.
|
|
28
|
+
*/
|
|
29
|
+
export declare function finishOAuthWindow(event: MessageEvent, params: OAuthWindowParams): Promise<string | undefined>;
|
|
30
|
+
/**
|
|
31
|
+
* Returns a promise that resolves to the token when the OAuth popup posts its
|
|
32
|
+
* redirect back. The listener is self-contained and cleaned up when the promise
|
|
33
|
+
* settles.
|
|
34
|
+
*
|
|
35
|
+
* Given a `watch`, closing the popup without completing the flow rejects rather
|
|
36
|
+
* than leaving the promise pending — and pending is not a benign state here,
|
|
37
|
+
* because `getToken` caches this promise and hands it to every later request:
|
|
38
|
+
* one dismissed login window left the account dead for the rest of the session,
|
|
39
|
+
* showing nothing. Same failure the blocked-popup check in `getTokenViaAuthFlow`
|
|
40
|
+
* covers, reached the other way.
|
|
41
|
+
*/
|
|
42
|
+
export declare function waitForOAuthMessage(finish: (event: MessageEvent) => Promise<string | undefined>, watch?: {
|
|
43
|
+
popup: Pick<Window, 'closed'>;
|
|
44
|
+
/** whether an event is this popup's redirect, tested synchronously */
|
|
45
|
+
isOwnMessage: (event: MessageEvent) => boolean;
|
|
46
|
+
}): Promise<string>;
|
package/esm/OAuthModel/util.js
CHANGED
|
@@ -1,23 +1,166 @@
|
|
|
1
|
-
|
|
2
|
-
export async function generateChallenge(val) {
|
|
3
|
-
return sha256Base64Url(val);
|
|
4
|
-
}
|
|
5
|
-
export function processError(text, invalidErrorCb) {
|
|
1
|
+
export function parseOAuthError(text) {
|
|
6
2
|
try {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
const { error, error_description } = JSON.parse(text);
|
|
4
|
+
return {
|
|
5
|
+
isInvalidGrant: error === 'invalid_grant',
|
|
6
|
+
statusText: error_description ?? text,
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return { isInvalidGrant: false, statusText: text };
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Completes the flow from the URL the provider redirected back to. Returns the
|
|
15
|
+
* token, or undefined if the redirect carries neither a token, a code, nor an
|
|
16
|
+
* error. Throws on any OAuth error.
|
|
17
|
+
*/
|
|
18
|
+
export async function finishOAuthRedirect(redirectUri, params) {
|
|
19
|
+
const redirectUrl = new URL(redirectUri);
|
|
20
|
+
// Both halves: the code flow answers in the query string, the implicit flow
|
|
21
|
+
// in the fragment. Read as a union rather than by rewriting the '#' into a
|
|
22
|
+
// '?', which produced a second '?' — and so one unsplittable parameter — for
|
|
23
|
+
// any redirect that carried both.
|
|
24
|
+
const urlParams = new URLSearchParams(redirectUrl.search);
|
|
25
|
+
for (const [key, value] of new URLSearchParams(redirectUrl.hash.replace('#', ''))) {
|
|
26
|
+
if (!urlParams.has(key)) {
|
|
27
|
+
urlParams.set(key, value);
|
|
10
28
|
}
|
|
11
|
-
return obj?.error_description ?? text;
|
|
12
29
|
}
|
|
13
|
-
|
|
30
|
+
if (params.expectedState && urlParams.get('state') !== params.expectedState) {
|
|
31
|
+
throw new Error('OAuth state mismatch — possible CSRF attack');
|
|
32
|
+
}
|
|
33
|
+
const accessToken = urlParams.get('access_token');
|
|
34
|
+
if (accessToken) {
|
|
35
|
+
params.storeToken(accessToken);
|
|
36
|
+
return accessToken;
|
|
37
|
+
}
|
|
38
|
+
const code = urlParams.get('code');
|
|
39
|
+
if (code) {
|
|
40
|
+
const token = await params.exchangeAuthorizationCode(code, redirectUrl.origin + redirectUrl.pathname);
|
|
41
|
+
params.storeToken(token);
|
|
42
|
+
return token;
|
|
14
43
|
}
|
|
15
|
-
|
|
44
|
+
const error = urlParams.get('error');
|
|
45
|
+
if (error === 'access_denied') {
|
|
46
|
+
throw new Error('OAuth flow was cancelled');
|
|
47
|
+
}
|
|
48
|
+
if (error) {
|
|
49
|
+
throw new Error(`OAuth flow error: ${error}`);
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
16
52
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
53
|
+
/**
|
|
54
|
+
* The redirect URI a message event carries, if it is this account's popup
|
|
55
|
+
* reporting back, and undefined otherwise. Synchronous, so a caller can tell a
|
|
56
|
+
* relevant message from an unrelated one before finishing it — finishing can
|
|
57
|
+
* take a round trip of its own.
|
|
58
|
+
*/
|
|
59
|
+
export function getOAuthRedirectUri(event, internetAccountId) {
|
|
60
|
+
// The popup redirects back to our own origin and posts from there
|
|
61
|
+
// (`initAuthWindow`), so anything else is another page talking to us — and
|
|
62
|
+
// its `redirectUri` would inject that page's access token as ours.
|
|
63
|
+
if (event.origin !== window.location.origin) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
// The window receives messages from anything holding a handle to it, of any
|
|
67
|
+
// shape. Reading through an unchecked `event.data` throws a TypeError, and
|
|
68
|
+
// the listener in waitForOAuthMessage turns a throw into a *rejected* auth
|
|
69
|
+
// flow — so an unrelated `postMessage(null)` used to fail the login.
|
|
70
|
+
const data = event.data;
|
|
71
|
+
if (typeof data !== 'object' ||
|
|
72
|
+
data === null ||
|
|
73
|
+
!('name' in data) ||
|
|
74
|
+
!('redirectUri' in data)) {
|
|
75
|
+
return undefined;
|
|
20
76
|
}
|
|
21
|
-
|
|
77
|
+
const { name, redirectUri } = data;
|
|
78
|
+
return name === `JBrowseAuthWindow-${internetAccountId}` &&
|
|
79
|
+
typeof redirectUri === 'string'
|
|
80
|
+
? redirectUri
|
|
81
|
+
: undefined;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Parses an OAuth redirect message event. Returns the token if the event
|
|
85
|
+
* completes the flow, undefined if the event isn't this account's redirect.
|
|
86
|
+
* Throws on any OAuth error.
|
|
87
|
+
*/
|
|
88
|
+
export async function finishOAuthWindow(event, params) {
|
|
89
|
+
const redirectUri = getOAuthRedirectUri(event, params.internetAccountId);
|
|
90
|
+
return redirectUri === undefined
|
|
91
|
+
? undefined
|
|
92
|
+
: finishOAuthRedirect(redirectUri, params);
|
|
93
|
+
}
|
|
94
|
+
// How often the popup is checked for having been closed, and how many
|
|
95
|
+
// consecutive closed readings it takes to call the flow cancelled.
|
|
96
|
+
const POLL_INTERVAL_MS = 500;
|
|
97
|
+
const CLOSED_POLLS_TO_CANCEL = 2;
|
|
98
|
+
/**
|
|
99
|
+
* Returns a promise that resolves to the token when the OAuth popup posts its
|
|
100
|
+
* redirect back. The listener is self-contained and cleaned up when the promise
|
|
101
|
+
* settles.
|
|
102
|
+
*
|
|
103
|
+
* Given a `watch`, closing the popup without completing the flow rejects rather
|
|
104
|
+
* than leaving the promise pending — and pending is not a benign state here,
|
|
105
|
+
* because `getToken` caches this promise and hands it to every later request:
|
|
106
|
+
* one dismissed login window left the account dead for the rest of the session,
|
|
107
|
+
* showing nothing. Same failure the blocked-popup check in `getTokenViaAuthFlow`
|
|
108
|
+
* covers, reached the other way.
|
|
109
|
+
*/
|
|
110
|
+
export function waitForOAuthMessage(finish, watch) {
|
|
111
|
+
return new Promise((resolve, reject) => {
|
|
112
|
+
let timer;
|
|
113
|
+
let finishing = false;
|
|
114
|
+
let closedPolls = 0;
|
|
115
|
+
const settle = (act) => {
|
|
116
|
+
window.removeEventListener('message', listener);
|
|
117
|
+
clearInterval(timer);
|
|
118
|
+
act();
|
|
119
|
+
};
|
|
120
|
+
const listener = async (event) => {
|
|
121
|
+
const own = watch?.isOwnMessage(event) ?? false;
|
|
122
|
+
finishing ||= own;
|
|
123
|
+
try {
|
|
124
|
+
const token = await finish(event);
|
|
125
|
+
if (token !== undefined) {
|
|
126
|
+
settle(() => {
|
|
127
|
+
resolve(token);
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
settle(() => {
|
|
134
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
135
|
+
});
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// a redirect of ours that completed nothing — a provider that came back
|
|
139
|
+
// with neither token, code nor error. Hand the popup back to the
|
|
140
|
+
// watchdog rather than latching it off for good.
|
|
141
|
+
if (own) {
|
|
142
|
+
finishing = false;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
window.addEventListener('message', listener);
|
|
146
|
+
if (watch) {
|
|
147
|
+
timer = setInterval(() => {
|
|
148
|
+
// A closed popup only means cancellation while no redirect of ours has
|
|
149
|
+
// arrived: the popup posts and *then* closes itself, and the code flow
|
|
150
|
+
// then spends a token exchange finishing. Hence both the `finishing`
|
|
151
|
+
// latch and a poll of grace for the gap between the post and this
|
|
152
|
+
// listener running.
|
|
153
|
+
if (finishing || !watch.popup.closed) {
|
|
154
|
+
closedPolls = 0;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
closedPolls += 1;
|
|
158
|
+
if (closedPolls >= CLOSED_POLLS_TO_CANCEL) {
|
|
159
|
+
settle(() => {
|
|
160
|
+
reject(new Error('OAuth flow was cancelled'));
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}, POLL_INTERVAL_MS);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
22
166
|
}
|
|
23
|
-
export { toBase64Url } from '@jbrowse/core/util';
|