@crossauth/frontend 0.0.2
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/LICENSE +203 -0
- package/README.md +13 -0
- package/dist/index.cjs +3005 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.iife.js +3008 -0
- package/dist/index.js +3005 -0
- package/dist/oauth/autorefresher.d.ts +44 -0
- package/dist/oauth/bffclient.d.ts +208 -0
- package/dist/oauth/client.d.ts +271 -0
- package/dist/oauth/devicecodepoller.d.ts +38 -0
- package/dist/oauth/tokenconsumer.d.ts +10 -0
- package/dist/oauth/tokenprovider.d.ts +39 -0
- package/package.json +34 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { CrossauthError } from '@crossauth/common';
|
|
2
|
+
import { OAuthTokenProvider } from './tokenprovider.ts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Used by {@link OAuthClient} and {@link OAuthBsffClient} to automatically
|
|
6
|
+
* refresh access and ID tokens
|
|
7
|
+
*/
|
|
8
|
+
export declare class OAuthAutoRefresher {
|
|
9
|
+
private autoRefreshUrl;
|
|
10
|
+
protected csrfHeader: string;
|
|
11
|
+
protected headers: {
|
|
12
|
+
[key: string]: string;
|
|
13
|
+
};
|
|
14
|
+
private autoRefreshActive;
|
|
15
|
+
protected mode: "no-cors" | "cors" | "same-origin";
|
|
16
|
+
protected credentials: "include" | "omit" | "same-origin";
|
|
17
|
+
protected tokenProvider: OAuthTokenProvider;
|
|
18
|
+
/**
|
|
19
|
+
* Constructor
|
|
20
|
+
*
|
|
21
|
+
* @param options
|
|
22
|
+
* - `autoRefreshUrl` the URL to call to perform the refresh Default is `/autorefresh`
|
|
23
|
+
* - `csrfHeader` the header to put CSRF tokens into
|
|
24
|
+
* (default `X-CROSSAUTH-CSRF`))
|
|
25
|
+
* - `mode` overrides the default `mode` in fetch calls
|
|
26
|
+
* - `credentials` - overrides the default `credentials` for fetch calls
|
|
27
|
+
* - `headers` - adds headers to fetfh calls
|
|
28
|
+
* - `tokenProvider` - class for fetching tokens and adding them to requests
|
|
29
|
+
*/
|
|
30
|
+
constructor(options: {
|
|
31
|
+
autoRefreshUrl: string;
|
|
32
|
+
csrfHeader?: string;
|
|
33
|
+
credentials?: "include" | "omit" | "same-origin";
|
|
34
|
+
mode?: "no-cors" | "cors" | "same-origin";
|
|
35
|
+
headers?: {
|
|
36
|
+
[key: string]: any;
|
|
37
|
+
};
|
|
38
|
+
tokenProvider: OAuthTokenProvider;
|
|
39
|
+
});
|
|
40
|
+
startAutoRefresh(tokensToFetch?: ("access" | "id")[], errorFn?: (msg: string, e?: CrossauthError) => void): Promise<void>;
|
|
41
|
+
stopAutoRefresh(): void;
|
|
42
|
+
private scheduleAutoRefresh;
|
|
43
|
+
private autoRefresh;
|
|
44
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { CrossauthError } from '@crossauth/common';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A browser-side OAuth client designed with work with the
|
|
5
|
+
* backend-for-frontend (BFF) mode of the backend OAuth client.
|
|
6
|
+
*
|
|
7
|
+
* See {@link @crossauth/fastify!FastifyOAuthClient}.
|
|
8
|
+
*/
|
|
9
|
+
export declare class OAuthBffClient {
|
|
10
|
+
private bffPrefix;
|
|
11
|
+
private csrfHeader;
|
|
12
|
+
private enableCsrfProtection;
|
|
13
|
+
private headers;
|
|
14
|
+
private mode;
|
|
15
|
+
private credentials;
|
|
16
|
+
private autoRefresher;
|
|
17
|
+
private deviceCodePoller;
|
|
18
|
+
private getCsrfTokenUrl;
|
|
19
|
+
private autoRefreshUrl;
|
|
20
|
+
private tokensUrl;
|
|
21
|
+
/**
|
|
22
|
+
* Constructor
|
|
23
|
+
*
|
|
24
|
+
* @param options
|
|
25
|
+
* - `bffPrefix` the base url for BFF calls to the OAuth client
|
|
26
|
+
* (eg `bff`, which is the default)
|
|
27
|
+
* - `csrfHeader` the header to put CSRF tokens into
|
|
28
|
+
* (default `X-CROSSAUTH-CSRF`))
|
|
29
|
+
* - `getCsrfTokenUrl` URL to use to fetch CSRF tokens. Default is
|
|
30
|
+
* `/api/getcsrftoken`
|
|
31
|
+
* - `autoRefreshUrl` URL to use to refresh tokens. Default is
|
|
32
|
+
* `/api/refreshtokens`
|
|
33
|
+
* - `tokensUrl` URL to use to fetch token payloads. Default is
|
|
34
|
+
* `/tokens`
|
|
35
|
+
* - `deviceCodePollUrl` URL for polling for device code authorization.
|
|
36
|
+
* Default is `/devicecodepoll`
|
|
37
|
+
* - `mode` overrides the default `mode` in fetch calls
|
|
38
|
+
* - `credentials` - overrides the default `credentials` for fetch calls
|
|
39
|
+
* - `headers` - adds headers to fetfh calls
|
|
40
|
+
*/
|
|
41
|
+
constructor(options?: {
|
|
42
|
+
bffPrefix?: string;
|
|
43
|
+
csrfHeader?: string;
|
|
44
|
+
credentials?: "include" | "omit" | "same-origin";
|
|
45
|
+
mode?: "no-cors" | "cors" | "same-origin";
|
|
46
|
+
headers?: {
|
|
47
|
+
[key: string]: any;
|
|
48
|
+
};
|
|
49
|
+
enableCsrfProtection?: boolean;
|
|
50
|
+
getCsrfTokenUrl?: string;
|
|
51
|
+
autoRefreshUrl?: string;
|
|
52
|
+
tokensUrl?: string;
|
|
53
|
+
deviceCodePollUrl?: string;
|
|
54
|
+
});
|
|
55
|
+
/**
|
|
56
|
+
* Gets a CSRF token from the server
|
|
57
|
+
* @returns the CSRF token that can be included in
|
|
58
|
+
* the `X-CROSSAUTH-CSRF` header
|
|
59
|
+
*/
|
|
60
|
+
getCsrfToken(): Promise<string | undefined>;
|
|
61
|
+
/**
|
|
62
|
+
* Fetches the ID token from the client.
|
|
63
|
+
*
|
|
64
|
+
* This only returns something if the ID token was returned to the BFF
|
|
65
|
+
* client in a previous OAuth call. Otherwise it returns an empty JSON.
|
|
66
|
+
*
|
|
67
|
+
* @param crfToken the CSRF token. If emtpy, one will be fetched before
|
|
68
|
+
* making the request
|
|
69
|
+
* @returns the ID token payload or an empty object if there isn't one
|
|
70
|
+
*/
|
|
71
|
+
getIdToken(csrfToken?: string): Promise<{
|
|
72
|
+
[key: string]: any;
|
|
73
|
+
} | null>;
|
|
74
|
+
/**
|
|
75
|
+
* Returns whether or not there is an ID token stored in the BFF server
|
|
76
|
+
* for this client.
|
|
77
|
+
*
|
|
78
|
+
* @param crfToken the CSRF token. If emtpy, one will be fetched before
|
|
79
|
+
* making the request
|
|
80
|
+
* @returns true or false
|
|
81
|
+
*/
|
|
82
|
+
haveIdToken(csrfToken?: string): Promise<boolean>;
|
|
83
|
+
/**
|
|
84
|
+
* Fetches the access token from the client.
|
|
85
|
+
*
|
|
86
|
+
* This only returns something if the access token was returned to the BFF
|
|
87
|
+
* client in a previous OAuth call. Otherwise it returns an empty JSON.
|
|
88
|
+
*
|
|
89
|
+
* @param crfToken the CSRF token. If emtpy, one will be fetched before
|
|
90
|
+
* making the request
|
|
91
|
+
* @param headers any additional headers to add (will be added to
|
|
92
|
+
* the ones given with {@link OAuthBffClient.addHeader} )
|
|
93
|
+
* @returns the access token payload or an empty object if there isn't one
|
|
94
|
+
*/
|
|
95
|
+
getAccessToken(csrfToken?: string): Promise<{
|
|
96
|
+
[key: string]: any;
|
|
97
|
+
} | null>;
|
|
98
|
+
/**
|
|
99
|
+
* Returns whether or not there is an access token stored in the BFF server
|
|
100
|
+
* for this client.
|
|
101
|
+
*
|
|
102
|
+
* @param crfToken the CSRF token. If emtpy, one will be fetched before
|
|
103
|
+
* making the request
|
|
104
|
+
* @returns true or false
|
|
105
|
+
*/
|
|
106
|
+
haveAccessToken(csrfToken?: string): Promise<boolean>;
|
|
107
|
+
/**
|
|
108
|
+
* Fetches the refresh token from the client.
|
|
109
|
+
*
|
|
110
|
+
* This only returns something if the refresh token was returned to the BFF
|
|
111
|
+
* client in a previous OAuth call. Otherwise it returns an empty JSON.
|
|
112
|
+
*
|
|
113
|
+
* @param crfToken the CSRF token. If emtpy, one will be fetched before
|
|
114
|
+
* making the request
|
|
115
|
+
* @returns the refresh token payload or an empty object if there isn't one
|
|
116
|
+
*/
|
|
117
|
+
getRefreshToken(csrfToken?: string): Promise<{
|
|
118
|
+
[key: string]: any;
|
|
119
|
+
} | null>;
|
|
120
|
+
/**
|
|
121
|
+
* Returns whether or not there is a refresh token stored in the BFF server
|
|
122
|
+
* for this client.
|
|
123
|
+
*
|
|
124
|
+
* @param crfToken the CSRF token. If emtpy, one will be fetched before
|
|
125
|
+
* making the request
|
|
126
|
+
* @returns true or false
|
|
127
|
+
*/
|
|
128
|
+
haveRefreshToken(csrfToken?: string): Promise<boolean>;
|
|
129
|
+
/**
|
|
130
|
+
* Calls an API endpoint via the BFF server
|
|
131
|
+
* @param method the HTTP method
|
|
132
|
+
* @param endpoint the endpoint to call, relative to `bffPrefix`
|
|
133
|
+
* @param body : the body to pass to the call
|
|
134
|
+
* @param csrfToken : the CSRF token
|
|
135
|
+
* @returns the HTTP status code and the body or null
|
|
136
|
+
*/
|
|
137
|
+
api(method: "GET" | "POST" | "PUT" | "PATCH" | "OPTIONS" | "HEAD" | "DELETE", endpoint: string, body?: {
|
|
138
|
+
[key: string]: any;
|
|
139
|
+
}, csrfToken?: string): Promise<{
|
|
140
|
+
status: number;
|
|
141
|
+
body: {
|
|
142
|
+
[key: string]: any;
|
|
143
|
+
} | null;
|
|
144
|
+
}>;
|
|
145
|
+
/**
|
|
146
|
+
* Return all tokens that the client has been enabled to return.
|
|
147
|
+
*
|
|
148
|
+
* @param csrfToken the CSRF token if one is needed
|
|
149
|
+
* @returns an object with the following (whichever are enabled at the client)
|
|
150
|
+
* - `id_token`
|
|
151
|
+
* - `access_token`
|
|
152
|
+
* - `refresh_token`
|
|
153
|
+
* - `have_id_token`
|
|
154
|
+
* - `have_access_token`
|
|
155
|
+
* - `have_refresh_token`
|
|
156
|
+
*/
|
|
157
|
+
getTokens(csrfToken?: string): Promise<{
|
|
158
|
+
[key: string]: any;
|
|
159
|
+
} | null>;
|
|
160
|
+
/**
|
|
161
|
+
* Turns auto refresh of tokens on
|
|
162
|
+
* @param tokensToFetch which tokens to fetch
|
|
163
|
+
* @param errorFn what to call in case of error
|
|
164
|
+
*/
|
|
165
|
+
startAutoRefresh(tokensToFetch?: ("access" | "id")[], errorFn?: (msg: string, e?: CrossauthError) => void): Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* Turns auto refresh of tokens off
|
|
168
|
+
*/
|
|
169
|
+
stopAutoRefresh(): void;
|
|
170
|
+
/**
|
|
171
|
+
* Turns polling for a device code
|
|
172
|
+
* @param tokensToFetch which tokens to fetch
|
|
173
|
+
* @param errorFn what to call in case of error
|
|
174
|
+
*/
|
|
175
|
+
startDeviceCodePolling(deviceCode: string, pollResultFn: (status: ("complete" | "completeAndRedirect" | "authorization_pending" | "expired_token" | "error"), error?: string, location?: string) => void, interval?: number): Promise<void>;
|
|
176
|
+
/**
|
|
177
|
+
* Turns off polling for a device code
|
|
178
|
+
*/
|
|
179
|
+
stopDeviceCodePolling(): void;
|
|
180
|
+
/**
|
|
181
|
+
* Fetches the expiry times for each token.
|
|
182
|
+
* @param crfToken the CSRF token. If emtpy
|
|
183
|
+
* , one will be fetched before
|
|
184
|
+
* making the request
|
|
185
|
+
* @returns for each token, either the expiry, `null` if it does not
|
|
186
|
+
* expire, or `undefined` if the token does not exist
|
|
187
|
+
*/
|
|
188
|
+
getTokenExpiries(tokensToFetch: ("access" | "id" | "refresh")[], csrfToken?: string): Promise<{
|
|
189
|
+
id: number | null | undefined;
|
|
190
|
+
access: number | null | undefined;
|
|
191
|
+
refresh: number | null | undefined;
|
|
192
|
+
}>;
|
|
193
|
+
/**
|
|
194
|
+
* Makes a fetch, adding in the requested token
|
|
195
|
+
* @param url the URL to fetch
|
|
196
|
+
* @param params parameters to add to the fetch
|
|
197
|
+
* @param token which token to add
|
|
198
|
+
* @returns parsed JSON response
|
|
199
|
+
*/
|
|
200
|
+
jsonFetchWithToken(url: string, params: {
|
|
201
|
+
[key: string]: any;
|
|
202
|
+
}, _token: "access" | "refresh"): Promise<Response>;
|
|
203
|
+
receiveTokens(_tokens: {
|
|
204
|
+
access_token?: string | null;
|
|
205
|
+
id_token?: string | null;
|
|
206
|
+
refresh_token?: string | null;
|
|
207
|
+
}): Promise<void>;
|
|
208
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { OAuthClientBase, CrossauthError, OAuthTokenResponse, OAuthDeviceAuthorizationResponse } from '@crossauth/common';
|
|
2
|
+
import { OAuthTokenConsumer } from './tokenconsumer';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This is the type for a function that is called when an OAuth endpoint
|
|
6
|
+
* returns the `error` field.
|
|
7
|
+
*/
|
|
8
|
+
export type ErrorFn = (client: OAuthClient, error: string, errorDescription?: string) => Promise<any>;
|
|
9
|
+
/**
|
|
10
|
+
* If this URL was called with an OAuth authorize response, the `token`
|
|
11
|
+
* endpoint will be called and, if successful, passed to this function.
|
|
12
|
+
*/
|
|
13
|
+
export type ReceiveTokenFn = (client: OAuthClient, response: OAuthTokenResponse) => Promise<any>;
|
|
14
|
+
export type TokenResponseType = "memory" | "localStorage" | "sessionStorage";
|
|
15
|
+
export declare class OAuthClient extends OAuthClientBase {
|
|
16
|
+
#private;
|
|
17
|
+
private resServerBaseUrl;
|
|
18
|
+
private resServerHeaders;
|
|
19
|
+
private resServerMode;
|
|
20
|
+
private resServerCredentials;
|
|
21
|
+
private accessTokenResponseType?;
|
|
22
|
+
private refreshTokenResponseType?;
|
|
23
|
+
private idTokenResponseType?;
|
|
24
|
+
private accessTokenName;
|
|
25
|
+
private refreshTokenName;
|
|
26
|
+
private idTokenName;
|
|
27
|
+
get idTokenPayload(): {
|
|
28
|
+
[key: string]: any;
|
|
29
|
+
} | undefined;
|
|
30
|
+
private autoRefresher;
|
|
31
|
+
private deviceCodePoller;
|
|
32
|
+
private deviceAuthorizationUrl;
|
|
33
|
+
/**
|
|
34
|
+
* Constructor
|
|
35
|
+
*
|
|
36
|
+
* @param options
|
|
37
|
+
* - `authServerBaseUrl` the base url the authorization server.
|
|
38
|
+
* For example, the authorize endpoint would be
|
|
39
|
+
* `authServerBaseUrl + "authorize"`.
|
|
40
|
+
* Required: no default
|
|
41
|
+
* - `resServerBaseUrl` the base url the resource server.
|
|
42
|
+
* For example, Relative URLs to the resource server are relative
|
|
43
|
+
* to this. If you always give absolute URLs, this is optional.
|
|
44
|
+
* If you don't give it and you do make relative URLs, it will be
|
|
45
|
+
* relative to the page you are on. Default: empty string.
|
|
46
|
+
* - `redirect_uri` a URL on the site serving this app which the
|
|
47
|
+
* authorization server will redirect to with an authorization
|
|
48
|
+
* code. See description in class documentation.
|
|
49
|
+
* This is not required if you are not using OAuth flows
|
|
50
|
+
* which require a redirect URI (eg the password flow).
|
|
51
|
+
* - `accessTokenResponseType` where to store access tokens. See
|
|
52
|
+
* class documentation. Default `return`.
|
|
53
|
+
* - `refreshTokenResponseType` where to store refresh tokens. See
|
|
54
|
+
* class documentation. Default `return`.
|
|
55
|
+
* - `idTokenResponseType` where to store id tokens. See
|
|
56
|
+
* class documentation. Default `return`.
|
|
57
|
+
* - `accessTokenName` name for access token in local or session
|
|
58
|
+
* storage, depending on `accessTokenResponseType`
|
|
59
|
+
* - `refreshTokenName` name for refresh token in local or session
|
|
60
|
+
* storage, depending on `refreshTokenResponseType`
|
|
61
|
+
* - `idTokenName` name for id token in local or session
|
|
62
|
+
* storage, depending on `idTokenResponseType`
|
|
63
|
+
* - `mresServerMode` overrides the default `mode` in fetch calls
|
|
64
|
+
* - `resServerCredentials` - overrides the default `credentials` for fetch calls
|
|
65
|
+
* - `resServerHeaders` - adds headers to fetfh calls
|
|
66
|
+
* - `autoRefresh` - if set and tokens are present in local or session storage,
|
|
67
|
+
* automatically turn on auto refresh
|
|
68
|
+
* - `deviceAuthorization` URL, relative to the authorization server base,
|
|
69
|
+
* for starting the device code flow. Default `device_authorization`
|
|
70
|
+
* Default is `/devicecodepoll`
|
|
71
|
+
* For other options see {@link @crossauth/common/OAuthClientBase}.
|
|
72
|
+
*/
|
|
73
|
+
constructor(options: {
|
|
74
|
+
authServerBaseUrl: string;
|
|
75
|
+
stateLength?: number;
|
|
76
|
+
verifierLength?: number;
|
|
77
|
+
client_id: string;
|
|
78
|
+
client_secret?: string;
|
|
79
|
+
redirect_uri?: string;
|
|
80
|
+
codeChallengeMethod?: "plain" | "S256";
|
|
81
|
+
tokenConsumer: OAuthTokenConsumer;
|
|
82
|
+
resServerBaseUrl?: string;
|
|
83
|
+
accessTokenResponseType?: TokenResponseType;
|
|
84
|
+
refreshTokenResponseType?: TokenResponseType;
|
|
85
|
+
idTokenResponseType?: TokenResponseType;
|
|
86
|
+
accessTokenName?: string;
|
|
87
|
+
refreshTokenName?: string;
|
|
88
|
+
idTokenName?: string;
|
|
89
|
+
resServerCredentials?: "include" | "omit" | "same-origin";
|
|
90
|
+
resServerMode?: "no-cors" | "cors" | "same-origin";
|
|
91
|
+
resServerHeaders?: {
|
|
92
|
+
[key: string]: any;
|
|
93
|
+
};
|
|
94
|
+
autoRefresh?: ("access" | "id")[];
|
|
95
|
+
deviceAuthorizationUrl?: string;
|
|
96
|
+
});
|
|
97
|
+
/**
|
|
98
|
+
* Processes the query parameters for a Redirect URI request if they
|
|
99
|
+
* exist in the URL.
|
|
100
|
+
*
|
|
101
|
+
* Call this on page load to see if it was called as redirect URI.
|
|
102
|
+
*
|
|
103
|
+
* If this URL doesn't match the redirect URI passed in the constructor,
|
|
104
|
+
* or this URL was not called with OAuth Redirect URI query parameters,
|
|
105
|
+
* undefined is returned.
|
|
106
|
+
*
|
|
107
|
+
* If this URL contains the error query parameter, `errorFn` is called.
|
|
108
|
+
* It is also called if the state does not match.
|
|
109
|
+
*
|
|
110
|
+
* If an authorization code was in the query parameters, the token
|
|
111
|
+
* endpoint is called. Depending on whether that returned an error,
|
|
112
|
+
* either `receiveTokenFn` or `errorFn` will be called.
|
|
113
|
+
*
|
|
114
|
+
* @param receiveTokenFn if defined, called if a token is returned.
|
|
115
|
+
*
|
|
116
|
+
* @param errorFn if defined, called if any OAuth endpoint returned `error`,
|
|
117
|
+
* or if the `state` was not correct.
|
|
118
|
+
*
|
|
119
|
+
* @returns the result of `receiveTokenFn`, `errorFn` or `undefined`. If
|
|
120
|
+
* `receiveTokenFn`/`errorFn` is not defined, rather than calling
|
|
121
|
+
* it, this function just returns the OAuth response.
|
|
122
|
+
*
|
|
123
|
+
*/
|
|
124
|
+
handleRedirectUri(): Promise<any | undefined>;
|
|
125
|
+
/**
|
|
126
|
+
* Turns auto refresh of tokens on
|
|
127
|
+
* @param tokensToFetch which tokens to fetch
|
|
128
|
+
* @param errorFn what to call in case of error
|
|
129
|
+
*/
|
|
130
|
+
startAutoRefresh(tokensToFetch?: ("access" | "id")[], errorFn?: (msg: string, e?: CrossauthError) => void): Promise<void>;
|
|
131
|
+
/**
|
|
132
|
+
* Turns auto refresh of tokens off
|
|
133
|
+
*/
|
|
134
|
+
stopAutoRefresh(): void;
|
|
135
|
+
/**
|
|
136
|
+
* Turns polling for a device code
|
|
137
|
+
* @param tokensToFetch which tokens to fetch
|
|
138
|
+
* @param errorFn what to call in case of error
|
|
139
|
+
*/
|
|
140
|
+
startDeviceCodePolling(deviceCode: string, pollResultFn: (status: ("complete" | "completeAndRedirect" | "authorization_pending" | "expired_token" | "error"), error?: string, location?: string) => void, interval?: number): Promise<void>;
|
|
141
|
+
/**
|
|
142
|
+
* Turns off polling for a device code
|
|
143
|
+
*/
|
|
144
|
+
stopDeviceCodePolling(): void;
|
|
145
|
+
/**
|
|
146
|
+
* Return the ID token payload
|
|
147
|
+
*
|
|
148
|
+
* This does the same thign as {@link idTokenPayload}. We have it here
|
|
149
|
+
* as well for consistency with {@link OAuthBffClient}.
|
|
150
|
+
*
|
|
151
|
+
* @returns the payload as an object
|
|
152
|
+
*/
|
|
153
|
+
getIdToken(): {
|
|
154
|
+
[key: string]: any;
|
|
155
|
+
} | undefined;
|
|
156
|
+
/**
|
|
157
|
+
* Produce a random Base64-url-encoded string, whose length before
|
|
158
|
+
* base64-url-encoding is the given length,
|
|
159
|
+
* @param length the length of the random array before base64-url-encoding.
|
|
160
|
+
* @returns the random value as a Base64-url-encoded srting
|
|
161
|
+
*/
|
|
162
|
+
protected randomValue(length: number): string;
|
|
163
|
+
/**
|
|
164
|
+
* SHA256 and Base64-url-encodes the given test
|
|
165
|
+
* @param plaintext the text to encode
|
|
166
|
+
* @returns the SHA256 hash, Base64-url-encode
|
|
167
|
+
*/
|
|
168
|
+
protected sha256(plaintext: string): Promise<string>;
|
|
169
|
+
/**
|
|
170
|
+
* Calls an API endpoint on the resource server
|
|
171
|
+
* @param method the HTTP method
|
|
172
|
+
* @param endpoint the endpoint to call, relative to `resServerBaseUrl`
|
|
173
|
+
* @param body : the body to pass to the call
|
|
174
|
+
* @returns the HTTP status code and the body or null
|
|
175
|
+
*/
|
|
176
|
+
api(method: "GET" | "POST" | "PUT" | "PATCH" | "OPTIONS" | "HEAD" | "DELETE", endpoint: string, body?: {
|
|
177
|
+
[key: string]: any;
|
|
178
|
+
}): Promise<{
|
|
179
|
+
status: number;
|
|
180
|
+
body: {
|
|
181
|
+
[key: string]: any;
|
|
182
|
+
} | null;
|
|
183
|
+
}>;
|
|
184
|
+
/**
|
|
185
|
+
* Fetches the expiry times for each token.
|
|
186
|
+
* @param crfToken the CSRF token. If emtpy
|
|
187
|
+
* , one will be fetched before
|
|
188
|
+
* making the request
|
|
189
|
+
* @returns for each token, either the expiry, `null` if it does not
|
|
190
|
+
* expire, or `undefined` if the token does not exist
|
|
191
|
+
*/
|
|
192
|
+
getTokenExpiries(_tokensToFetch: ("access" | "id" | "refresh")[], _csrfToken?: string): Promise<{
|
|
193
|
+
id: number | null | undefined;
|
|
194
|
+
access: number | null | undefined;
|
|
195
|
+
refresh: number | null | undefined;
|
|
196
|
+
}>;
|
|
197
|
+
/**
|
|
198
|
+
* Makes a fetch, adding in the requested token.
|
|
199
|
+
*
|
|
200
|
+
* Also adds client ID and secret if they are defined.
|
|
201
|
+
*
|
|
202
|
+
* @param url the URL to fetch
|
|
203
|
+
* @param params parameters to add to the fetch
|
|
204
|
+
* @param token which token to add
|
|
205
|
+
* @returns parsed JSON response
|
|
206
|
+
*/
|
|
207
|
+
jsonFetchWithToken(url: string, params: {
|
|
208
|
+
[key: string]: any;
|
|
209
|
+
}, token: "access" | "refresh"): Promise<Response>;
|
|
210
|
+
/**
|
|
211
|
+
* Does nothing as CSRF tokens are not needed for this class
|
|
212
|
+
* @returns `undefined`
|
|
213
|
+
*/
|
|
214
|
+
getCsrfToken(): Promise<undefined>;
|
|
215
|
+
receiveTokens(tokens: {
|
|
216
|
+
access_token?: string | null;
|
|
217
|
+
id_token?: string | null;
|
|
218
|
+
refresh_token?: string | null;
|
|
219
|
+
}): Promise<void>;
|
|
220
|
+
/**
|
|
221
|
+
* See {@link @crossuath/common!OAuthClientBase}. Calls the base function
|
|
222
|
+
* then saves the tokens, as per the requested method
|
|
223
|
+
* @param scope
|
|
224
|
+
*/
|
|
225
|
+
clientCredentialsFlow(scope?: string): Promise<OAuthTokenResponse>;
|
|
226
|
+
/**
|
|
227
|
+
* See {@link @crossuath/common!OAuthClientBase}. Calls the base function
|
|
228
|
+
* then saves the tokens, as per the requested method
|
|
229
|
+
* @param scope
|
|
230
|
+
*/
|
|
231
|
+
passwordFlow(username: string, password: string, scope?: string): Promise<OAuthTokenResponse>;
|
|
232
|
+
/**
|
|
233
|
+
* See {@link @crossuath/common!OAuthClientBase}. Calls the base function
|
|
234
|
+
* then saves the tokens, as per the requested method
|
|
235
|
+
* @param scope
|
|
236
|
+
*/
|
|
237
|
+
deviceCodeFlow(scope?: string): Promise<OAuthDeviceAuthorizationResponse>;
|
|
238
|
+
/**
|
|
239
|
+
* See {@link @crossuath/common!OAuthClientBase}. Calls the base function
|
|
240
|
+
* then saves the tokens, as per the requested method
|
|
241
|
+
* @param scope
|
|
242
|
+
*/
|
|
243
|
+
mfaOtpComplete(mfaToken: string, otp: string): Promise<{
|
|
244
|
+
access_token?: string;
|
|
245
|
+
refresh_token?: string;
|
|
246
|
+
id_token?: string;
|
|
247
|
+
expires_in?: number;
|
|
248
|
+
scope?: string;
|
|
249
|
+
token_type?: string;
|
|
250
|
+
error?: string;
|
|
251
|
+
error_description?: string;
|
|
252
|
+
}>;
|
|
253
|
+
/**
|
|
254
|
+
* See {@link @crossuath/common!OAuthClientBase}. Calls the base function
|
|
255
|
+
* then saves the tokens, as per the requested method
|
|
256
|
+
* @param scope
|
|
257
|
+
*/
|
|
258
|
+
mfaOobComplete(mfaToken: string, oobCode: string, bindingCode: string): Promise<OAuthTokenResponse>;
|
|
259
|
+
/**
|
|
260
|
+
* See {@link @crossuath/common!OAuthClientBase}. Calls the base function
|
|
261
|
+
* then saves the tokens, as per the requested method
|
|
262
|
+
* @param scope
|
|
263
|
+
*/
|
|
264
|
+
refreshTokenFlow(refreshToken?: string): Promise<OAuthTokenResponse>;
|
|
265
|
+
/**
|
|
266
|
+
* Executes the authorization code flow
|
|
267
|
+
* @param scope the scope to request
|
|
268
|
+
* @param pkce whether or not to use PKCE.
|
|
269
|
+
*/
|
|
270
|
+
authorizationCodeFlow(scope?: string, pkce?: boolean): Promise<void>;
|
|
271
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { OAuthClientBase } from '@crossauth/common';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Used by {@link OAuthClient} and {@link OAuthBsffClient} to poll for
|
|
5
|
+
* authorization in the device code flow
|
|
6
|
+
*/
|
|
7
|
+
export declare class OAuthDeviceCodePoller {
|
|
8
|
+
private deviceCodePollUrl;
|
|
9
|
+
protected headers: {
|
|
10
|
+
[key: string]: string;
|
|
11
|
+
};
|
|
12
|
+
private pollingActive;
|
|
13
|
+
protected mode: "no-cors" | "cors" | "same-origin";
|
|
14
|
+
protected credentials: "include" | "omit" | "same-origin";
|
|
15
|
+
protected respectRedirect: boolean;
|
|
16
|
+
protected oauthClient?: OAuthClientBase;
|
|
17
|
+
/**
|
|
18
|
+
* Constructor
|
|
19
|
+
*
|
|
20
|
+
* @param options
|
|
21
|
+
* - `deviceCodePollUrl` the URL to call to poll for authorization. Default `/devicecodepoll`
|
|
22
|
+
* - `mode` overrides the default `mode` in fetch calls
|
|
23
|
+
* - `credentials` - overrides the default `credentials` for fetch calls
|
|
24
|
+
* - `headers` - adds headers to fetfh calls
|
|
25
|
+
*/
|
|
26
|
+
constructor(options: {
|
|
27
|
+
deviceCodePollUrl?: string | null;
|
|
28
|
+
credentials?: "include" | "omit" | "same-origin";
|
|
29
|
+
mode?: "no-cors" | "cors" | "same-origin";
|
|
30
|
+
headers?: {
|
|
31
|
+
[key: string]: any;
|
|
32
|
+
};
|
|
33
|
+
oauthClient?: OAuthClientBase;
|
|
34
|
+
});
|
|
35
|
+
startPolling(deviceCode: string, pollResultFn: (status: ("complete" | "completeAndRedirect" | "authorization_pending" | "expired_token" | "error"), error?: string, location?: string) => void, interval?: number): Promise<void>;
|
|
36
|
+
stopPolling(): void;
|
|
37
|
+
private poll;
|
|
38
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { OAuthTokenConsumerBase } from '@crossauth/common';
|
|
2
|
+
|
|
3
|
+
export declare class OAuthTokenConsumer extends OAuthTokenConsumerBase {
|
|
4
|
+
/**
|
|
5
|
+
* SHA256 and Base64-url-encodes the given test
|
|
6
|
+
* @param plaintext the text to encode
|
|
7
|
+
* @returns the SHA256 hash, Base64-url-encode
|
|
8
|
+
*/
|
|
9
|
+
hash(plaintext: string): Promise<string>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* USed by {@link OAuthAutoRefresher} to get tokens from the client
|
|
3
|
+
*/
|
|
4
|
+
export declare abstract class OAuthTokenProvider {
|
|
5
|
+
/**
|
|
6
|
+
* Gets a CSRF token from the server
|
|
7
|
+
* @returns the CSRF token that can be included in
|
|
8
|
+
* the `X-CROSSAUTH-CSRF` header
|
|
9
|
+
*/
|
|
10
|
+
getCsrfToken(): Promise<string | undefined>;
|
|
11
|
+
/**
|
|
12
|
+
* Fetches the expiry times for each token.
|
|
13
|
+
* @param crfToken the CSRF token. If emtpy
|
|
14
|
+
* , one will be fetched before
|
|
15
|
+
* making the request
|
|
16
|
+
* @returns for each token, either the expiry, `null` if it does not
|
|
17
|
+
* expire, or `undefined` if the token does not exist
|
|
18
|
+
*/
|
|
19
|
+
abstract getTokenExpiries(tokensToFetch: ("access" | "id" | "refresh")[], csrfToken?: string): Promise<{
|
|
20
|
+
id: number | null | undefined;
|
|
21
|
+
access: number | null | undefined;
|
|
22
|
+
refresh: number | null | undefined;
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* Makes a fetch, adding in the requested token
|
|
26
|
+
* @param url the URL to fetch
|
|
27
|
+
* @param params parameters to add to the fetch
|
|
28
|
+
* @param token which token to add. Ignored as this client doesn't add tokens
|
|
29
|
+
* @returns parsed JSON response
|
|
30
|
+
*/
|
|
31
|
+
abstract jsonFetchWithToken(url: string, params: {
|
|
32
|
+
[key: string]: any;
|
|
33
|
+
}, token: "access" | "refresh"): Promise<Response>;
|
|
34
|
+
abstract receiveTokens(tokens: {
|
|
35
|
+
access_token?: string | null;
|
|
36
|
+
id_token?: string | null;
|
|
37
|
+
refresh_token?: string | null;
|
|
38
|
+
}): Promise<void>;
|
|
39
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crossauth/frontend",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.0.2",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"typings": "./dist/index.d.ts",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/*"
|
|
12
|
+
],
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@types/node": "^20.10.8",
|
|
15
|
+
"@types/supertest": "^6.0.2",
|
|
16
|
+
"typedoc": "^0.25.4",
|
|
17
|
+
"typescript": "^5.3.3",
|
|
18
|
+
"vite": "^5.0.8",
|
|
19
|
+
"vite-plugin-dts": "^3.6.4",
|
|
20
|
+
"vitest": "^1.1.0"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
|
24
|
+
"@crossauth/common": "^0.0.2"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"dev": "vite",
|
|
28
|
+
"build": "vite build --emptyOutDir=false",
|
|
29
|
+
"preview": "vite preview",
|
|
30
|
+
"test": "echo 'No tests defined'",
|
|
31
|
+
"testonce": "echo 'No tests defined'",
|
|
32
|
+
"doc": "typedoc"
|
|
33
|
+
}
|
|
34
|
+
}
|