@friggframework/core 2.0.0-next.109 → 2.0.0-next.110
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/modules/module.js
CHANGED
|
@@ -148,9 +148,29 @@ class Module extends Delegate {
|
|
|
148
148
|
await this.deauthorize();
|
|
149
149
|
} else if (delegateString === this.api.DLGT_INVALID_AUTH) {
|
|
150
150
|
await this.markCredentialsInvalid(object);
|
|
151
|
+
} else if (delegateString === this.api.DLGT_CREDENTIAL_RELOAD) {
|
|
152
|
+
return this.reloadCredential();
|
|
151
153
|
}
|
|
152
154
|
}
|
|
153
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Re-reads the credential row from the database. The requester can then
|
|
158
|
+
* adopt a concurrent invocation's refresh and does not race it.
|
|
159
|
+
* @returns {Promise<Object|null>} The persisted token fields, or null
|
|
160
|
+
* when no credential row is available. The requester treats null as
|
|
161
|
+
* "nothing to adopt" and continues with its own refresh.
|
|
162
|
+
*/
|
|
163
|
+
async reloadCredential() {
|
|
164
|
+
if (!this.credential?.id) return null;
|
|
165
|
+
const freshFromDatabase =
|
|
166
|
+
await this.credentialRepository.findCredentialById(
|
|
167
|
+
this.credential.id
|
|
168
|
+
);
|
|
169
|
+
if (!freshFromDatabase) return null;
|
|
170
|
+
this.credential = freshFromDatabase;
|
|
171
|
+
return this.apiParamsFromCredential(freshFromDatabase);
|
|
172
|
+
}
|
|
173
|
+
|
|
154
174
|
async markCredentialsInvalid(diagnosticInfo = null) {
|
|
155
175
|
if (!this.credential) return;
|
|
156
176
|
|
|
@@ -60,9 +60,26 @@ class OAuth2Requester extends Requester {
|
|
|
60
60
|
this.DLGT_TOKEN_UPDATE = 'TOKEN_UPDATE';
|
|
61
61
|
/** @type {string} Delegate type for token deauthorization notifications */
|
|
62
62
|
this.DLGT_TOKEN_DEAUTHORIZED = 'TOKEN_DEAUTHORIZED';
|
|
63
|
+
/**
|
|
64
|
+
* @type {string} Delegate type that asks the Module for the stored
|
|
65
|
+
* credential. The requester can then adopt a concurrent invocation's
|
|
66
|
+
* refresh and does not race it. See _adoptNewerCredential.
|
|
67
|
+
*/
|
|
68
|
+
this.DLGT_CREDENTIAL_RELOAD = 'CREDENTIAL_RELOAD';
|
|
63
69
|
|
|
64
70
|
this.delegateTypes.push(this.DLGT_TOKEN_UPDATE);
|
|
65
71
|
this.delegateTypes.push(this.DLGT_TOKEN_DEAUTHORIZED);
|
|
72
|
+
this.delegateTypes.push(this.DLGT_CREDENTIAL_RELOAD);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Re-read delays after an invalid_grant, in ms. The winner's write
|
|
76
|
+
* can arrive after the loser's rejection (716 ms measured in
|
|
77
|
+
* production). Wait between re-reads before you decide that the
|
|
78
|
+
* credential is dead. Tests can inject other values.
|
|
79
|
+
*/
|
|
80
|
+
this.credentialReloadBackoffMs = params?.credentialReloadBackoffMs ?? [
|
|
81
|
+
500, 1000, 1500,
|
|
82
|
+
];
|
|
66
83
|
|
|
67
84
|
/** @type {string} OAuth grant type */
|
|
68
85
|
this.grant_type = get(params, 'grant_type', 'authorization_code');
|
|
@@ -295,6 +312,16 @@ class OAuth2Requester extends Requester {
|
|
|
295
312
|
* @returns {Promise<boolean>} True if refresh succeeded, false if failed
|
|
296
313
|
*/
|
|
297
314
|
async refreshAuth() {
|
|
315
|
+
// The wrapper runs this check before it calls refreshAuth(). Callers
|
|
316
|
+
// that reach this method directly skip the wrapper, so the check runs
|
|
317
|
+
// here for them. The guard keeps it at one read per refresh.
|
|
318
|
+
if (
|
|
319
|
+
!this._isInsideRefreshFlow() &&
|
|
320
|
+
(await this._adoptNewerCredential())
|
|
321
|
+
) {
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
|
|
298
325
|
try {
|
|
299
326
|
console.log('[Frigg] Starting token refresh', {
|
|
300
327
|
grant_type: this.grant_type,
|
|
@@ -310,7 +337,11 @@ class OAuth2Requester extends Requester {
|
|
|
310
337
|
refresh_token: this.refresh_token,
|
|
311
338
|
});
|
|
312
339
|
} else {
|
|
313
|
-
|
|
340
|
+
// getTokenFromClientCredentials() reports a failed token
|
|
341
|
+
// request itself and resolves to undefined. Without this
|
|
342
|
+
// check, the refresh counts as a success with no new token.
|
|
343
|
+
const tokenRes = await this.getTokenFromClientCredentials();
|
|
344
|
+
if (!tokenRes) return false;
|
|
314
345
|
}
|
|
315
346
|
console.log('[Frigg] Token refresh succeeded');
|
|
316
347
|
return true;
|
|
@@ -322,8 +353,21 @@ class OAuth2Requester extends Requester {
|
|
|
322
353
|
response_status: error?.response?.status,
|
|
323
354
|
response_data: error?.response?.data,
|
|
324
355
|
});
|
|
325
|
-
|
|
326
|
-
|
|
356
|
+
|
|
357
|
+
if (!this._isDefinitiveAuthRejection(error)) {
|
|
358
|
+
throw this._transportFailureError(error, moduleName);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (await this._adoptNewerCredentialWithBackoff()) return true;
|
|
362
|
+
|
|
363
|
+
// The provider rejected the grant, and the store has nothing
|
|
364
|
+
// newer. The credential is dead.
|
|
365
|
+
this.telemetry?.count?.('frigg.auth.refresh_race_lost', 1, {
|
|
366
|
+
module: this._telemetryModuleLabel(),
|
|
367
|
+
});
|
|
368
|
+
// Send the status only. The refresh body contains the
|
|
369
|
+
// client_secret, and FetchError puts the body in its message
|
|
370
|
+
// outside prod.
|
|
327
371
|
await this.notify(this.DLGT_INVALID_AUTH, {
|
|
328
372
|
statusCode: error?.statusCode,
|
|
329
373
|
});
|
|
@@ -331,6 +375,106 @@ class OAuth2Requester extends Requester {
|
|
|
331
375
|
}
|
|
332
376
|
}
|
|
333
377
|
|
|
378
|
+
/**
|
|
379
|
+
* A timeout, a 429, or a 5xx from the token endpoint says nothing about
|
|
380
|
+
* the credential. The error must stay retryable: the caller fails loudly
|
|
381
|
+
* (worker throw → SQS retry → DLQ) and does not flag a healthy
|
|
382
|
+
* credential. This is a fresh Error on purpose. Outside prod, the
|
|
383
|
+
* original message can contain the request body, and the body carries
|
|
384
|
+
* the client_secret.
|
|
385
|
+
*/
|
|
386
|
+
_transportFailureError(error, moduleName) {
|
|
387
|
+
const status =
|
|
388
|
+
error?.statusCode ?? error?.status ?? error?.response?.status;
|
|
389
|
+
const transportError = new Error(
|
|
390
|
+
`[Frigg] Token refresh transport failure for ${moduleName}` +
|
|
391
|
+
(status != null ? ` (status ${status})` : '')
|
|
392
|
+
);
|
|
393
|
+
transportError.statusCode = status;
|
|
394
|
+
transportError.isTokenRefreshTransportFailure = true;
|
|
395
|
+
return transportError;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* A definitive rejection can mean that another invocation consumed this
|
|
400
|
+
* refresh token first. That invocation's write can be unreadable for a
|
|
401
|
+
* short time. Re-read with a bounded backoff before you decide that the
|
|
402
|
+
* credential is dead.
|
|
403
|
+
*/
|
|
404
|
+
async _adoptNewerCredentialWithBackoff() {
|
|
405
|
+
for (const delayMs of this.credentialReloadBackoffMs) {
|
|
406
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
407
|
+
if (await this._adoptNewerCredential()) {
|
|
408
|
+
console.log(
|
|
409
|
+
'[Frigg] Adopted a newer credential after a refresh rejection',
|
|
410
|
+
{ module: this._telemetryModuleLabel() }
|
|
411
|
+
);
|
|
412
|
+
this.telemetry?.count?.('frigg.auth.refresh_race_recovered', 1, {
|
|
413
|
+
module: this._telemetryModuleLabel(),
|
|
414
|
+
});
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* True when the token endpoint refused the grant. RFC 6749 §5.2 sets
|
|
423
|
+
* the status: 400, or 401 for invalid_client. A 429 or a 5xx is never a
|
|
424
|
+
* verdict on the credential. Body markers are only a fallback for SDK
|
|
425
|
+
* errors that have no status code. Production FetchErrors have a
|
|
426
|
+
* sanitized body, so a marker cannot be the primary signal.
|
|
427
|
+
*/
|
|
428
|
+
_isDefinitiveAuthRejection(error) {
|
|
429
|
+
const status =
|
|
430
|
+
error?.statusCode ?? error?.status ?? error?.response?.status;
|
|
431
|
+
if (status !== undefined && status !== null) {
|
|
432
|
+
return status === 400 || status === 401;
|
|
433
|
+
}
|
|
434
|
+
const haystack = [
|
|
435
|
+
error?.message,
|
|
436
|
+
error?.body,
|
|
437
|
+
typeof error?.error === 'string' ? error.error : null,
|
|
438
|
+
error?.response?.data && JSON.stringify(error.response.data),
|
|
439
|
+
]
|
|
440
|
+
.filter(Boolean)
|
|
441
|
+
.join(' ');
|
|
442
|
+
return /\b(invalid_grant|invalid_client)\b/i.test(haystack);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Adopts the credential stored in the database if it is newer than the
|
|
447
|
+
* credential from the instance. The refresh token decides "newer": a
|
|
448
|
+
* provider can rotate it and return an identical access-token string.
|
|
449
|
+
* The reload only reads. A reload failure is not fatal: a database blip
|
|
450
|
+
* must not change the auth behavior.
|
|
451
|
+
*
|
|
452
|
+
* @returns {Promise<boolean>} True if the module adopted a newer
|
|
453
|
+
* credential.
|
|
454
|
+
*/
|
|
455
|
+
async _adoptNewerCredential() {
|
|
456
|
+
let stored = null;
|
|
457
|
+
try {
|
|
458
|
+
stored = await this.notify(this.DLGT_CREDENTIAL_RELOAD);
|
|
459
|
+
} catch (_) {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
if (!stored?.refresh_token) return false;
|
|
463
|
+
if (stored.refresh_token === this.refresh_token) return false;
|
|
464
|
+
|
|
465
|
+
if (stored.access_token) {
|
|
466
|
+
this.access_token = stored.access_token;
|
|
467
|
+
}
|
|
468
|
+
this.refresh_token = stored.refresh_token;
|
|
469
|
+
if (stored.accessTokenExpire !== undefined) {
|
|
470
|
+
this.accessTokenExpire = stored.accessTokenExpire;
|
|
471
|
+
}
|
|
472
|
+
if (stored.refreshTokenExpire !== undefined) {
|
|
473
|
+
this.refreshTokenExpire = stored.refreshTokenExpire;
|
|
474
|
+
}
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
|
|
334
478
|
/**
|
|
335
479
|
* Obtains tokens using the Resource Owner Password Credentials grant.
|
|
336
480
|
* Requires username and password to be set.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const fetch = require('node-fetch');
|
|
2
|
+
const { AsyncLocalStorage } = require('async_hooks');
|
|
2
3
|
const { Delegate } = require('../../core');
|
|
3
4
|
const { FetchError } = require('../../errors');
|
|
4
5
|
const { get } = require('../../assertions');
|
|
@@ -7,6 +8,14 @@ const { getTelemetry } = require('../../telemetry/telemetry-runtime');
|
|
|
7
8
|
const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
|
|
8
9
|
const MAX_AUTH_RETRIES = 3;
|
|
9
10
|
|
|
11
|
+
// This context marks the async call chain that holds the refresh slot, and
|
|
12
|
+
// names the requester that holds it. Token requests re-enter _rawRequest
|
|
13
|
+
// through this._post. A 401 from inside that chain must fail fast: if it
|
|
14
|
+
// joins the refresh in flight, it awaits its own promise and hangs. The
|
|
15
|
+
// identity keeps a second requester used inside the chain out of the check.
|
|
16
|
+
// AsyncLocalStorage reaches the nested calls without help from the subclasses.
|
|
17
|
+
const refreshContext = new AsyncLocalStorage();
|
|
18
|
+
|
|
10
19
|
class Requester extends Delegate {
|
|
11
20
|
constructor(params) {
|
|
12
21
|
super(params);
|
|
@@ -14,6 +23,11 @@ class Requester extends Delegate {
|
|
|
14
23
|
this.isRefreshable = false;
|
|
15
24
|
this.refreshCount = 0;
|
|
16
25
|
this.authGraceRetryCount = 0;
|
|
26
|
+
// Concurrent 401s share one refreshAuth() run. See _refreshAuthOnce().
|
|
27
|
+
this._inFlightRefresh = null;
|
|
28
|
+
// This counter increases when the tokens change. A stale 401 (the
|
|
29
|
+
// token changed already) then retries and does not refresh again.
|
|
30
|
+
this._authGeneration = 0;
|
|
17
31
|
this.DLGT_INVALID_AUTH = 'INVALID_AUTH';
|
|
18
32
|
this.delegateTypes.push(this.DLGT_INVALID_AUTH);
|
|
19
33
|
this.agent = get(params, 'agent', null);
|
|
@@ -184,6 +198,10 @@ class Requester extends Delegate {
|
|
|
184
198
|
|
|
185
199
|
options.headers = await this.addAuthHeaders(options.headers);
|
|
186
200
|
|
|
201
|
+
// A 401 that arrives after a concurrent refresh is stale. It is not
|
|
202
|
+
// proof that the new token failed.
|
|
203
|
+
const authGenerationAtDispatch = this._authGeneration;
|
|
204
|
+
|
|
187
205
|
if (this.agent) options.agent = this.agent;
|
|
188
206
|
|
|
189
207
|
// Per-attempt timeout — fresh AbortController per call so the retry
|
|
@@ -266,6 +284,26 @@ class Requester extends Delegate {
|
|
|
266
284
|
}
|
|
267
285
|
|
|
268
286
|
if (status === 401) {
|
|
287
|
+
// A 401 from inside the refresh flow means the provider
|
|
288
|
+
// rejected the credential itself (invalid_client). A new
|
|
289
|
+
// refresh cannot help. A join would await this same call.
|
|
290
|
+
if (this._isInsideRefreshFlow()) {
|
|
291
|
+
throw await this._invalidateAuth(
|
|
292
|
+
encodedUrl,
|
|
293
|
+
options,
|
|
294
|
+
response
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const tokenReplacedWhileInFlight =
|
|
299
|
+
this._authGeneration !== authGenerationAtDispatch;
|
|
300
|
+
if (this.isRefreshable && tokenReplacedWhileInFlight) {
|
|
301
|
+
// This request did not try the current token. Retry with
|
|
302
|
+
// it. Do not spend one more provider-side rotation.
|
|
303
|
+
clearRequestTimer();
|
|
304
|
+
return this._rawRequest(url, options, attempt + 1);
|
|
305
|
+
}
|
|
306
|
+
|
|
269
307
|
if (!this.isRefreshable) {
|
|
270
308
|
// Up to MAX_AUTH_RETRIES grace retries before invalidating
|
|
271
309
|
// — a 401 alone isn't proof the credential is bad.
|
|
@@ -290,14 +328,15 @@ class Requester extends Delegate {
|
|
|
290
328
|
);
|
|
291
329
|
}
|
|
292
330
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
clearRequestTimer();
|
|
298
|
-
return this._rawRequest(url, options, attempt + 1);
|
|
299
|
-
}
|
|
331
|
+
// Concurrent 401s share one refresh. Independent refreshes
|
|
332
|
+
// kill each other, because many providers use single-use
|
|
333
|
+
// refresh tokens. Only the initiator spends the retry budget.
|
|
334
|
+
const refreshAlreadyInFlight = Boolean(this._inFlightRefresh);
|
|
300
335
|
|
|
336
|
+
if (
|
|
337
|
+
!refreshAlreadyInFlight &&
|
|
338
|
+
this.refreshCount >= MAX_AUTH_RETRIES
|
|
339
|
+
) {
|
|
301
340
|
throw await this._invalidateAuth(
|
|
302
341
|
encodedUrl,
|
|
303
342
|
options,
|
|
@@ -305,6 +344,16 @@ class Requester extends Delegate {
|
|
|
305
344
|
);
|
|
306
345
|
}
|
|
307
346
|
|
|
347
|
+
if (!refreshAlreadyInFlight) {
|
|
348
|
+
this.refreshCount++;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const refreshSucceeded = await this._refreshAuthOnce();
|
|
352
|
+
if (refreshSucceeded) {
|
|
353
|
+
clearRequestTimer();
|
|
354
|
+
return this._rawRequest(url, options, attempt + 1);
|
|
355
|
+
}
|
|
356
|
+
|
|
308
357
|
throw await this._invalidateAuth(encodedUrl, options, response);
|
|
309
358
|
}
|
|
310
359
|
|
|
@@ -427,6 +476,64 @@ class Requester extends Delegate {
|
|
|
427
476
|
return this._request(options.url, fetchOptions);
|
|
428
477
|
}
|
|
429
478
|
|
|
479
|
+
/**
|
|
480
|
+
* Runs one refreshAuth() at a time. The first caller starts the refresh.
|
|
481
|
+
* Callers that arrive during the refresh await the same promise. The
|
|
482
|
+
* check-and-store step is synchronous. Thus two concurrent callers
|
|
483
|
+
* cannot both start a refresh.
|
|
484
|
+
*
|
|
485
|
+
* @returns {Promise<boolean>} True if the refresh succeeded.
|
|
486
|
+
*/
|
|
487
|
+
_refreshAuthOnce() {
|
|
488
|
+
if (!this._inFlightRefresh) {
|
|
489
|
+
// Keep .finally last. The stored promise must be the promise
|
|
490
|
+
// that clears the slot. Then the slot is free before a waiter
|
|
491
|
+
// resumes.
|
|
492
|
+
this._inFlightRefresh = this._adoptOrRefresh().finally(() => {
|
|
493
|
+
this._inFlightRefresh = null;
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
return this._inFlightRefresh;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Adopts a newer stored credential, or refreshes. Both steps run inside
|
|
501
|
+
* the marker, because the marker must cover the whole time this instance
|
|
502
|
+
* holds the slot. The adoption lives here, not in refreshAuth(), so a
|
|
503
|
+
* module that overrides refreshAuth() still gets it.
|
|
504
|
+
*
|
|
505
|
+
* @returns {Promise<boolean>} True if the instance holds a usable token.
|
|
506
|
+
*/
|
|
507
|
+
async _adoptOrRefresh() {
|
|
508
|
+
const refreshSucceeded = await refreshContext.run(
|
|
509
|
+
{ requester: this },
|
|
510
|
+
async () => {
|
|
511
|
+
if (await this._adoptNewerCredential()) return true;
|
|
512
|
+
return this.refreshAuth();
|
|
513
|
+
}
|
|
514
|
+
);
|
|
515
|
+
if (refreshSucceeded) this._authGeneration++;
|
|
516
|
+
return refreshSucceeded;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Hook for requesters that hold a rotating stored credential. Return true
|
|
521
|
+
* when the instance adopted a newer stored credential and needs no
|
|
522
|
+
* refresh. A module backed by a vendor SDK overrides this, calls super,
|
|
523
|
+
* and then copies the adopted tokens into its SDK client.
|
|
524
|
+
*
|
|
525
|
+
* @returns {Promise<boolean>} True if the instance adopted a newer
|
|
526
|
+
* credential.
|
|
527
|
+
*/
|
|
528
|
+
async _adoptNewerCredential() {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/** True while this instance runs its own refresh. */
|
|
533
|
+
_isInsideRefreshFlow() {
|
|
534
|
+
return refreshContext.getStore()?.requester === this;
|
|
535
|
+
}
|
|
536
|
+
|
|
430
537
|
async refreshAuth() {
|
|
431
538
|
throw new Error('refreshAuth not yet defined in child of Requester');
|
|
432
539
|
}
|
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.110",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.588.0",
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@friggframework/eslint-config": "2.0.0-next.
|
|
52
|
-
"@friggframework/prettier-config": "2.0.0-next.
|
|
53
|
-
"@friggframework/test": "2.0.0-next.
|
|
51
|
+
"@friggframework/eslint-config": "2.0.0-next.110",
|
|
52
|
+
"@friggframework/prettier-config": "2.0.0-next.110",
|
|
53
|
+
"@friggframework/test": "2.0.0-next.110",
|
|
54
54
|
"@prisma/client": "^6.19.3",
|
|
55
55
|
"@types/lodash": "4.17.15",
|
|
56
56
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
@@ -90,5 +90,5 @@
|
|
|
90
90
|
"publishConfig": {
|
|
91
91
|
"access": "public"
|
|
92
92
|
},
|
|
93
|
-
"gitHead": "
|
|
93
|
+
"gitHead": "90c538f9523764b80b71a49d2d037e89cf426e0a"
|
|
94
94
|
}
|
|
@@ -41,6 +41,7 @@ declare module "@friggframework/module-plugin" {
|
|
|
41
41
|
): Promise<any>;
|
|
42
42
|
parseBody(response: any): Promise<any>;
|
|
43
43
|
refreshAuth(): Promise<any>;
|
|
44
|
+
_adoptNewerCredential(): Promise<boolean>;
|
|
44
45
|
|
|
45
46
|
delegate: any;
|
|
46
47
|
delegateTypes: any[];
|
|
@@ -72,6 +73,7 @@ declare module "@friggframework/module-plugin" {
|
|
|
72
73
|
_put(options: RequestOptions): Promise<any>;
|
|
73
74
|
_delete(options: RequestOptions): Promise<any>;
|
|
74
75
|
refreshAuth(): Promise<any>;
|
|
76
|
+
_adoptNewerCredential(): Promise<boolean>;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
type RequestOptions = {
|
|
@@ -201,6 +203,7 @@ declare module "@friggframework/module-plugin" {
|
|
|
201
203
|
addAuthHeaders(headers: object): Promise<object>;
|
|
202
204
|
isAuthenticated(): boolean;
|
|
203
205
|
refreshAuth(): Promise<void>;
|
|
206
|
+
_adoptNewerCredential(): Promise<boolean>;
|
|
204
207
|
getTokenFromUsernamePassword(): Promise<Token>;
|
|
205
208
|
getTokenFromClientCredentials(): Promise<Token>;
|
|
206
209
|
}
|