@zackbart/connecta 0.37.0 → 0.38.0
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/CHANGELOG.md +80 -0
- package/README.md +2 -2
- package/bin/connecta.mjs +1 -39
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.js +2 -4
- package/dist/agent-identity.js +1 -1
- package/dist/auth/clerk-sdk/generated.js +2 -1
- package/dist/auth/cloudflare-access.d.ts +3 -1
- package/dist/auth/cloudflare-access.js +22 -11
- package/dist/auth/downstream-oauth.js +195 -270
- package/dist/auth/downstream-revocation.js +1 -1
- package/dist/auth/oauth-grant.js +11 -0
- package/dist/auth/oauth-token-response.js +1 -1
- package/dist/auth/vercel-agent.js +2 -1
- package/dist/auth-elicitation.js +2 -2
- package/dist/branding.d.ts +1 -15
- package/dist/branding.js +2 -2
- package/dist/bytes.js +42 -0
- package/dist/call-admission-context.js +2 -5
- package/dist/call-admission.js +5 -0
- package/dist/call-recovery.js +0 -3
- package/dist/catalog-cache.js +26 -7
- package/dist/catalog-fingerprint.js +2 -18
- package/dist/catalog-revision.js +9 -0
- package/dist/catalog-service.js +51 -20
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +1 -1
- package/dist/config-schema.js +6 -2
- package/dist/config.d.ts +41 -14
- package/dist/config.js +21 -71
- package/dist/connector-caller.js +1 -1
- package/dist/connectors/api-connector.js +4 -3
- package/dist/connectors/guarded-fetch.d.ts +0 -6
- package/dist/connectors/guarded-fetch.js +1 -11
- package/dist/connectors/remote-mcp.js +93 -69
- package/dist/connectors/resource-uri.js +0 -4
- package/dist/credentials.js +3 -0
- package/dist/d1.d.ts +0 -89
- package/dist/d1.js +0 -34
- package/dist/downstream-input.js +2 -2
- package/dist/execute.js +161 -116
- package/dist/executor-result.js +2 -1
- package/dist/executors/quickjs-runtime.js +2 -25
- package/dist/executors/quickjs.js +1 -1
- package/dist/guest-runtime.js +5 -3
- package/dist/guest-types.d.ts +5 -5
- package/dist/http/client.js +1 -1
- package/dist/http/errors.js +2 -2
- package/dist/http/json-schema.js +1 -1
- package/dist/http/node-transport.js +0 -1
- package/dist/http/oauth.js +1 -1
- package/dist/http/paging.js +6 -9
- package/dist/http/provider.js +1 -1
- package/dist/http/safety.js +1 -1
- package/dist/http/services.js +1 -8
- package/dist/http/tools.js +1 -7
- package/dist/http/vendor.js +7 -7
- package/dist/identity.js +2 -6
- package/dist/index.js +1 -2
- package/dist/invocation-auth.js +2 -1
- package/dist/invocation.js +8 -2
- package/dist/meta-tools.js +27 -27
- package/dist/native-tools.js +2 -1
- package/dist/oauth-handoff.js +2 -2
- package/dist/operation-vocabulary.js +1 -1
- package/dist/operator-record.d.ts +8 -2
- package/dist/operator-record.js +18 -50
- package/dist/operator-ui/contract.d.ts +4 -2
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +7 -2
- package/dist/program-writes.js +3 -8
- package/dist/provider-call.js +0 -7
- package/dist/provider.d.ts +1 -1
- package/dist/providers/_shared/google/kit.js +1 -6
- package/dist/providers/_shared/google/service-account.js +1 -3
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/_shared/guide.js +3 -0
- package/dist/providers/_shared/json.js +12 -0
- package/dist/providers/_shared/rest/compact-index.js +1 -1
- package/dist/providers/_shared/rest/kit.js +1 -3
- package/dist/providers/_shared/rest/operation-index.js +1 -3
- package/dist/providers/_shared/rest/value-safety.js +26 -28
- package/dist/providers/breeze/api.js +2 -7
- package/dist/providers/breeze/index.js +4 -2
- package/dist/providers/breeze/tools.js +3 -4
- package/dist/providers/ccb/api.js +1 -6
- package/dist/providers/ccb/index.js +2 -1
- package/dist/providers/ccb/tools.js +1 -6
- package/dist/providers/cloudflare/api.js +2 -2
- package/dist/providers/cloudflare/index.js +2 -1
- package/dist/providers/cloudflare/safety.js +3 -3
- package/dist/providers/docs/tools.js +2 -10
- package/dist/providers/drive/tools.js +12 -25
- package/dist/providers/forms/tools.js +1 -9
- package/dist/providers/gmail/tools.js +5 -21
- package/dist/providers/overflow/api.js +1 -3
- package/dist/providers/overflow/index.js +2 -1
- package/dist/providers/overflow/tools.js +3 -7
- package/dist/providers/planning-center/api.js +2 -1
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/lib/json-api.js +1 -9
- package/dist/providers/planning-center/tools.js +2 -1
- package/dist/providers/sheets/tools.js +1 -9
- package/dist/providers/slides/tools.js +4 -13
- package/dist/providers/stripe/index.js +2 -1
- package/dist/providers/stripe/safety.js +2 -2
- package/dist/providers/tithely/api.js +2 -7
- package/dist/providers/tithely/index.js +3 -2
- package/dist/providers/tithely/tools.js +7 -5
- package/dist/providers/vercel/api.js +1 -6
- package/dist/providers/vercel/lib/pin.js +1 -1
- package/dist/providers/vercel/safety.js +1 -1
- package/dist/providers/vercel/tools.js +2 -1
- package/dist/redaction-marker.js +2 -2
- package/dist/registry.d.ts +4 -1
- package/dist/registry.js +35 -22
- package/dist/request-principal.js +15 -2
- package/dist/request-state.js +2 -2
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/activity-disclosure.js +1 -1
- package/dist/routes/connect.js +2 -2
- package/dist/routes/credentials.js +1 -1
- package/dist/routes/dispatch.js +10 -0
- package/dist/routes/mcp.js +23 -21
- package/dist/routes/oauth-management.js +1 -1
- package/dist/routes/operator.js +53 -7
- package/dist/routes/shared.d.ts +8 -8
- package/dist/routes/shared.js +22 -15
- package/dist/routes/ui-config.js +6 -6
- package/dist/routes/ui-data.js +2 -2
- package/dist/routes/ui.js +9 -4
- package/dist/runtime/call-admission.js +1 -1
- package/dist/runtime/call.js +1 -1
- package/dist/runtime/settlement.js +1 -1
- package/dist/runtime/shared-read.js +2 -15
- package/dist/sent-secrets.d.ts +14 -6
- package/dist/sent-secrets.js +231 -85
- package/dist/server.js +4 -4
- package/dist/skills.js +8 -35
- package/dist/sqlite.d.ts +0 -19
- package/dist/sqlite.js +2 -83
- package/dist/storage/keys.js +29 -124
- package/dist/storage/sql-native.js +14 -111
- package/dist/storage/sql.d.ts +1 -24
- package/dist/storage/sql.js +1 -12
- package/dist/tool-error.js +4 -0
- package/dist/types.d.ts +4 -3
- package/dist/ui.js +3 -0
- package/dist/url-safety.js +0 -4
- package/dist/usage-guide.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +1 -1
- package/documentation/README.md +6 -42
- package/documentation/architecture.md +190 -507
- package/documentation/auth.md +172 -58
- package/documentation/code-mode.md +98 -66
- package/documentation/deploying.md +19 -40
- package/documentation/integrating.md +8 -19
- package/documentation/meta-tools.md +23 -6
- package/documentation/migration-0.33.md +5 -5
- package/documentation/migration-0.34.md +2 -4
- package/documentation/migration-retired-layouts.md +74 -0
- package/documentation/operator-tests.md +19 -119
- package/documentation/operator-ui.md +46 -8
- package/documentation/provider-auth-migration.md +4 -10
- package/documentation/provider-migration-0.29.md +4 -15
- package/examples/worker/AGENTS.md +4 -6
- package/examples/worker/README.md +20 -115
- package/guides/connecting-agents.md +7 -30
- package/guides/how-it-works.md +2 -2
- package/guides/safety-and-trust.md +4 -1
- package/package.json +19 -19
- package/templates/node/README.md +9 -26
- package/templates/node/package.json +1 -1
- package/dist/auth/oauth-v2-migration.js +0 -135
- package/dist/provider-call-facts.d.ts +0 -115
- package/dist/provider-call.d.ts +0 -112
- package/dist/storage/d1-copy.js +0 -255
- package/dist/storage/sql-schema.d.ts +0 -6
- package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
- package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bytesToHex, sha256Hex } from "../bytes.js";
|
|
1
2
|
import { UnauthorizedError } from "@modelcontextprotocol/client";
|
|
2
3
|
import { Effect, Exit } from "effect";
|
|
3
4
|
import { byteReadResponse } from "../byte-read-response.js";
|
|
@@ -8,12 +9,12 @@ import { keepErrorName } from "../redaction-identity.js";
|
|
|
8
9
|
import { fromSignal, runEdge } from "../runtime/run.js";
|
|
9
10
|
import { Settlement } from "../runtime/settlement.js";
|
|
10
11
|
import { SentSecrets } from "../sent-secrets.js";
|
|
11
|
-
import { OAUTH_FLOW_TTL_SECONDS, OAUTH_REFRESH_LEASE_SECONDS, oauthFlowKeys, oauthGrantKeys, oauthRefreshActiveKeys, oauthRefreshKeys, oauthRefreshSpentKeys, } from "../storage/keys.js";
|
|
12
|
+
import { OAUTH_FLOW_TTL_SECONDS, OAUTH_REFRESH_LEASE_SECONDS, oauthFlowKeys, oauthGrantKeys, retiredOAuthKeys, oauthRefreshActiveKeys, oauthRefreshKeys, oauthRefreshSpentKeys, } from "../storage/keys.js";
|
|
12
13
|
import { authenticateRemoteClient, downstreamClientMetadata, remoteClientAuthMethod, } from "./downstream-client-metadata.js";
|
|
13
14
|
import { revokeDownstreamGrant } from "./downstream-revocation.js";
|
|
14
15
|
import { authAwait, authCall, authCatch, authDetached, authFlow, authInvoke, authMethod, authOwnMethod, authProgram, authStorage, authThen, authValue, registerAuth, registerAuthMethod, } from "./native.js";
|
|
15
16
|
import { isOAuthCredentialRequest, isRefreshTokenRequest, refreshResponseOutcome, sdkSafeTokenResponse, tokenGrantType, } from "./oauth-token-response.js";
|
|
16
|
-
import {
|
|
17
|
+
import { discoveryIssuer } from "./oauth-grant.js";
|
|
17
18
|
/** @internal Keep the SDK's cloned body reads inside the code exchange's request. */
|
|
18
19
|
export function codeExchangeResponse(response, signal) {
|
|
19
20
|
if (!response.body)
|
|
@@ -105,12 +106,11 @@ export class OAuthRequestNotSentError extends ConnectorCallError {
|
|
|
105
106
|
function randomState() {
|
|
106
107
|
const bytes = new Uint8Array(32);
|
|
107
108
|
crypto.getRandomValues(bytes);
|
|
108
|
-
return
|
|
109
|
+
return bytesToHex(bytes);
|
|
109
110
|
}
|
|
110
111
|
/** Lowercase hex SHA-256 of a consent's `state`: the name of its flow record. */
|
|
111
112
|
export async function oauthStateDigest(state) {
|
|
112
|
-
|
|
113
|
-
return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
113
|
+
return sha256Hex(state);
|
|
114
114
|
}
|
|
115
115
|
function storedRefreshSpent(raw) {
|
|
116
116
|
const value = raw === null ? undefined : parsed(raw);
|
|
@@ -126,10 +126,11 @@ function storedRefreshSpent(raw) {
|
|
|
126
126
|
: undefined;
|
|
127
127
|
}
|
|
128
128
|
const REFRESH_REQUEST_DEADLINE_MS = 20_000;
|
|
129
|
+
const REFRESH_COMMIT_DEADLINE_MS = 20_000;
|
|
129
130
|
const REFRESH_WAIT_DEADLINE_MS = 35_000;
|
|
130
|
-
function refreshWait(signal) {
|
|
131
|
+
function refreshWait(signal, timeoutMs = REFRESH_WAIT_DEADLINE_MS) {
|
|
131
132
|
const deadline = new AbortController();
|
|
132
|
-
let timer = setTimeout(() => deadline.abort(),
|
|
133
|
+
let timer = setTimeout(() => deadline.abort(), timeoutMs);
|
|
133
134
|
let closed = false;
|
|
134
135
|
let transferred = false;
|
|
135
136
|
let requestStarted = false;
|
|
@@ -429,9 +430,13 @@ export class OAuthRefreshCoordinator {
|
|
|
429
430
|
return authFlow(function* () {
|
|
430
431
|
const flight = { outcome: new Settlement() };
|
|
431
432
|
this.flights.set(epoch, flight);
|
|
432
|
-
// Pin the owner partition until
|
|
433
|
+
// Pin the owner partition until this coordinator exits, whoever is waiting.
|
|
433
434
|
const release = this.retainPartition();
|
|
435
|
+
let settled = false;
|
|
434
436
|
const settle = (outcome) => {
|
|
437
|
+
if (settled)
|
|
438
|
+
return;
|
|
439
|
+
settled = true;
|
|
435
440
|
if (this.flights.get(epoch) === flight)
|
|
436
441
|
this.flights.delete(epoch);
|
|
437
442
|
release();
|
|
@@ -443,131 +448,162 @@ export class OAuthRefreshCoordinator {
|
|
|
443
448
|
provider.recordRefreshFailure(verdict);
|
|
444
449
|
settle({ status: "failed", error, ...(verdict ? { verdict } : {}) });
|
|
445
450
|
};
|
|
446
|
-
// The authorization server this redemption answers to, fixed now.
|
|
447
|
-
const issuer = provider.refreshIssuer(epoch);
|
|
448
|
-
let lease;
|
|
449
|
-
try {
|
|
450
|
-
const claim = (yield authInvoke(provider, "claimRefresh", [epoch, requested, undefined, waiting]));
|
|
451
|
-
if (claim instanceof Response) {
|
|
452
|
-
const tokens = (yield authInvoke(waiting, "run", [
|
|
453
|
-
authMethod(() => {
|
|
454
|
-
return authInvoke(provider, "storedTokens", [waiting.signal]);
|
|
455
|
-
}),
|
|
456
|
-
]));
|
|
457
|
-
if (tokens)
|
|
458
|
-
settle({ status: "refreshed", tokens });
|
|
459
|
-
else
|
|
460
|
-
settle({ status: "retired" });
|
|
461
|
-
return claim;
|
|
462
|
-
}
|
|
463
|
-
lease = claim;
|
|
464
|
-
}
|
|
465
|
-
catch (error) {
|
|
466
|
-
fail(error, provider.refreshVerdict());
|
|
467
|
-
throw error;
|
|
468
|
-
}
|
|
469
|
-
const releaseLease = authMethod((verdict, resolution = "ambiguous") => {
|
|
470
|
-
return authFlow(function* () {
|
|
471
|
-
(yield authInvoke(provider, "releaseRefresh", [lease, verdict, undefined, resolution]));
|
|
472
|
-
}.bind(this));
|
|
473
|
-
});
|
|
474
|
-
let outcome;
|
|
475
|
-
const deadline = waiting.signal;
|
|
476
451
|
let dispatched = false;
|
|
452
|
+
const unknownOutcome = () => new UnauthorizedError("OAuth refresh outcome is unknown; authorization required.");
|
|
453
|
+
// One budget for the entire commit/cleanup phase, including retries
|
|
454
|
+
// and lease release. Interrupt native continuations, never detach a
|
|
455
|
+
// Promise whose late read could still start a credential write.
|
|
456
|
+
const complete = authMethod((operation) => {
|
|
457
|
+
const completion = refreshWait(undefined, REFRESH_COMMIT_DEADLINE_MS);
|
|
458
|
+
return authInvoke(completion, "run", [operation]).pipe(Effect.catch((error) => Effect.fail(completion.signal.aborted ? unknownOutcome() : error)), Effect.ensuring(Effect.sync(() => completion.close())));
|
|
459
|
+
});
|
|
477
460
|
try {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
input,
|
|
486
|
-
{ ...init, redirect: "manual", signal: deadline },
|
|
487
|
-
]));
|
|
488
|
-
}.bind(this))),
|
|
489
|
-
waiting,
|
|
490
|
-
]));
|
|
491
|
-
if (deadline.aborted) {
|
|
492
|
-
void response.body?.cancel().catch(() => { });
|
|
493
|
-
throw aborted(deadline);
|
|
494
|
-
}
|
|
495
|
-
return (yield authCall(refreshResponseOutcome, [response, deadline]));
|
|
496
|
-
}.bind(this));
|
|
497
|
-
outcome = (yield Effect.raceFirst(read, fromSignal(deadline)));
|
|
498
|
-
if (deadline.aborted)
|
|
499
|
-
throw aborted(deadline);
|
|
500
|
-
}
|
|
501
|
-
catch (error) {
|
|
502
|
-
if (!dispatched) {
|
|
503
|
-
// Only the coordinator's own preparation deadline can interrupt a
|
|
504
|
-
// submitted claim. Caller cancellation no longer reaches this work.
|
|
505
|
-
if (!waiting.signal.aborted) {
|
|
506
|
-
(yield authCatch(authInvoke(waiting, "run", [
|
|
461
|
+
// The authorization server this redemption answers to, fixed now.
|
|
462
|
+
const issuer = provider.refreshIssuer(epoch);
|
|
463
|
+
let lease;
|
|
464
|
+
try {
|
|
465
|
+
const claim = (yield authInvoke(provider, "claimRefresh", [epoch, requested, undefined, waiting]));
|
|
466
|
+
if (claim instanceof Response) {
|
|
467
|
+
const tokens = (yield authInvoke(waiting, "run", [
|
|
507
468
|
authMethod(() => {
|
|
508
|
-
return
|
|
469
|
+
return authInvoke(provider, "storedTokens", [waiting.signal]);
|
|
509
470
|
}),
|
|
510
|
-
])
|
|
511
|
-
if (
|
|
471
|
+
]));
|
|
472
|
+
if (tokens)
|
|
473
|
+
settle({ status: "refreshed", tokens });
|
|
474
|
+
else
|
|
475
|
+
settle({ status: "retired" });
|
|
476
|
+
return claim;
|
|
477
|
+
}
|
|
478
|
+
lease = claim;
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
fail(error, provider.refreshVerdict());
|
|
482
|
+
throw error;
|
|
483
|
+
}
|
|
484
|
+
const releaseLease = authMethod((verdict, resolution = "ambiguous") => {
|
|
485
|
+
return authFlow(function* () {
|
|
486
|
+
(yield authInvoke(provider, "releaseRefresh", [lease, verdict, undefined, resolution]));
|
|
487
|
+
}.bind(this));
|
|
488
|
+
});
|
|
489
|
+
let outcome;
|
|
490
|
+
const deadline = waiting.signal;
|
|
491
|
+
try {
|
|
492
|
+
const read = authFlow(function* () {
|
|
493
|
+
const response = (yield authInvoke(provider, "dispatchRefresh", [
|
|
494
|
+
lease,
|
|
495
|
+
authMethod(() => authFlow(function* () {
|
|
496
|
+
waiting.beginRequest();
|
|
497
|
+
dispatched = true;
|
|
498
|
+
return (yield authCall(baseFetch, [
|
|
499
|
+
input,
|
|
500
|
+
{ ...init, redirect: "manual", signal: deadline },
|
|
501
|
+
]));
|
|
502
|
+
}.bind(this))),
|
|
503
|
+
waiting,
|
|
504
|
+
]));
|
|
505
|
+
if (deadline.aborted) {
|
|
506
|
+
void response.body?.cancel().catch(() => { });
|
|
507
|
+
throw aborted(deadline);
|
|
508
|
+
}
|
|
509
|
+
return (yield authCall(refreshResponseOutcome, [response, deadline]));
|
|
510
|
+
}.bind(this));
|
|
511
|
+
outcome = (yield Effect.raceFirst(read, fromSignal(deadline)));
|
|
512
|
+
if (deadline.aborted)
|
|
513
|
+
throw aborted(deadline);
|
|
514
|
+
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
if (!dispatched) {
|
|
517
|
+
// Only the coordinator's own preparation deadline can interrupt a
|
|
518
|
+
// submitted claim. Caller cancellation no longer reaches this work.
|
|
519
|
+
if (!waiting.signal.aborted) {
|
|
512
520
|
(yield authCatch(authInvoke(waiting, "run", [
|
|
513
521
|
authMethod(() => {
|
|
514
|
-
return
|
|
522
|
+
return authCall(releaseLease, []);
|
|
515
523
|
}),
|
|
516
524
|
]), () => { }));
|
|
525
|
+
if (provider.refreshVerdict()?.kind === "dead") {
|
|
526
|
+
(yield authCatch(authInvoke(waiting, "run", [
|
|
527
|
+
authMethod(() => {
|
|
528
|
+
return authInvoke(provider, "discardRefusedGrant", [requested, epoch]);
|
|
529
|
+
}),
|
|
530
|
+
]), () => { }));
|
|
531
|
+
}
|
|
517
532
|
}
|
|
533
|
+
const failure = waiting.signal.aborted ? provider.refreshContended() : error;
|
|
534
|
+
fail(failure, provider.refreshVerdict());
|
|
535
|
+
throw failure;
|
|
518
536
|
}
|
|
519
|
-
|
|
520
|
-
|
|
537
|
+
// A sent request without a definitive answer may have rotated the token.
|
|
538
|
+
// Persist the refusal before dropping tokens, so interrupted cleanup or a
|
|
539
|
+
// later write of the same fingerprint cannot make it redeemable again.
|
|
540
|
+
const failure = new UnauthorizedError("OAuth refresh outcome is unknown; authorization required.");
|
|
541
|
+
(yield authCall(complete, [
|
|
542
|
+
authMethod(() => authFlow(function* () {
|
|
543
|
+
(yield authCall(releaseLease, [{ kind: "dead" }]));
|
|
544
|
+
(yield authInvoke(provider, "discardRefusedGrant", [requested, epoch]));
|
|
545
|
+
})),
|
|
546
|
+
]));
|
|
547
|
+
fail(failure, { kind: "dead" });
|
|
521
548
|
throw failure;
|
|
522
549
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
550
|
+
finally {
|
|
551
|
+
waiting.close();
|
|
552
|
+
}
|
|
553
|
+
return (yield authCall(complete, [
|
|
554
|
+
authMethod(() => authFlow(function* () {
|
|
555
|
+
// An answer exists from here on, and is committed whatever the owner does.
|
|
556
|
+
if (outcome.failure) {
|
|
557
|
+
// Drop a refused grant while the flight still stands: a caller arriving
|
|
558
|
+
// meanwhile joins it instead of redeeming the dead token again.
|
|
559
|
+
(yield authInvoke(provider, "discardRefusedGrant", [requested, epoch]));
|
|
560
|
+
(yield authCall(releaseLease, [outcome.verdict, "resolved"]));
|
|
561
|
+
fail(outcome.failure, outcome.verdict);
|
|
562
|
+
return outcome.forSdk;
|
|
563
|
+
}
|
|
564
|
+
// The server consumed the old refresh token; the SDK merges it back the
|
|
565
|
+
// same way when a rotation omits one.
|
|
566
|
+
const accepted = {
|
|
567
|
+
...outcome.tokens,
|
|
568
|
+
...(outcome.tokens.refresh_token === undefined && requested !== null
|
|
569
|
+
? { refresh_token: requested }
|
|
570
|
+
: {}),
|
|
571
|
+
};
|
|
572
|
+
let committed;
|
|
573
|
+
try {
|
|
574
|
+
committed = (yield authInvoke(provider, "storeRefresh", [epoch, requested, accepted, issuer]));
|
|
575
|
+
}
|
|
576
|
+
catch {
|
|
577
|
+
const failure = new UnauthorizedError("OAuth refresh could not be committed; authorization required.");
|
|
578
|
+
(yield authCall(releaseLease, [{ kind: "dead" }, "resolved"]));
|
|
579
|
+
(yield authInvoke(provider, "discardRefusedGrant", [requested, epoch]));
|
|
580
|
+
fail(failure, { kind: "dead" });
|
|
581
|
+
throw failure;
|
|
582
|
+
}
|
|
583
|
+
if (!committed) {
|
|
584
|
+
// Restart, Disconnect, or another consent decides the newer grant.
|
|
585
|
+
(yield authCall(releaseLease, [{ kind: "dead" }, "resolved"]));
|
|
586
|
+
const failure = new UnauthorizedError("OAuth refresh grant is no longer active.");
|
|
587
|
+
fail(failure, { kind: "dead" });
|
|
588
|
+
throw failure;
|
|
589
|
+
}
|
|
590
|
+
(yield authInvoke(provider, "releaseRefresh", [lease, undefined, committed, "resolved"]));
|
|
591
|
+
settle({ status: "refreshed", tokens: committed });
|
|
592
|
+
return provider.adoptRefresh(committed);
|
|
593
|
+
})),
|
|
594
|
+
]));
|
|
595
|
+
}
|
|
596
|
+
catch (error) {
|
|
597
|
+
fail(error, dispatched ? { kind: "dead" } : provider.refreshVerdict());
|
|
598
|
+
throw error;
|
|
531
599
|
}
|
|
532
600
|
finally {
|
|
533
601
|
waiting.close();
|
|
602
|
+
// No storage in this finalizer: even interruption or a failed
|
|
603
|
+
// cleanup must drop the local flight and its owner retention.
|
|
604
|
+
if (!settled)
|
|
605
|
+
fail(unknownOutcome(), dispatched ? { kind: "dead" } : provider.refreshVerdict());
|
|
534
606
|
}
|
|
535
|
-
// An answer exists from here on, and is committed whatever the owner does.
|
|
536
|
-
if (outcome.failure) {
|
|
537
|
-
// Drop a refused grant while the flight still stands: a caller arriving
|
|
538
|
-
// meanwhile joins it instead of redeeming the dead token again.
|
|
539
|
-
(yield authInvoke(provider, "discardRefusedGrant", [requested, epoch]));
|
|
540
|
-
(yield authCall(releaseLease, [outcome.verdict, "resolved"]));
|
|
541
|
-
fail(outcome.failure, outcome.verdict);
|
|
542
|
-
return outcome.forSdk;
|
|
543
|
-
}
|
|
544
|
-
// The server consumed the old refresh token; the SDK merges it back the
|
|
545
|
-
// same way when a rotation omits one.
|
|
546
|
-
const accepted = {
|
|
547
|
-
...outcome.tokens,
|
|
548
|
-
...(outcome.tokens.refresh_token === undefined && requested !== null ? { refresh_token: requested } : {}),
|
|
549
|
-
};
|
|
550
|
-
let committed;
|
|
551
|
-
try {
|
|
552
|
-
committed = (yield authInvoke(provider, "storeRefresh", [epoch, requested, accepted, issuer]));
|
|
553
|
-
}
|
|
554
|
-
catch {
|
|
555
|
-
const failure = new UnauthorizedError("OAuth refresh could not be committed; authorization required.");
|
|
556
|
-
(yield authCall(releaseLease, [{ kind: "dead" }, "resolved"]));
|
|
557
|
-
(yield authInvoke(provider, "discardRefusedGrant", [requested, epoch]));
|
|
558
|
-
fail(failure, { kind: "dead" });
|
|
559
|
-
throw failure;
|
|
560
|
-
}
|
|
561
|
-
if (!committed) {
|
|
562
|
-
// Restart, Disconnect, or another consent decides the newer grant.
|
|
563
|
-
(yield authCall(releaseLease, [{ kind: "dead" }, "resolved"]));
|
|
564
|
-
const failure = new UnauthorizedError("OAuth refresh grant is no longer active.");
|
|
565
|
-
fail(failure, { kind: "dead" });
|
|
566
|
-
throw failure;
|
|
567
|
-
}
|
|
568
|
-
(yield authInvoke(provider, "releaseRefresh", [lease, undefined, committed, "resolved"]));
|
|
569
|
-
settle({ status: "refreshed", tokens: committed });
|
|
570
|
-
return provider.adoptRefresh(committed);
|
|
571
607
|
}.bind(this));
|
|
572
608
|
}, OAuthRefreshCoordinator.prototype.redeem);
|
|
573
609
|
}
|
|
@@ -782,7 +818,7 @@ export class KvOAuthProvider {
|
|
|
782
818
|
if (client)
|
|
783
819
|
this.addClientAuthentication = (headers, params) => authenticateRemoteClient(client, headers, params);
|
|
784
820
|
}
|
|
785
|
-
/** Stored facts only: no
|
|
821
|
+
/** Stored facts only: no sealing writes, refresh or discovery. */
|
|
786
822
|
storedAuthorization(issuer) {
|
|
787
823
|
return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "storedAuthorization", [issuer]));
|
|
788
824
|
}
|
|
@@ -798,39 +834,6 @@ export class KvOAuthProvider {
|
|
|
798
834
|
wait(work, signal = this.signal) {
|
|
799
835
|
return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "wait", [work, signal]));
|
|
800
836
|
}
|
|
801
|
-
/** Legacy helpers must fence their inner reads/deletes too, not just our join. */
|
|
802
|
-
preparationStorage(signal) {
|
|
803
|
-
if (!signal)
|
|
804
|
-
return this.storage;
|
|
805
|
-
const storage = this.storage;
|
|
806
|
-
return {
|
|
807
|
-
...storage,
|
|
808
|
-
get: authMethod((key) => {
|
|
809
|
-
return authInvoke(this, "wait", [
|
|
810
|
-
authMethod(() => {
|
|
811
|
-
return authStorage(storage, "get", [key]);
|
|
812
|
-
}),
|
|
813
|
-
signal,
|
|
814
|
-
]);
|
|
815
|
-
}),
|
|
816
|
-
list: authMethod((prefix) => {
|
|
817
|
-
return authInvoke(this, "wait", [
|
|
818
|
-
authMethod(() => {
|
|
819
|
-
return authStorage(storage, "list", [prefix]);
|
|
820
|
-
}),
|
|
821
|
-
signal,
|
|
822
|
-
]);
|
|
823
|
-
}),
|
|
824
|
-
delete: authMethod((key) => {
|
|
825
|
-
return authInvoke(this, "wait", [
|
|
826
|
-
authMethod(() => {
|
|
827
|
-
return authStorage(storage, "delete", [key]);
|
|
828
|
-
}),
|
|
829
|
-
signal,
|
|
830
|
-
]);
|
|
831
|
-
}),
|
|
832
|
-
};
|
|
833
|
-
}
|
|
834
837
|
// --- the grant record ---------------------------------------------------
|
|
835
838
|
/** The grant's additional authenticated data: record and epoch. */
|
|
836
839
|
grantLabel(epoch) {
|
|
@@ -848,21 +851,13 @@ export class KvOAuthProvider {
|
|
|
848
851
|
return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "openGrant", [raw, signal]));
|
|
849
852
|
}
|
|
850
853
|
/**
|
|
851
|
-
* The grant
|
|
854
|
+
* The current grant record. A plaintext
|
|
852
855
|
* body, written before the vault could seal, is sealed where it lies by
|
|
853
856
|
* compare-and-set; a failure leaves it readable for the next write to seal.
|
|
854
857
|
*/
|
|
855
858
|
readGrant(signal = this.signal) {
|
|
856
859
|
return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "readGrant", [signal]));
|
|
857
860
|
}
|
|
858
|
-
/** Layout 2 migration and its cleanup retain the reading operation's owner. */
|
|
859
|
-
migrate(signal) {
|
|
860
|
-
return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "migrate", [signal]));
|
|
861
|
-
}
|
|
862
|
-
/** Deletion is retried on every read, including reads after a reset. */
|
|
863
|
-
cleanupV2(raw, signal = this.signal) {
|
|
864
|
-
return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "cleanupV2", [raw, signal]));
|
|
865
|
-
}
|
|
866
861
|
/** The grant a bound flow reads: a replaced epoch fails it. */
|
|
867
862
|
boundGrant(signal = this.signal) {
|
|
868
863
|
return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "boundGrant", [signal]));
|
|
@@ -892,7 +887,6 @@ export class KvOAuthProvider {
|
|
|
892
887
|
throw this.flowSuperseded();
|
|
893
888
|
return {
|
|
894
889
|
epoch: `${ACTIVE_EPOCH_PREFIX}${crypto.randomUUID()}`,
|
|
895
|
-
...(grant.cleanupPending ? { cleanupPending: true } : {}),
|
|
896
890
|
body,
|
|
897
891
|
};
|
|
898
892
|
}
|
|
@@ -948,9 +942,10 @@ export class KvOAuthProvider {
|
|
|
948
942
|
* Begin a flow — a connect, whose 401 may refresh or start consent, or an
|
|
949
943
|
* `api()` call or start — in the live epoch, and return it. Nothing about
|
|
950
944
|
* the grant is decided here: a grant belongs to one server by construction.
|
|
945
|
+
* An expected epoch continues an existing attempt; a replacement fails it.
|
|
951
946
|
*/
|
|
952
|
-
beginFlow() {
|
|
953
|
-
return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "beginFlow", []));
|
|
947
|
+
beginFlow(expectedEpoch) {
|
|
948
|
+
return runEdge(authOwnMethod(KvOAuthProvider.prototype, this, "beginFlow", [expectedEpoch]));
|
|
954
949
|
}
|
|
955
950
|
/** The epoch this flow is bound to, or the live one. */
|
|
956
951
|
flowEpoch() {
|
|
@@ -1373,7 +1368,6 @@ export class KvOAuthProvider {
|
|
|
1373
1368
|
const stored = {
|
|
1374
1369
|
connectaOAuth: GRANT_VERSION,
|
|
1375
1370
|
epoch: grant.epoch,
|
|
1376
|
-
...(grant.cleanupPending ? { cleanupPending: true } : {}),
|
|
1377
1371
|
...(grant.flow !== undefined ? { flow: grant.flow } : {}),
|
|
1378
1372
|
};
|
|
1379
1373
|
if (Object.keys(grant.body).length > 0) {
|
|
@@ -1429,7 +1423,6 @@ export class KvOAuthProvider {
|
|
|
1429
1423
|
}
|
|
1430
1424
|
return {
|
|
1431
1425
|
epoch: stored.epoch,
|
|
1432
|
-
...(stored.cleanupPending ? { cleanupPending: true } : {}),
|
|
1433
1426
|
...(stored.flow !== undefined ? { flow: stored.flow } : {}),
|
|
1434
1427
|
body: grantBody(body),
|
|
1435
1428
|
};
|
|
@@ -1460,10 +1453,17 @@ export class KvOAuthProvider {
|
|
|
1460
1453
|
}),
|
|
1461
1454
|
signal,
|
|
1462
1455
|
]));
|
|
1463
|
-
if (raw === null)
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1456
|
+
if (raw === null) {
|
|
1457
|
+
// Inspect names only: retired credentials are never opened or copied.
|
|
1458
|
+
const keys = (yield authInvoke(this, "wait", [
|
|
1459
|
+
authMethod(() => authStorage(this.storage, "list", [retiredOAuthKeys.scan])),
|
|
1460
|
+
signal,
|
|
1461
|
+
]));
|
|
1462
|
+
if (keys.some((key) => retiredOAuthKeys.prefixes.some((prefix) => key.startsWith(prefix))))
|
|
1463
|
+
(this.sealer ?? console).warn(`[connecta] connector "${this.connectorId}" has an unsupported pre-layout-3 OAuth grant; ` +
|
|
1464
|
+
"treating it as absent. Connect again to authorize, or restore a backup and run the " +
|
|
1465
|
+
"OAuth migration on Connecta 0.37.1 before upgrading.");
|
|
1466
|
+
}
|
|
1467
1467
|
const grant = (yield authInvoke(this, "openGrant", [raw, signal]));
|
|
1468
1468
|
if (raw !== null && this.sealer && storedGrant(raw)?.body !== undefined) {
|
|
1469
1469
|
const sealed = (yield authInvoke(this, "encodeGrant", [grant, signal]));
|
|
@@ -1478,99 +1478,6 @@ export class KvOAuthProvider {
|
|
|
1478
1478
|
return { raw, grant };
|
|
1479
1479
|
}.bind(this));
|
|
1480
1480
|
}, KvOAuthProvider.prototype.readGrant);
|
|
1481
|
-
registerAuthMethod(KvOAuthProvider.prototype, "migrate", function (signal) {
|
|
1482
|
-
return authFlow(function* () {
|
|
1483
|
-
const sealer = this.sealer;
|
|
1484
|
-
const found = (yield authInvoke(this, "wait", [
|
|
1485
|
-
authMethod(() => {
|
|
1486
|
-
return authCall(readV2Grant, [
|
|
1487
|
-
this.preparationStorage(signal),
|
|
1488
|
-
sealer && {
|
|
1489
|
-
...sealer,
|
|
1490
|
-
open: authMethod((label, value) => {
|
|
1491
|
-
return authInvoke(this, "wait", [
|
|
1492
|
-
authMethod(() => {
|
|
1493
|
-
return authInvoke(sealer, "open", [label, value]);
|
|
1494
|
-
}),
|
|
1495
|
-
signal,
|
|
1496
|
-
]);
|
|
1497
|
-
}),
|
|
1498
|
-
},
|
|
1499
|
-
]);
|
|
1500
|
-
}),
|
|
1501
|
-
signal,
|
|
1502
|
-
]));
|
|
1503
|
-
if (!found)
|
|
1504
|
-
return null;
|
|
1505
|
-
const prefix = found.disconnected ? DISCONNECTED_EPOCH_PREFIX : ACTIVE_EPOCH_PREFIX;
|
|
1506
|
-
const encoded = (yield authInvoke(this, "encodeGrant", [
|
|
1507
|
-
{
|
|
1508
|
-
epoch: `${prefix}${crypto.randomUUID()}`,
|
|
1509
|
-
body: found.body,
|
|
1510
|
-
cleanupPending: true,
|
|
1511
|
-
},
|
|
1512
|
-
signal,
|
|
1513
|
-
]));
|
|
1514
|
-
let written;
|
|
1515
|
-
try {
|
|
1516
|
-
written = (yield authInvoke(this, "wait", [
|
|
1517
|
-
authMethod(() => {
|
|
1518
|
-
return authStorage(this.storage, "compareAndSet", [GRANT, null, encoded]);
|
|
1519
|
-
}),
|
|
1520
|
-
signal,
|
|
1521
|
-
]));
|
|
1522
|
-
}
|
|
1523
|
-
catch {
|
|
1524
|
-
if (signal?.aborted)
|
|
1525
|
-
throw aborted(signal);
|
|
1526
|
-
throw this.credentialWriteError();
|
|
1527
|
-
}
|
|
1528
|
-
return (yield written
|
|
1529
|
-
? authValue(() => encoded)
|
|
1530
|
-
: authInvoke(this, "wait", [
|
|
1531
|
-
authMethod(() => {
|
|
1532
|
-
return authStorage(this.storage, "get", [GRANT]);
|
|
1533
|
-
}),
|
|
1534
|
-
signal,
|
|
1535
|
-
]));
|
|
1536
|
-
}.bind(this));
|
|
1537
|
-
}, KvOAuthProvider.prototype.migrate);
|
|
1538
|
-
registerAuthMethod(KvOAuthProvider.prototype, "cleanupV2", function (raw, signal) {
|
|
1539
|
-
signal = signal === undefined ? this.signal : signal;
|
|
1540
|
-
return authFlow(function* () {
|
|
1541
|
-
try {
|
|
1542
|
-
if (!(yield authInvoke(this, "wait", [
|
|
1543
|
-
authMethod(() => {
|
|
1544
|
-
return authCall(deleteV2Keys, [this.preparationStorage(signal)]);
|
|
1545
|
-
}),
|
|
1546
|
-
signal,
|
|
1547
|
-
])))
|
|
1548
|
-
return raw;
|
|
1549
|
-
const grant = (yield authInvoke(this, "openGrant", [raw, signal]));
|
|
1550
|
-
delete grant.cleanupPending;
|
|
1551
|
-
const cleared = (yield authInvoke(this, "encodeGrant", [grant, signal]));
|
|
1552
|
-
if ((yield authInvoke(this, "wait", [
|
|
1553
|
-
authMethod(() => {
|
|
1554
|
-
return authStorage(this.storage, "compareAndSet", [GRANT, raw, cleared]);
|
|
1555
|
-
}),
|
|
1556
|
-
signal,
|
|
1557
|
-
])))
|
|
1558
|
-
return cleared;
|
|
1559
|
-
return (yield authInvoke(this, "wait", [
|
|
1560
|
-
authMethod(() => {
|
|
1561
|
-
return authStorage(this.storage, "get", [GRANT]);
|
|
1562
|
-
}),
|
|
1563
|
-
signal,
|
|
1564
|
-
]));
|
|
1565
|
-
}
|
|
1566
|
-
catch {
|
|
1567
|
-
if (signal?.aborted)
|
|
1568
|
-
throw aborted(signal);
|
|
1569
|
-
// The durable marker survives both deletion and marker-clear failures.
|
|
1570
|
-
return raw;
|
|
1571
|
-
}
|
|
1572
|
-
}.bind(this));
|
|
1573
|
-
}, KvOAuthProvider.prototype.cleanupV2);
|
|
1574
1481
|
registerAuthMethod(KvOAuthProvider.prototype, "boundGrant", function (signal) {
|
|
1575
1482
|
signal = signal === undefined ? this.signal : signal;
|
|
1576
1483
|
return authFlow(function* () {
|
|
@@ -1770,9 +1677,11 @@ export class KvOAuthProvider {
|
|
|
1770
1677
|
return isDisconnectedEpoch((yield authInvoke(this, "liveEpoch", [])));
|
|
1771
1678
|
}.bind(this));
|
|
1772
1679
|
}, KvOAuthProvider.prototype.operatorDisconnected);
|
|
1773
|
-
registerAuthMethod(KvOAuthProvider.prototype, "beginFlow", function () {
|
|
1680
|
+
registerAuthMethod(KvOAuthProvider.prototype, "beginFlow", function (expectedEpoch) {
|
|
1774
1681
|
return authFlow(function* () {
|
|
1775
1682
|
const epoch = (yield authInvoke(this, "liveEpoch", []));
|
|
1683
|
+
if (expectedEpoch !== undefined && epoch !== expectedEpoch)
|
|
1684
|
+
throw this.flowSuperseded();
|
|
1776
1685
|
this.epoch = epoch;
|
|
1777
1686
|
this.bound = true;
|
|
1778
1687
|
return epoch;
|
|
@@ -1893,9 +1802,29 @@ export class KvOAuthProvider {
|
|
|
1893
1802
|
const { body } = (yield authInvoke(this, "activeGrant", []));
|
|
1894
1803
|
if (this.signal?.aborted)
|
|
1895
1804
|
throw aborted(this.signal);
|
|
1896
|
-
|
|
1805
|
+
let state = body.discovery;
|
|
1806
|
+
if (state?.resourceMetadataUrl && this.clientOptions?.resourceOrigin) {
|
|
1807
|
+
let sameOrigin = false;
|
|
1808
|
+
try {
|
|
1809
|
+
sameOrigin = new URL(state.resourceMetadataUrl).origin === this.clientOptions.resourceOrigin;
|
|
1810
|
+
}
|
|
1811
|
+
catch {
|
|
1812
|
+
// A malformed legacy URL is no more usable than a foreign one.
|
|
1813
|
+
}
|
|
1814
|
+
if (!sameOrigin) {
|
|
1815
|
+
// Never hand the SDK a legacy foreign URL or its resource
|
|
1816
|
+
// document. Keep the issuer binding for callbacks, but force
|
|
1817
|
+
// same-origin resource discovery on every SDK path. This read
|
|
1818
|
+
// does not mutate the stored grant.
|
|
1819
|
+
state = { ...state };
|
|
1820
|
+
delete state.resourceMetadataUrl;
|
|
1821
|
+
delete state.resourceMetadata;
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1897
1824
|
this.assertCredentialFree(state, body);
|
|
1898
|
-
|
|
1825
|
+
// Bind consent to durable discovery until the SDK saves a new
|
|
1826
|
+
// state. A same-origin metadata 404 can leave the cache unchanged.
|
|
1827
|
+
this.consentDiscovery = body.discovery;
|
|
1899
1828
|
return state;
|
|
1900
1829
|
}.bind(this));
|
|
1901
1830
|
}, KvOAuthProvider.prototype.discoveryState);
|
|
@@ -2704,10 +2633,7 @@ export class KvOAuthProvider {
|
|
|
2704
2633
|
for (let attempt = 0; attempt < MAX_GRANT_WRITES; attempt++) {
|
|
2705
2634
|
const { raw, grant } = (yield authInvoke(this, "readGrant", [null]));
|
|
2706
2635
|
const carried = preserveClient && !operatorDisconnected ? this.carriableClient(grant) : undefined;
|
|
2707
|
-
const encoded = (yield authInvoke(this, "encodeGrant", [
|
|
2708
|
-
{ epoch, body: carried ?? {}, cleanupPending: true },
|
|
2709
|
-
null,
|
|
2710
|
-
]));
|
|
2636
|
+
const encoded = (yield authInvoke(this, "encodeGrant", [{ epoch, body: carried ?? {} }, null]));
|
|
2711
2637
|
let written;
|
|
2712
2638
|
try {
|
|
2713
2639
|
written = (yield authStorage(this.storage, "compareAndSet", [GRANT, raw, encoded]));
|
|
@@ -2718,7 +2644,6 @@ export class KvOAuthProvider {
|
|
|
2718
2644
|
if (written) {
|
|
2719
2645
|
this.refreshCoordinator?.retire(grant.epoch);
|
|
2720
2646
|
try {
|
|
2721
|
-
(yield authInvoke(this, "cleanupV2", [encoded, null]));
|
|
2722
2647
|
(yield authInvoke(this, "sweepConsents", []));
|
|
2723
2648
|
(yield authInvoke(this, "sweepRefreshLeases", []));
|
|
2724
2649
|
}
|
|
@@ -4,7 +4,7 @@ import { fromSignal, runEdge } from "../runtime/run.js";
|
|
|
4
4
|
import { classifyHost } from "../url-safety.js";
|
|
5
5
|
import { authenticateRemoteClient, remoteClientAuthMethod, } from "./downstream-client-metadata.js";
|
|
6
6
|
import { authCall, authFlow, authProgram, registerAuth } from "./native.js";
|
|
7
|
-
import { discoveryIssuer } from "./oauth-
|
|
7
|
+
import { discoveryIssuer } from "./oauth-grant.js";
|
|
8
8
|
/** Local removal succeeded; provider-side removal needs the operator's attention. */
|
|
9
9
|
export class OAuthRevocationError extends ConnectorCallError {
|
|
10
10
|
constructor() {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
function plainObject(value) {
|
|
2
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
/** The issuer a discovery record names, as the SDK derives it. */
|
|
5
|
+
export function discoveryIssuer(state) {
|
|
6
|
+
if (!plainObject(state) || typeof state.authorizationServerUrl !== "string")
|
|
7
|
+
return undefined;
|
|
8
|
+
const metadata = state.authorizationServerMetadata;
|
|
9
|
+
const issuer = plainObject(metadata) && typeof metadata.issuer === "string" ? metadata.issuer : state.authorizationServerUrl;
|
|
10
|
+
return issuer === "" ? undefined : issuer;
|
|
11
|
+
}
|
|
@@ -3,7 +3,7 @@ import { authCall, authFlow, authInvoke, authProgram, registerAuth } from "./nat
|
|
|
3
3
|
// The token endpoint's answers as downstream OAuth classifies them and the SDK
|
|
4
4
|
// gets to parse them: a refresh's re-consent verdict, and every failure
|
|
5
5
|
// rebuilt from its OAuth `error` code alone, so nothing else a token endpoint
|
|
6
|
-
// wrote reaches the SDK's console output
|
|
6
|
+
// wrote reaches the SDK's console output.
|
|
7
7
|
/** The `grant_type` of a token request, or undefined for any other request. */
|
|
8
8
|
export function tokenGrantType(init) {
|
|
9
9
|
if ((init?.method ?? "GET").toUpperCase() !== "POST")
|