@friggframework/core 2.0.0-next.64 → 2.0.0-next.66
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.
|
@@ -2,37 +2,117 @@ const { Requester } = require('./requester');
|
|
|
2
2
|
const { get } = require('../../assertions');
|
|
3
3
|
const { ModuleConstants } = require('../ModuleConstants');
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* OAuth 2.0 Requester - Base class for API modules using OAuth 2.0 authentication.
|
|
7
|
+
*
|
|
8
|
+
* Supports multiple OAuth 2.0 grant types:
|
|
9
|
+
* - `authorization_code` (default): Standard OAuth flow with user consent
|
|
10
|
+
* - `client_credentials`: Server-to-server authentication without user
|
|
11
|
+
* - `password`: Resource Owner Password Credentials grant
|
|
12
|
+
*
|
|
13
|
+
* @extends Requester
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // Authorization Code flow (default)
|
|
17
|
+
* const api = new MyApi({ grant_type: 'authorization_code' });
|
|
18
|
+
* const authUrl = api.getAuthorizationUri();
|
|
19
|
+
* // After user authorizes...
|
|
20
|
+
* await api.getTokenFromCode(code);
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // Client Credentials flow
|
|
24
|
+
* const api = new MyApi({
|
|
25
|
+
* grant_type: 'client_credentials',
|
|
26
|
+
* client_id: process.env.CLIENT_ID,
|
|
27
|
+
* client_secret: process.env.CLIENT_SECRET,
|
|
28
|
+
* audience: 'https://api.example.com',
|
|
29
|
+
* });
|
|
30
|
+
* await api.getTokenFromClientCredentials();
|
|
31
|
+
*/
|
|
5
32
|
class OAuth2Requester extends Requester {
|
|
6
33
|
|
|
7
34
|
static requesterType = ModuleConstants.authType.oauth2;
|
|
8
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Creates an OAuth2Requester instance.
|
|
38
|
+
*
|
|
39
|
+
* @param {Object} params - Configuration parameters
|
|
40
|
+
* @param {string} [params.grant_type='authorization_code'] - OAuth grant type:
|
|
41
|
+
* 'authorization_code', 'client_credentials', or 'password'
|
|
42
|
+
* @param {string} [params.client_id] - OAuth client ID
|
|
43
|
+
* @param {string} [params.client_secret] - OAuth client secret
|
|
44
|
+
* @param {string} [params.redirect_uri] - OAuth redirect URI for authorization code flow
|
|
45
|
+
* @param {string} [params.scope] - OAuth scopes (space-separated)
|
|
46
|
+
* @param {string} [params.authorizationUri] - Authorization endpoint URL
|
|
47
|
+
* @param {string} [params.tokenUri] - Token endpoint URL for exchanging codes/credentials
|
|
48
|
+
* @param {string} [params.baseURL] - Base URL for API requests
|
|
49
|
+
* @param {string} [params.access_token] - Existing access token
|
|
50
|
+
* @param {string} [params.refresh_token] - Existing refresh token
|
|
51
|
+
* @param {Date} [params.accessTokenExpire] - Access token expiration date
|
|
52
|
+
* @param {Date} [params.refreshTokenExpire] - Refresh token expiration date
|
|
53
|
+
* @param {string} [params.audience] - Token audience (for client_credentials)
|
|
54
|
+
* @param {string} [params.username] - Username (for password grant)
|
|
55
|
+
* @param {string} [params.password] - Password (for password grant)
|
|
56
|
+
* @param {string} [params.state] - OAuth state parameter for CSRF protection
|
|
57
|
+
*/
|
|
9
58
|
constructor(params) {
|
|
10
59
|
super(params);
|
|
60
|
+
/** @type {string} Delegate type for token update notifications */
|
|
11
61
|
this.DLGT_TOKEN_UPDATE = 'TOKEN_UPDATE';
|
|
62
|
+
/** @type {string} Delegate type for token deauthorization notifications */
|
|
12
63
|
this.DLGT_TOKEN_DEAUTHORIZED = 'TOKEN_DEAUTHORIZED';
|
|
13
64
|
|
|
14
65
|
this.delegateTypes.push(this.DLGT_TOKEN_UPDATE);
|
|
15
66
|
this.delegateTypes.push(this.DLGT_TOKEN_DEAUTHORIZED);
|
|
16
67
|
|
|
68
|
+
/** @type {string} OAuth grant type */
|
|
17
69
|
this.grant_type = get(params, 'grant_type', 'authorization_code');
|
|
70
|
+
/** @type {string|null} OAuth client ID */
|
|
18
71
|
this.client_id = get(params, 'client_id', null);
|
|
72
|
+
/** @type {string|null} OAuth client secret */
|
|
19
73
|
this.client_secret = get(params, 'client_secret', null);
|
|
74
|
+
/** @type {string|null} OAuth redirect URI */
|
|
20
75
|
this.redirect_uri = get(params, 'redirect_uri', null);
|
|
76
|
+
/** @type {string|null} OAuth scopes */
|
|
21
77
|
this.scope = get(params, 'scope', null);
|
|
78
|
+
/** @type {string|null} Authorization endpoint URL */
|
|
22
79
|
this.authorizationUri = get(params, 'authorizationUri', null);
|
|
80
|
+
/** @type {string|null} Token endpoint URL */
|
|
81
|
+
this.tokenUri = get(params, 'tokenUri', null);
|
|
82
|
+
/** @type {string|null} Base URL for API requests */
|
|
23
83
|
this.baseURL = get(params, 'baseURL', null);
|
|
84
|
+
/** @type {string|null} Current access token */
|
|
24
85
|
this.access_token = get(params, 'access_token', null);
|
|
86
|
+
/** @type {string|null} Current refresh token */
|
|
25
87
|
this.refresh_token = get(params, 'refresh_token', null);
|
|
88
|
+
/** @type {Date|null} Access token expiration */
|
|
26
89
|
this.accessTokenExpire = get(params, 'accessTokenExpire', null);
|
|
90
|
+
/** @type {Date|null} Refresh token expiration */
|
|
27
91
|
this.refreshTokenExpire = get(params, 'refreshTokenExpire', null);
|
|
92
|
+
/** @type {string|null} Token audience */
|
|
28
93
|
this.audience = get(params, 'audience', null);
|
|
94
|
+
/** @type {string|null} Username for password grant */
|
|
29
95
|
this.username = get(params, 'username', null);
|
|
96
|
+
/** @type {string|null} Password for password grant */
|
|
30
97
|
this.password = get(params, 'password', null);
|
|
98
|
+
/** @type {string|null} OAuth state for CSRF protection */
|
|
31
99
|
this.state = get(params, 'state', null);
|
|
32
100
|
|
|
101
|
+
/** @type {boolean} Whether this requester supports token refresh */
|
|
33
102
|
this.isRefreshable = true;
|
|
34
103
|
}
|
|
35
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Sets OAuth tokens and calculates expiration times.
|
|
107
|
+
* Notifies delegates of token update via DLGT_TOKEN_UPDATE.
|
|
108
|
+
*
|
|
109
|
+
* @param {Object} params - Token response from OAuth server
|
|
110
|
+
* @param {string} params.access_token - The access token
|
|
111
|
+
* @param {string} [params.refresh_token] - The refresh token (if provided)
|
|
112
|
+
* @param {number} [params.expires_in] - Access token lifetime in seconds
|
|
113
|
+
* @param {number} [params.x_refresh_token_expires_in] - Refresh token lifetime in seconds
|
|
114
|
+
* @returns {Promise<void>}
|
|
115
|
+
*/
|
|
36
116
|
async setTokens(params) {
|
|
37
117
|
this.access_token = get(params, 'access_token');
|
|
38
118
|
this.refresh_token = get(params, 'refresh_token', null);
|
|
@@ -49,10 +129,20 @@ class OAuth2Requester extends Requester {
|
|
|
49
129
|
await this.notify(this.DLGT_TOKEN_UPDATE);
|
|
50
130
|
}
|
|
51
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Gets the OAuth authorization URL for initiating the authorization code flow.
|
|
134
|
+
*
|
|
135
|
+
* @returns {string|null} The authorization URL
|
|
136
|
+
*/
|
|
52
137
|
getAuthorizationUri() {
|
|
53
138
|
return this.authorizationUri;
|
|
54
139
|
}
|
|
55
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Returns authorization requirements for this OAuth flow.
|
|
143
|
+
*
|
|
144
|
+
* @returns {{url: string|null, type: string}} Authorization requirements
|
|
145
|
+
*/
|
|
56
146
|
getAuthorizationRequirements() {
|
|
57
147
|
return {
|
|
58
148
|
url: this.getAuthorizationUri(),
|
|
@@ -60,8 +150,13 @@ class OAuth2Requester extends Requester {
|
|
|
60
150
|
};
|
|
61
151
|
}
|
|
62
152
|
|
|
63
|
-
|
|
64
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Exchanges an authorization code for access and refresh tokens.
|
|
155
|
+
* Requires client_id, client_secret, redirect_uri, and tokenUri to be set.
|
|
156
|
+
*
|
|
157
|
+
* @param {string} code - The authorization code from the OAuth callback
|
|
158
|
+
* @returns {Promise<Object>} Token response containing access_token, refresh_token, etc.
|
|
159
|
+
*/
|
|
65
160
|
async getTokenFromCode(code) {
|
|
66
161
|
const params = new URLSearchParams();
|
|
67
162
|
params.append('grant_type', 'authorization_code');
|
|
@@ -82,9 +177,14 @@ class OAuth2Requester extends Requester {
|
|
|
82
177
|
return response;
|
|
83
178
|
}
|
|
84
179
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Exchanges an authorization code for tokens using Basic Auth header.
|
|
182
|
+
* Alternative to getTokenFromCode() for OAuth servers requiring Basic Auth.
|
|
183
|
+
* Override getTokenFromCode() in child class to use this instead.
|
|
184
|
+
*
|
|
185
|
+
* @param {string} code - The authorization code from the OAuth callback
|
|
186
|
+
* @returns {Promise<Object>} Token response containing access_token, refresh_token, etc.
|
|
187
|
+
*/
|
|
88
188
|
async getTokenFromCodeBasicAuthHeader(code) {
|
|
89
189
|
const params = new URLSearchParams();
|
|
90
190
|
params.append('grant_type', 'authorization_code');
|
|
@@ -108,8 +208,14 @@ class OAuth2Requester extends Requester {
|
|
|
108
208
|
return response;
|
|
109
209
|
}
|
|
110
210
|
|
|
111
|
-
|
|
112
|
-
|
|
211
|
+
/**
|
|
212
|
+
* Refreshes the access token using the refresh token.
|
|
213
|
+
* Used for authorization_code and password grant types.
|
|
214
|
+
*
|
|
215
|
+
* @param {Object} refreshTokenObject - Object containing refresh_token
|
|
216
|
+
* @param {string} refreshTokenObject.refresh_token - The refresh token
|
|
217
|
+
* @returns {Promise<Object>} New token response
|
|
218
|
+
*/
|
|
113
219
|
async refreshAccessToken(refreshTokenObject) {
|
|
114
220
|
this.access_token = undefined;
|
|
115
221
|
const params = new URLSearchParams();
|
|
@@ -131,7 +237,16 @@ class OAuth2Requester extends Requester {
|
|
|
131
237
|
return response;
|
|
132
238
|
}
|
|
133
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Adds OAuth Bearer token to request headers.
|
|
242
|
+
* Clears any existing Authorization header first to prevent stale tokens
|
|
243
|
+
* from being reused after failed refresh attempts.
|
|
244
|
+
*
|
|
245
|
+
* @param {Object} headers - Headers object to modify
|
|
246
|
+
* @returns {Promise<Object>} Headers with Authorization added
|
|
247
|
+
*/
|
|
134
248
|
async addAuthHeaders(headers) {
|
|
249
|
+
delete headers.Authorization;
|
|
135
250
|
if (this.access_token) {
|
|
136
251
|
headers.Authorization = `Bearer ${this.access_token}`;
|
|
137
252
|
}
|
|
@@ -139,29 +254,51 @@ class OAuth2Requester extends Requester {
|
|
|
139
254
|
return headers;
|
|
140
255
|
}
|
|
141
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Checks if the requester has valid authentication.
|
|
259
|
+
*
|
|
260
|
+
* @returns {boolean} True if authenticated with valid tokens
|
|
261
|
+
*/
|
|
142
262
|
isAuthenticated() {
|
|
143
|
-
return (
|
|
144
|
-
this.
|
|
145
|
-
this.
|
|
263
|
+
return !!(
|
|
264
|
+
this.access_token !== null &&
|
|
265
|
+
this.refresh_token !== null &&
|
|
146
266
|
this.accessTokenExpire &&
|
|
147
267
|
this.refreshTokenExpire
|
|
148
268
|
);
|
|
149
269
|
}
|
|
150
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Refreshes authentication based on the configured grant type.
|
|
273
|
+
* - For authorization_code/password: Uses refreshAccessToken() with refresh_token
|
|
274
|
+
* - For client_credentials: Uses getTokenFromClientCredentials() to get new token
|
|
275
|
+
*
|
|
276
|
+
* On failure, notifies delegates via DLGT_INVALID_AUTH.
|
|
277
|
+
*
|
|
278
|
+
* @returns {Promise<boolean>} True if refresh succeeded, false if failed
|
|
279
|
+
*/
|
|
151
280
|
async refreshAuth() {
|
|
152
281
|
try {
|
|
153
|
-
if (this.
|
|
282
|
+
if (this.grant_type !== 'client_credentials') {
|
|
154
283
|
await this.refreshAccessToken({
|
|
155
284
|
refresh_token: this.refresh_token,
|
|
156
285
|
});
|
|
157
286
|
} else {
|
|
158
287
|
await this.getTokenFromClientCredentials();
|
|
159
288
|
}
|
|
289
|
+
return true;
|
|
160
290
|
} catch {
|
|
161
291
|
await this.notify(this.DLGT_INVALID_AUTH);
|
|
292
|
+
return false;
|
|
162
293
|
}
|
|
163
294
|
}
|
|
164
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Obtains tokens using the Resource Owner Password Credentials grant.
|
|
298
|
+
* Requires username and password to be set.
|
|
299
|
+
*
|
|
300
|
+
* @returns {Promise<Object|undefined>} Token response or undefined on error
|
|
301
|
+
*/
|
|
165
302
|
async getTokenFromUsernamePassword() {
|
|
166
303
|
try {
|
|
167
304
|
const url = this.tokenUri;
|
|
@@ -188,6 +325,13 @@ class OAuth2Requester extends Requester {
|
|
|
188
325
|
}
|
|
189
326
|
}
|
|
190
327
|
|
|
328
|
+
/**
|
|
329
|
+
* Obtains tokens using the Client Credentials grant.
|
|
330
|
+
* Used for server-to-server authentication without a user context.
|
|
331
|
+
* Requires client_id, client_secret, and optionally audience to be set.
|
|
332
|
+
*
|
|
333
|
+
* @returns {Promise<Object|undefined>} Token response or undefined on error
|
|
334
|
+
*/
|
|
191
335
|
async getTokenFromClientCredentials() {
|
|
192
336
|
try {
|
|
193
337
|
const url = this.tokenUri;
|
|
@@ -75,8 +75,10 @@ class Requester extends Delegate {
|
|
|
75
75
|
await this.notify(this.DLGT_INVALID_AUTH);
|
|
76
76
|
} else {
|
|
77
77
|
this.refreshCount++;
|
|
78
|
-
await this.refreshAuth();
|
|
79
|
-
|
|
78
|
+
const refreshSucceeded = await this.refreshAuth();
|
|
79
|
+
if (refreshSucceeded) {
|
|
80
|
+
return this._request(url, options, i + 1);
|
|
81
|
+
}
|
|
80
82
|
}
|
|
81
83
|
}
|
|
82
84
|
|
|
@@ -108,7 +110,6 @@ class Requester extends Delegate {
|
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
async _post(options, stringify = true) {
|
|
111
|
-
console.log('options', options);
|
|
112
113
|
const fetchOptions = {
|
|
113
114
|
method: 'POST',
|
|
114
115
|
credentials: 'include',
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/core",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0-next.
|
|
4
|
+
"version": "2.0.0-next.66",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.588.0",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@friggframework/eslint-config": "2.0.0-next.
|
|
42
|
-
"@friggframework/prettier-config": "2.0.0-next.
|
|
43
|
-
"@friggframework/test": "2.0.0-next.
|
|
41
|
+
"@friggframework/eslint-config": "2.0.0-next.66",
|
|
42
|
+
"@friggframework/prettier-config": "2.0.0-next.66",
|
|
43
|
+
"@friggframework/test": "2.0.0-next.66",
|
|
44
44
|
"@prisma/client": "^6.17.0",
|
|
45
45
|
"@types/lodash": "4.17.15",
|
|
46
46
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"publishConfig": {
|
|
81
81
|
"access": "public"
|
|
82
82
|
},
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "33a0a9416970c03aa3eec7d5f295590cc08d7f98"
|
|
84
84
|
}
|