@astrale-os/cli 1.0.0-beta.31 → 1.0.0-beta.32
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/dist/astrale.js +128 -43
- package/dist/public/connect-core.js +42 -13
- package/dist/public/keys/index.js +14 -0
- package/dist/public/paths/index.js +14 -0
- package/dist/types/connection/auth.d.ts +1 -0
- package/dist/types/connection/credential.d.ts +1 -1
- package/dist/types/connection/lifetime.d.ts +6 -0
- package/dist/types/lib/credential-lifetime.d.ts +3 -0
- package/dist/types/lib/idp-session.d.ts +2 -0
- package/dist/types/lib/idp.d.ts +1 -1
- package/dist/types/state/exchange-credentials.d.ts +1 -1
- package/package.json +1 -1
- package/src/connection/.spec/architecture.md +5 -2
- package/src/connection/.spec/flows/session.ts +2 -1
- package/src/connection/.spec/laws/connection.ts +9 -1
- package/src/connection/.spec/layout.ts +1 -0
- package/src/connection/__tests__/credential.test.ts +115 -0
- package/src/connection/__tests__/exchange.test.ts +94 -9
- package/src/connection/auth.ts +20 -4
- package/src/connection/credential.ts +29 -8
- package/src/connection/exchange.ts +65 -7
- package/src/connection/lifetime.ts +25 -0
- package/src/lib/__tests__/idp-session.driver.ts +10 -3
- package/src/lib/__tests__/idp-session.test.ts +25 -0
- package/src/lib/credential-lifetime.ts +24 -0
- package/src/lib/idp-session.ts +26 -4
- package/src/lib/idp.ts +24 -4
- package/src/state/__tests__/exchange-credentials.test.ts +112 -5
- package/src/state/exchange-credentials.ts +26 -8
package/dist/astrale.js
CHANGED
|
@@ -2578,7 +2578,7 @@ var package_default;
|
|
|
2578
2578
|
var init_package = __esm(() => {
|
|
2579
2579
|
package_default = {
|
|
2580
2580
|
name: "@astrale-os/cli",
|
|
2581
|
-
version: "1.0.0-beta.
|
|
2581
|
+
version: "1.0.0-beta.32",
|
|
2582
2582
|
description: "Astrale CLI — connect to existing Astrale kernels",
|
|
2583
2583
|
keywords: [
|
|
2584
2584
|
"astrale",
|
|
@@ -34087,6 +34087,20 @@ var init_auth4 = __esm(() => {
|
|
|
34087
34087
|
init_auth();
|
|
34088
34088
|
});
|
|
34089
34089
|
|
|
34090
|
+
// src/lib/credential-lifetime.ts
|
|
34091
|
+
function remainingCredentialLifetimeSeconds(expiresAtEpochSeconds, nowEpochSeconds = Math.ceil(Date.now() / 1000)) {
|
|
34092
|
+
if (!Number.isSafeInteger(expiresAtEpochSeconds) || !Number.isSafeInteger(nowEpochSeconds)) {
|
|
34093
|
+
throw new TypeError("Credential expiration and current time must be safe epoch seconds.");
|
|
34094
|
+
}
|
|
34095
|
+
return expiresAtEpochSeconds - nowEpochSeconds - 1;
|
|
34096
|
+
}
|
|
34097
|
+
function credentialLifetimeCovers(expiresAtEpochSeconds, minimumRemainingSeconds, nowEpochSeconds) {
|
|
34098
|
+
if (!Number.isSafeInteger(minimumRemainingSeconds) || minimumRemainingSeconds < 1) {
|
|
34099
|
+
throw new TypeError("Credential minimum lifetime must be a positive safe integer.");
|
|
34100
|
+
}
|
|
34101
|
+
return remainingCredentialLifetimeSeconds(expiresAtEpochSeconds, nowEpochSeconds) >= minimumRemainingSeconds;
|
|
34102
|
+
}
|
|
34103
|
+
|
|
34090
34104
|
// src/state/paths.ts
|
|
34091
34105
|
import { homedir } from "node:os";
|
|
34092
34106
|
import { join } from "node:path";
|
|
@@ -34141,15 +34155,19 @@ class ExchangeCredentialCache {
|
|
|
34141
34155
|
constructor(path = EXCHANGE_CREDENTIALS_PATH) {
|
|
34142
34156
|
this.path = path;
|
|
34143
34157
|
}
|
|
34144
|
-
getOrRefresh(key, refresh, now = () => Math.floor(Date.now() / 1000)) {
|
|
34158
|
+
getOrRefresh(key, minimumRemainingSeconds, refresh, now = () => Math.floor(Date.now() / 1000)) {
|
|
34159
|
+
if (!Number.isSafeInteger(minimumRemainingSeconds) || minimumRemainingSeconds < 1) {
|
|
34160
|
+
throw new TypeError("Exchange credential minimum lifetime must be a positive safe integer.");
|
|
34161
|
+
}
|
|
34145
34162
|
const encoded = encodeKey(key);
|
|
34146
|
-
const
|
|
34163
|
+
const pendingKey = `${encoded}\x00${minimumRemainingSeconds}`;
|
|
34164
|
+
const current = this.refreshing.get(pendingKey);
|
|
34147
34165
|
if (current !== undefined)
|
|
34148
34166
|
return current;
|
|
34149
|
-
const pending = this.getOrRefreshOnce(key, encoded, refresh, now).finally(() => {
|
|
34150
|
-
this.refreshing.delete(
|
|
34167
|
+
const pending = this.getOrRefreshOnce(key, encoded, minimumRemainingSeconds, refresh, now).finally(() => {
|
|
34168
|
+
this.refreshing.delete(pendingKey);
|
|
34151
34169
|
});
|
|
34152
|
-
this.refreshing.set(
|
|
34170
|
+
this.refreshing.set(pendingKey, pending);
|
|
34153
34171
|
return pending;
|
|
34154
34172
|
}
|
|
34155
34173
|
async deleteKernel(kernelIssuer) {
|
|
@@ -34167,20 +34185,20 @@ class ExchangeCredentialCache {
|
|
|
34167
34185
|
delete store.entries[encoded];
|
|
34168
34186
|
});
|
|
34169
34187
|
}
|
|
34170
|
-
async getOrRefreshOnce(key, encoded, refresh, now) {
|
|
34188
|
+
async getOrRefreshOnce(key, encoded, minimumRemainingSeconds, refresh, now) {
|
|
34171
34189
|
return withFileLock(`${this.path}.lock`, async () => {
|
|
34172
34190
|
await ensurePrivateDirectory(this.path);
|
|
34173
34191
|
const store = await readStore(this.path);
|
|
34174
34192
|
const changed = scrub(store, now());
|
|
34175
34193
|
const cached3 = store.entries[encoded];
|
|
34176
|
-
if (cached3 !== undefined && validEntry(key, cached3, now())) {
|
|
34194
|
+
if (cached3 !== undefined && validEntry(key, cached3, now(), minimumRemainingSeconds)) {
|
|
34177
34195
|
if (changed)
|
|
34178
34196
|
await writeStore(this.path, store);
|
|
34179
34197
|
return cached3.credential;
|
|
34180
34198
|
}
|
|
34181
34199
|
const next = await refresh();
|
|
34182
|
-
if (!validEntry(key, next, now(),
|
|
34183
|
-
throw new Error("Token exchange returned a credential inconsistent with its cache key.");
|
|
34200
|
+
if (!validEntry(key, next, now(), minimumRemainingSeconds)) {
|
|
34201
|
+
throw new Error("Token exchange returned a credential inconsistent with its cache key or required lifetime.");
|
|
34184
34202
|
}
|
|
34185
34203
|
store.entries[encoded] = Object.freeze({ ...next });
|
|
34186
34204
|
await writeStore(this.path, store);
|
|
@@ -34239,7 +34257,7 @@ function validEntry(key, entry, now, minimumRemaining = MINIMUM_REMAINING_SECOND
|
|
|
34239
34257
|
return false;
|
|
34240
34258
|
}
|
|
34241
34259
|
exports_grant.accept({ expr: Reflect.get(issued, "expr") });
|
|
34242
|
-
return inspected.iss === key.domainIssuer && inspected.aud === key.kernelIssuer && inspected.claims.exp === entry.expiresAt && !Object.hasOwn(inspected.claims, "delegation") && proof.iss === key.kernelIssuer && proof.sub === entry.user && proof.aud === key.kernelIssuer;
|
|
34260
|
+
return inspected.iss === key.domainIssuer && inspected.aud === key.kernelIssuer && inspected.claims.exp === entry.expiresAt && !Object.hasOwn(inspected.claims, "delegation") && proof.iss === key.kernelIssuer && proof.sub === entry.user && proof.aud === key.kernelIssuer && typeof proof.claims.exp === "number" && Number.isSafeInteger(proof.claims.exp) && credentialLifetimeCovers(proof.claims.exp, minimumRemaining, now);
|
|
34243
34261
|
} catch {
|
|
34244
34262
|
return false;
|
|
34245
34263
|
}
|
|
@@ -38190,18 +38208,25 @@ function isSessionExpired(session, skewMs = 60000) {
|
|
|
38190
38208
|
return false;
|
|
38191
38209
|
return new Date(expiresAt).getTime() <= Date.now() + skewMs;
|
|
38192
38210
|
}
|
|
38193
|
-
function accessTokenForAudience(session, audience) {
|
|
38211
|
+
function accessTokenForAudience(session, audience, minimumRemainingMs = 60000) {
|
|
38194
38212
|
if (audience === undefined) {
|
|
38195
|
-
return
|
|
38213
|
+
return tokenHasMinimumLifetime(session, minimumRemainingMs) ? session.access_token : undefined;
|
|
38196
38214
|
}
|
|
38197
38215
|
const entry = session.tokens?.[audience];
|
|
38198
|
-
if (entry &&
|
|
38216
|
+
if (entry && tokenHasMinimumLifetime(entry, minimumRemainingMs))
|
|
38199
38217
|
return entry.access_token;
|
|
38200
|
-
if (
|
|
38218
|
+
if (tokenHasMinimumLifetime(session, minimumRemainingMs) && tokenAudienceMatches(session.access_token, audience)) {
|
|
38201
38219
|
return session.access_token;
|
|
38202
38220
|
}
|
|
38203
38221
|
return;
|
|
38204
38222
|
}
|
|
38223
|
+
function tokenHasMinimumLifetime(value2, minimumRemainingMs) {
|
|
38224
|
+
const expiration = decodeTokenClaims(value2.access_token)?.exp;
|
|
38225
|
+
if (typeof expiration === "number" && Number.isSafeInteger(expiration)) {
|
|
38226
|
+
return credentialLifetimeCovers(expiration, Math.ceil(minimumRemainingMs / 1000));
|
|
38227
|
+
}
|
|
38228
|
+
return !isSessionExpired(value2, minimumRemainingMs);
|
|
38229
|
+
}
|
|
38205
38230
|
function withCachedToken(tokens, accessToken, expiresAt) {
|
|
38206
38231
|
const next = {};
|
|
38207
38232
|
for (const [aud2, entry] of Object.entries(tokens ?? {})) {
|
|
@@ -80780,10 +80805,11 @@ function idpSessionLockPath(identityName) {
|
|
|
80780
80805
|
return `${idpSessionPath(identityName)}.lock`;
|
|
80781
80806
|
}
|
|
80782
80807
|
async function ensureFreshSession(identityName, opts = {}) {
|
|
80808
|
+
const minimumRemainingMs = minimumLifetimeMs(opts.minimumRemainingSeconds);
|
|
80783
80809
|
const session3 = await readIdpSession(identityName);
|
|
80784
80810
|
if (!session3)
|
|
80785
80811
|
throw new IdpSessionMissingError(identityName);
|
|
80786
|
-
if (accessTokenForAudience(session3, opts.audience))
|
|
80812
|
+
if (accessTokenForAudience(session3, opts.audience, minimumRemainingMs))
|
|
80787
80813
|
return session3;
|
|
80788
80814
|
if (!session3.refresh_token)
|
|
80789
80815
|
throw new IdpSessionNoRefreshTokenError(identityName);
|
|
@@ -80791,7 +80817,7 @@ async function ensureFreshSession(identityName, opts = {}) {
|
|
|
80791
80817
|
const current = await readIdpSession(identityName);
|
|
80792
80818
|
if (!current)
|
|
80793
80819
|
throw new IdpSessionMissingError(identityName);
|
|
80794
|
-
if (accessTokenForAudience(current, opts.audience))
|
|
80820
|
+
if (accessTokenForAudience(current, opts.audience, minimumRemainingMs))
|
|
80795
80821
|
return current;
|
|
80796
80822
|
const bookmarkOrg = opts.organizationId ?? (opts.audience ? await orgIdForAudience(opts.audience) : undefined);
|
|
80797
80823
|
const organizationId = bookmarkOrg ?? (opts.audience ? await (opts.resolveOrganizationId ?? fetchOrgHint)(opts.audience) : undefined);
|
|
@@ -80810,23 +80836,30 @@ async function ensureFreshSession(identityName, opts = {}) {
|
|
|
80810
80836
|
});
|
|
80811
80837
|
}
|
|
80812
80838
|
}
|
|
80813
|
-
const rescued = await rescueAfterInvalidGrant(identityName, current, opts.audience, e);
|
|
80839
|
+
const rescued = await rescueAfterInvalidGrant(identityName, current, opts.audience, minimumRemainingMs, e);
|
|
80814
80840
|
if (rescued)
|
|
80815
80841
|
return rescued;
|
|
80816
80842
|
throw e;
|
|
80817
80843
|
}
|
|
80818
80844
|
});
|
|
80819
80845
|
}
|
|
80820
|
-
async function rescueAfterInvalidGrant(identityName, seen, audience, error52) {
|
|
80846
|
+
async function rescueAfterInvalidGrant(identityName, seen, audience, minimumRemainingMs, error52) {
|
|
80821
80847
|
if (!(error52 instanceof OAuthTokenError) || error52.code !== "invalid_grant")
|
|
80822
80848
|
return;
|
|
80823
80849
|
const latest = await readIdpSession(identityName).catch(() => null);
|
|
80824
80850
|
if (!latest || latest.updatedAt === seen.updatedAt)
|
|
80825
80851
|
return;
|
|
80826
|
-
if (!accessTokenForAudience(latest, audience))
|
|
80852
|
+
if (!accessTokenForAudience(latest, audience, minimumRemainingMs))
|
|
80827
80853
|
return;
|
|
80828
80854
|
return latest;
|
|
80829
80855
|
}
|
|
80856
|
+
function minimumLifetimeMs(input) {
|
|
80857
|
+
const seconds = input ?? 60;
|
|
80858
|
+
if (!Number.isSafeInteger(seconds) || seconds < 1 || seconds > Math.floor(Number.MAX_SAFE_INTEGER / 1000)) {
|
|
80859
|
+
throw new TypeError("IdP session minimum lifetime must be a positive safe integer.");
|
|
80860
|
+
}
|
|
80861
|
+
return seconds * 1000;
|
|
80862
|
+
}
|
|
80830
80863
|
var IdpSessionMissingError, IdpSessionNoRefreshTokenError;
|
|
80831
80864
|
var init_idp_session = __esm(() => {
|
|
80832
80865
|
init_state();
|
|
@@ -80871,14 +80904,14 @@ async function resolveCredential2(opts, config2, audience = config2.issuer, regi
|
|
|
80871
80904
|
resolvedIdentity = identity4;
|
|
80872
80905
|
resolvedName = identityName;
|
|
80873
80906
|
if ((identity4.source ?? "key") === "idp")
|
|
80874
|
-
return await resolveIdpAccessToken(identityName, identity4, audience);
|
|
80907
|
+
return await resolveIdpAccessToken(identityName, identity4, audience, opts.minimumRemainingSeconds);
|
|
80875
80908
|
return await signAs(identity4.subject, KEYS_DIR, resolveKeyIdentityAuthOptions(identity4, config2, audience, registrationKey));
|
|
80876
80909
|
}
|
|
80877
80910
|
const identity3 = await getDefault();
|
|
80878
80911
|
resolvedIdentity = identity3;
|
|
80879
80912
|
resolvedName = identity3.name;
|
|
80880
80913
|
if ((identity3.source ?? "key") === "idp") {
|
|
80881
|
-
return await resolveIdpAccessToken(identity3.name, identity3, audience);
|
|
80914
|
+
return await resolveIdpAccessToken(identity3.name, identity3, audience, opts.minimumRemainingSeconds);
|
|
80882
80915
|
}
|
|
80883
80916
|
return await signAs(identity3.subject, KEYS_DIR, resolveKeyIdentityAuthOptions(identity3, config2, audience, registrationKey));
|
|
80884
80917
|
} catch (e) {
|
|
@@ -80920,10 +80953,10 @@ function resolveKeyIdentityAuthOptions(identity3, config2, audience = config2.is
|
|
|
80920
80953
|
function systemIdentityIssuer(identity3, audience, config2) {
|
|
80921
80954
|
return identity3.subject === "system" ? audience : config2.issuer;
|
|
80922
80955
|
}
|
|
80923
|
-
async function resolveIdpAccessToken(identityName, identity3, audience) {
|
|
80956
|
+
async function resolveIdpAccessToken(identityName, identity3, audience, minimumRemainingSeconds) {
|
|
80924
80957
|
let resolved;
|
|
80925
80958
|
try {
|
|
80926
|
-
resolved = await ensureFreshSession(identityName, { audience });
|
|
80959
|
+
resolved = await ensureFreshSession(identityName, { audience, minimumRemainingSeconds });
|
|
80927
80960
|
} catch (e) {
|
|
80928
80961
|
if (e instanceof IdpSessionMissingError) {
|
|
80929
80962
|
throw new Error(`No cached IdP session for "${identityName}". Run: astrale auth login --idp ${identity3.idp ?? "<idp>"}`);
|
|
@@ -80978,9 +81011,26 @@ var init_auth7 = __esm(() => {
|
|
|
80978
81011
|
};
|
|
80979
81012
|
});
|
|
80980
81013
|
|
|
81014
|
+
// src/connection/lifetime.ts
|
|
81015
|
+
function invocationCredentialTtlSeconds(timeoutMs) {
|
|
81016
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1) {
|
|
81017
|
+
throw new TypeError("Invocation credential timeout must be a positive safe integer.");
|
|
81018
|
+
}
|
|
81019
|
+
return Math.max(MINIMUM_CREDENTIAL_TTL_SECONDS, Math.ceil(timeoutMs / 1000) + INVOCATION_RECEIPT_MARGIN_SECONDS);
|
|
81020
|
+
}
|
|
81021
|
+
function exchangeCredentialTtlSeconds(timeoutMs) {
|
|
81022
|
+
return invocationCredentialTtlSeconds(timeoutMs) + TOKEN_EXCHANGE_SETTLEMENT_MARGIN_SECONDS;
|
|
81023
|
+
}
|
|
81024
|
+
function cachedCredentialTtlSeconds(timeoutMs) {
|
|
81025
|
+
return invocationCredentialTtlSeconds(timeoutMs) + CACHE_HANDOFF_MARGIN_SECONDS;
|
|
81026
|
+
}
|
|
81027
|
+
var MINIMUM_CREDENTIAL_TTL_SECONDS = 60, INVOCATION_RECEIPT_MARGIN_SECONDS = 5, CACHE_HANDOFF_MARGIN_SECONDS = 5, TOKEN_EXCHANGE_SETTLEMENT_MARGIN_SECONDS = 15;
|
|
81028
|
+
|
|
80981
81029
|
// src/connection/exchange.ts
|
|
80982
81030
|
function createExchangeCredentialResolver(target2, source2, fetch2, timeoutMs, cache2 = new ExchangeCredentialCache) {
|
|
80983
81031
|
requireExchangeTransport(target2);
|
|
81032
|
+
const cacheTtlSeconds = cachedCredentialTtlSeconds(timeoutMs);
|
|
81033
|
+
const exchangeTtlSeconds = exchangeCredentialTtlSeconds(timeoutMs);
|
|
80984
81034
|
return Object.freeze({
|
|
80985
81035
|
async resolve(kernelIssuer, signal) {
|
|
80986
81036
|
requireLive3(signal);
|
|
@@ -80992,8 +81042,8 @@ function createExchangeCredentialResolver(target2, source2, fetch2, timeoutMs, c
|
|
|
80992
81042
|
domainIssuer: target2.domainIssuer,
|
|
80993
81043
|
sourceIssuer: sourceIdentity.issuer,
|
|
80994
81044
|
sourceSubject: sourceIdentity.subject
|
|
80995
|
-
}), async () => {
|
|
80996
|
-
const delegationTtlSeconds = delegationLifetime(sourceToken);
|
|
81045
|
+
}), cacheTtlSeconds, async () => {
|
|
81046
|
+
const delegationTtlSeconds = delegationLifetime(sourceToken, exchangeTtlSeconds);
|
|
80997
81047
|
const client = new Client({ url: `${kernelIssuer}/invoke`, fetch: fetch2, timeoutMs });
|
|
80998
81048
|
try {
|
|
80999
81049
|
const authenticated3 = client.as(sourceToken);
|
|
@@ -81022,7 +81072,7 @@ function createExchangeCredentialResolver(target2, source2, fetch2, timeoutMs, c
|
|
|
81022
81072
|
if (envelope === undefined)
|
|
81023
81073
|
throw new Error("Token delegation returned no credential.");
|
|
81024
81074
|
return {
|
|
81025
|
-
...await exchange(target2.domainIssuer, kernelIssuer, envelope, fetch2, signal),
|
|
81075
|
+
...await exchange(target2.domainIssuer, kernelIssuer, envelope, cacheTtlSeconds, fetch2, signal),
|
|
81026
81076
|
user: user.id,
|
|
81027
81077
|
sourceIssuer: sourceIdentity.issuer,
|
|
81028
81078
|
sourceSubject: sourceIdentity.subject
|
|
@@ -81051,18 +81101,21 @@ function unknownFunctionOutcome(cause) {
|
|
|
81051
81101
|
return false;
|
|
81052
81102
|
return error52.reason.code === "FUNCTION_OUTCOME_UNKNOWN";
|
|
81053
81103
|
}
|
|
81054
|
-
function delegationLifetime(sourceToken) {
|
|
81104
|
+
function delegationLifetime(sourceToken, requiredTtlSeconds) {
|
|
81055
81105
|
const expiresAt = exports_credential.inspect(sourceToken).claims.exp;
|
|
81056
81106
|
if (typeof expiresAt !== "number" || !Number.isSafeInteger(expiresAt)) {
|
|
81057
81107
|
throw new AstraleError("TOKEN_EXCHANGE_SOURCE_INVALID", "The source identity credential has no valid expiration.");
|
|
81058
81108
|
}
|
|
81059
|
-
const remaining = expiresAt
|
|
81109
|
+
const remaining = remainingCredentialLifetimeSeconds(expiresAt);
|
|
81060
81110
|
if (!Number.isSafeInteger(remaining) || remaining < 1) {
|
|
81061
81111
|
throw new AstraleError("TOKEN_EXCHANGE_SOURCE_EXPIRED", "The source identity credential has no lifetime available for token exchange.");
|
|
81062
81112
|
}
|
|
81063
|
-
|
|
81113
|
+
if (remaining < requiredTtlSeconds) {
|
|
81114
|
+
throw new AstraleError("TOKEN_EXCHANGE_SOURCE_LIFETIME_INSUFFICIENT", "The source credential cannot cover the requested command timeout.", `Refresh the identity session or use a shorter --timeout; ${remaining} seconds remain but ${requiredTtlSeconds} are required.`);
|
|
81115
|
+
}
|
|
81116
|
+
return requiredTtlSeconds;
|
|
81064
81117
|
}
|
|
81065
|
-
async function exchange(domainIssuer, kernelIssuer, envelope, fetch2, signal) {
|
|
81118
|
+
async function exchange(domainIssuer, kernelIssuer, envelope, requiredTtlSeconds, fetch2, signal) {
|
|
81066
81119
|
const configurationUrl = new URL(exports_exchange.paths(domainIssuer).configuration, domainIssuer).toString();
|
|
81067
81120
|
const configurationResponse = await fetchExchange(fetch2, configurationUrl, {
|
|
81068
81121
|
method: "GET",
|
|
@@ -81119,8 +81172,31 @@ async function exchange(domainIssuer, kernelIssuer, envelope, fetch2, signal) {
|
|
|
81119
81172
|
if (inspected.iss !== domainIssuer || inspected.aud !== kernelIssuer || inspected.claims.exp !== exchanged.expiresAt) {
|
|
81120
81173
|
throw new AstraleError("TOKEN_EXCHANGE_PROTOCOL_ERROR", "Token exchange returned a credential inconsistent with the requested Domain and Kernel.");
|
|
81121
81174
|
}
|
|
81175
|
+
const remaining = effectiveExchangeLifetime(inspected, exchanged.expiresAt);
|
|
81176
|
+
if (remaining < requiredTtlSeconds) {
|
|
81177
|
+
throw new AstraleError("TOKEN_EXCHANGE_LIFETIME_INSUFFICIENT", "The Domain exchange credential cannot cover the requested command timeout.", `The Domain issuer returned ${Math.max(0, remaining)} seconds but ${requiredTtlSeconds} are required. Use a shorter --timeout or update the Domain execution service.`);
|
|
81178
|
+
}
|
|
81122
81179
|
return Object.freeze({ credential: exchanged.token, expiresAt: exchanged.expiresAt });
|
|
81123
81180
|
}
|
|
81181
|
+
function effectiveExchangeLifetime(inspected, outerExpiresAt) {
|
|
81182
|
+
let proofExpiresAt;
|
|
81183
|
+
try {
|
|
81184
|
+
const carried = exports_grant.acceptUnresolved(inspected.claims.grant).expr;
|
|
81185
|
+
if (carried.kind !== "identity" || !("credential" in carried) || typeof carried.credential !== "string") {
|
|
81186
|
+
throw new TypeError("Domain credential does not carry an identity proof.");
|
|
81187
|
+
}
|
|
81188
|
+
const value3 = exports_credential.inspect(carried.credential).claims.exp;
|
|
81189
|
+
if (typeof value3 !== "number" || !Number.isSafeInteger(value3)) {
|
|
81190
|
+
throw new TypeError("Domain credential carries an identity proof without an expiration.");
|
|
81191
|
+
}
|
|
81192
|
+
proofExpiresAt = value3;
|
|
81193
|
+
} catch (cause) {
|
|
81194
|
+
if (!(cause instanceof TypeError))
|
|
81195
|
+
throw cause;
|
|
81196
|
+
throw new AstraleError("TOKEN_EXCHANGE_PROTOCOL_ERROR", "Token exchange returned an invalid carried identity proof.", cause.message);
|
|
81197
|
+
}
|
|
81198
|
+
return remainingCredentialLifetimeSeconds(Math.min(outerExpiresAt, proofExpiresAt));
|
|
81199
|
+
}
|
|
81124
81200
|
async function fetchExchange(fetch2, input, init, message2) {
|
|
81125
81201
|
try {
|
|
81126
81202
|
return await fetch2(input, init);
|
|
@@ -81191,14 +81267,13 @@ function requireLive3(signal) {
|
|
|
81191
81267
|
return;
|
|
81192
81268
|
throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted.", "AbortError");
|
|
81193
81269
|
}
|
|
81194
|
-
var
|
|
81270
|
+
var MAXIMUM_RESPONSE_BYTES;
|
|
81195
81271
|
var init_exchange4 = __esm(() => {
|
|
81196
81272
|
init_auth4();
|
|
81197
81273
|
init_auth4();
|
|
81198
81274
|
init_client3();
|
|
81199
81275
|
init_errors2();
|
|
81200
81276
|
init_exchange_credentials();
|
|
81201
|
-
EXCHANGE_TTL_SECONDS = 5 * 60;
|
|
81202
81277
|
MAXIMUM_RESPONSE_BYTES = 256 * 1024;
|
|
81203
81278
|
});
|
|
81204
81279
|
|
|
@@ -81388,28 +81463,36 @@ var init_target2 = __esm(() => {
|
|
|
81388
81463
|
});
|
|
81389
81464
|
|
|
81390
81465
|
// src/connection/credential.ts
|
|
81391
|
-
function createConnectionCredential(expectedSourceIssuer, source2) {
|
|
81466
|
+
function createConnectionCredential(expectedSourceIssuer, source2, ttlSeconds = DELEGATION_TTL_SECONDS) {
|
|
81467
|
+
if (!Number.isSafeInteger(ttlSeconds) || ttlSeconds < 1) {
|
|
81468
|
+
throw new TypeError("Connection credential ttlSeconds must be a positive safe integer.");
|
|
81469
|
+
}
|
|
81392
81470
|
const resolveSource = source2.resolve.bind(source2);
|
|
81393
81471
|
return Object.freeze({
|
|
81394
|
-
ttlSeconds
|
|
81472
|
+
ttlSeconds,
|
|
81395
81473
|
async resolve(_call, signal) {
|
|
81396
81474
|
const resolved = await resolveSource(expectedSourceIssuer, signal);
|
|
81397
|
-
const
|
|
81475
|
+
const delegatedTtlSeconds = sourceBoundDelegationTtl(resolved, ttlSeconds);
|
|
81398
81476
|
return Object.freeze({
|
|
81399
81477
|
credential: resolved,
|
|
81400
|
-
...
|
|
81478
|
+
...delegatedTtlSeconds === undefined ? {} : { delegate: { ttlSeconds: delegatedTtlSeconds } }
|
|
81401
81479
|
});
|
|
81402
81480
|
}
|
|
81403
81481
|
});
|
|
81404
81482
|
}
|
|
81405
|
-
function sourceBoundDelegationTtl(input) {
|
|
81483
|
+
function sourceBoundDelegationTtl(input, requestedTtlSeconds) {
|
|
81406
81484
|
try {
|
|
81407
81485
|
const expiresAt = exports_credential.inspect(input).claims.exp;
|
|
81408
81486
|
if (typeof expiresAt !== "number" || !Number.isSafeInteger(expiresAt))
|
|
81409
81487
|
return;
|
|
81410
|
-
const remaining = expiresAt
|
|
81411
|
-
|
|
81412
|
-
|
|
81488
|
+
const remaining = remainingCredentialLifetimeSeconds(expiresAt);
|
|
81489
|
+
if (remaining < requestedTtlSeconds) {
|
|
81490
|
+
throw new AstraleError("CREDENTIAL_LIFETIME_INSUFFICIENT", "The selected credential cannot cover the requested command timeout.", `Use a fresh identity session or a shorter --timeout; ${Math.max(0, remaining)} seconds remain but ${requestedTtlSeconds} are required.`);
|
|
81491
|
+
}
|
|
81492
|
+
return Math.max(1, Math.min(requestedTtlSeconds, remaining));
|
|
81493
|
+
} catch (cause) {
|
|
81494
|
+
if (cause instanceof AstraleError)
|
|
81495
|
+
throw cause;
|
|
81413
81496
|
return;
|
|
81414
81497
|
}
|
|
81415
81498
|
}
|
|
@@ -81420,7 +81503,8 @@ function createCliCredential(target2, options, config2, fetch2 = globalThis.fetc
|
|
|
81420
81503
|
const authOptions = Object.freeze({
|
|
81421
81504
|
...options.as === undefined ? {} : { as: options.as },
|
|
81422
81505
|
...options.creds === undefined ? {} : { creds: options.creds },
|
|
81423
|
-
...target2.defaultIdentity === undefined ? {} : { defaultIdentity: target2.defaultIdentity }
|
|
81506
|
+
...target2.defaultIdentity === undefined ? {} : { defaultIdentity: target2.defaultIdentity },
|
|
81507
|
+
minimumRemainingSeconds: target2.domainIssuer === undefined ? invocationCredentialTtlSeconds(timeoutMs) : exchangeCredentialTtlSeconds(timeoutMs)
|
|
81424
81508
|
});
|
|
81425
81509
|
const source2 = {
|
|
81426
81510
|
async resolve(audience, signal) {
|
|
@@ -81431,7 +81515,8 @@ function createCliCredential(target2, options, config2, fetch2 = globalThis.fetc
|
|
|
81431
81515
|
}
|
|
81432
81516
|
};
|
|
81433
81517
|
const effective = target2.domainIssuer === undefined || options.creds !== undefined ? source2 : createExchangeCredentialResolver({ ...target2, domainIssuer: target2.domainIssuer }, source2, fetch2, timeoutMs);
|
|
81434
|
-
|
|
81518
|
+
const ttlSeconds = invocationCredentialTtlSeconds(timeoutMs);
|
|
81519
|
+
return createConnectionCredential(target2.kernelIssuer, effective, ttlSeconds);
|
|
81435
81520
|
}
|
|
81436
81521
|
function validateCredentialSelection(options) {
|
|
81437
81522
|
if (options.as !== undefined && options.creds !== undefined) {
|
|
@@ -22743,6 +22743,20 @@ function date4(params) {
|
|
|
22743
22743
|
|
|
22744
22744
|
// node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
22745
22745
|
config(en_default());
|
|
22746
|
+
// src/lib/credential-lifetime.ts
|
|
22747
|
+
function remainingCredentialLifetimeSeconds(expiresAtEpochSeconds, nowEpochSeconds = Math.ceil(Date.now() / 1000)) {
|
|
22748
|
+
if (!Number.isSafeInteger(expiresAtEpochSeconds) || !Number.isSafeInteger(nowEpochSeconds)) {
|
|
22749
|
+
throw new TypeError("Credential expiration and current time must be safe epoch seconds.");
|
|
22750
|
+
}
|
|
22751
|
+
return expiresAtEpochSeconds - nowEpochSeconds - 1;
|
|
22752
|
+
}
|
|
22753
|
+
function credentialLifetimeCovers(expiresAtEpochSeconds, minimumRemainingSeconds, nowEpochSeconds) {
|
|
22754
|
+
if (!Number.isSafeInteger(minimumRemainingSeconds) || minimumRemainingSeconds < 1) {
|
|
22755
|
+
throw new TypeError("Credential minimum lifetime must be a positive safe integer.");
|
|
22756
|
+
}
|
|
22757
|
+
return remainingCredentialLifetimeSeconds(expiresAtEpochSeconds, nowEpochSeconds) >= minimumRemainingSeconds;
|
|
22758
|
+
}
|
|
22759
|
+
|
|
22746
22760
|
// src/state/paths.ts
|
|
22747
22761
|
import { homedir } from "node:os";
|
|
22748
22762
|
import { join } from "node:path";
|
|
@@ -24130,18 +24144,25 @@ function isSessionExpired(session, skewMs = 60000) {
|
|
|
24130
24144
|
return false;
|
|
24131
24145
|
return new Date(expiresAt).getTime() <= Date.now() + skewMs;
|
|
24132
24146
|
}
|
|
24133
|
-
function accessTokenForAudience(session, audience) {
|
|
24147
|
+
function accessTokenForAudience(session, audience, minimumRemainingMs = 60000) {
|
|
24134
24148
|
if (audience === undefined) {
|
|
24135
|
-
return
|
|
24149
|
+
return tokenHasMinimumLifetime(session, minimumRemainingMs) ? session.access_token : undefined;
|
|
24136
24150
|
}
|
|
24137
24151
|
const entry = session.tokens?.[audience];
|
|
24138
|
-
if (entry &&
|
|
24152
|
+
if (entry && tokenHasMinimumLifetime(entry, minimumRemainingMs))
|
|
24139
24153
|
return entry.access_token;
|
|
24140
|
-
if (
|
|
24154
|
+
if (tokenHasMinimumLifetime(session, minimumRemainingMs) && tokenAudienceMatches(session.access_token, audience)) {
|
|
24141
24155
|
return session.access_token;
|
|
24142
24156
|
}
|
|
24143
24157
|
return;
|
|
24144
24158
|
}
|
|
24159
|
+
function tokenHasMinimumLifetime(value2, minimumRemainingMs) {
|
|
24160
|
+
const expiration = decodeTokenClaims(value2.access_token)?.exp;
|
|
24161
|
+
if (typeof expiration === "number" && Number.isSafeInteger(expiration)) {
|
|
24162
|
+
return credentialLifetimeCovers(expiration, Math.ceil(minimumRemainingMs / 1000));
|
|
24163
|
+
}
|
|
24164
|
+
return !isSessionExpired(value2, minimumRemainingMs);
|
|
24165
|
+
}
|
|
24145
24166
|
function withCachedToken(tokens, accessToken, expiresAt) {
|
|
24146
24167
|
const next = {};
|
|
24147
24168
|
for (const [aud2, entry] of Object.entries(tokens ?? {})) {
|
|
@@ -25047,10 +25068,11 @@ function idpSessionLockPath(identityName) {
|
|
|
25047
25068
|
return `${idpSessionPath(identityName)}.lock`;
|
|
25048
25069
|
}
|
|
25049
25070
|
async function ensureFreshSession(identityName, opts = {}) {
|
|
25071
|
+
const minimumRemainingMs = minimumLifetimeMs(opts.minimumRemainingSeconds);
|
|
25050
25072
|
const session = await readIdpSession(identityName);
|
|
25051
25073
|
if (!session)
|
|
25052
25074
|
throw new IdpSessionMissingError(identityName);
|
|
25053
|
-
if (accessTokenForAudience(session, opts.audience))
|
|
25075
|
+
if (accessTokenForAudience(session, opts.audience, minimumRemainingMs))
|
|
25054
25076
|
return session;
|
|
25055
25077
|
if (!session.refresh_token)
|
|
25056
25078
|
throw new IdpSessionNoRefreshTokenError(identityName);
|
|
@@ -25058,7 +25080,7 @@ async function ensureFreshSession(identityName, opts = {}) {
|
|
|
25058
25080
|
const current = await readIdpSession(identityName);
|
|
25059
25081
|
if (!current)
|
|
25060
25082
|
throw new IdpSessionMissingError(identityName);
|
|
25061
|
-
if (accessTokenForAudience(current, opts.audience))
|
|
25083
|
+
if (accessTokenForAudience(current, opts.audience, minimumRemainingMs))
|
|
25062
25084
|
return current;
|
|
25063
25085
|
const bookmarkOrg = opts.organizationId ?? (opts.audience ? await orgIdForAudience(opts.audience) : undefined);
|
|
25064
25086
|
const organizationId = bookmarkOrg ?? (opts.audience ? await (opts.resolveOrganizationId ?? fetchOrgHint)(opts.audience) : undefined);
|
|
@@ -25077,23 +25099,30 @@ async function ensureFreshSession(identityName, opts = {}) {
|
|
|
25077
25099
|
});
|
|
25078
25100
|
}
|
|
25079
25101
|
}
|
|
25080
|
-
const rescued = await rescueAfterInvalidGrant(identityName, current, opts.audience, e);
|
|
25102
|
+
const rescued = await rescueAfterInvalidGrant(identityName, current, opts.audience, minimumRemainingMs, e);
|
|
25081
25103
|
if (rescued)
|
|
25082
25104
|
return rescued;
|
|
25083
25105
|
throw e;
|
|
25084
25106
|
}
|
|
25085
25107
|
});
|
|
25086
25108
|
}
|
|
25087
|
-
async function rescueAfterInvalidGrant(identityName, seen, audience, error51) {
|
|
25109
|
+
async function rescueAfterInvalidGrant(identityName, seen, audience, minimumRemainingMs, error51) {
|
|
25088
25110
|
if (!(error51 instanceof OAuthTokenError) || error51.code !== "invalid_grant")
|
|
25089
25111
|
return;
|
|
25090
25112
|
const latest = await readIdpSession(identityName).catch(() => null);
|
|
25091
25113
|
if (!latest || latest.updatedAt === seen.updatedAt)
|
|
25092
25114
|
return;
|
|
25093
|
-
if (!accessTokenForAudience(latest, audience))
|
|
25115
|
+
if (!accessTokenForAudience(latest, audience, minimumRemainingMs))
|
|
25094
25116
|
return;
|
|
25095
25117
|
return latest;
|
|
25096
25118
|
}
|
|
25119
|
+
function minimumLifetimeMs(input) {
|
|
25120
|
+
const seconds = input ?? 60;
|
|
25121
|
+
if (!Number.isSafeInteger(seconds) || seconds < 1 || seconds > Math.floor(Number.MAX_SAFE_INTEGER / 1000)) {
|
|
25122
|
+
throw new TypeError("IdP session minimum lifetime must be a positive safe integer.");
|
|
25123
|
+
}
|
|
25124
|
+
return seconds * 1000;
|
|
25125
|
+
}
|
|
25097
25126
|
|
|
25098
25127
|
// src/connection/auth.ts
|
|
25099
25128
|
async function resolveCredential(opts, config2, audience = config2.issuer, registrationKey) {
|
|
@@ -25108,14 +25137,14 @@ async function resolveCredential(opts, config2, audience = config2.issuer, regis
|
|
|
25108
25137
|
resolvedIdentity = identity3;
|
|
25109
25138
|
resolvedName = identityName;
|
|
25110
25139
|
if ((identity3.source ?? "key") === "idp")
|
|
25111
|
-
return await resolveIdpAccessToken(identityName, identity3, audience);
|
|
25140
|
+
return await resolveIdpAccessToken(identityName, identity3, audience, opts.minimumRemainingSeconds);
|
|
25112
25141
|
return await signAs(identity3.subject, KEYS_DIR, resolveKeyIdentityAuthOptions(identity3, config2, audience, registrationKey));
|
|
25113
25142
|
}
|
|
25114
25143
|
const identity2 = await getDefault();
|
|
25115
25144
|
resolvedIdentity = identity2;
|
|
25116
25145
|
resolvedName = identity2.name;
|
|
25117
25146
|
if ((identity2.source ?? "key") === "idp") {
|
|
25118
|
-
return await resolveIdpAccessToken(identity2.name, identity2, audience);
|
|
25147
|
+
return await resolveIdpAccessToken(identity2.name, identity2, audience, opts.minimumRemainingSeconds);
|
|
25119
25148
|
}
|
|
25120
25149
|
return await signAs(identity2.subject, KEYS_DIR, resolveKeyIdentityAuthOptions(identity2, config2, audience, registrationKey));
|
|
25121
25150
|
} catch (e) {
|
|
@@ -25157,10 +25186,10 @@ function resolveKeyIdentityAuthOptions(identity2, config2, audience = config2.is
|
|
|
25157
25186
|
function systemIdentityIssuer(identity2, audience, config2) {
|
|
25158
25187
|
return identity2.subject === "system" ? audience : config2.issuer;
|
|
25159
25188
|
}
|
|
25160
|
-
async function resolveIdpAccessToken(identityName, identity2, audience) {
|
|
25189
|
+
async function resolveIdpAccessToken(identityName, identity2, audience, minimumRemainingSeconds) {
|
|
25161
25190
|
let resolved;
|
|
25162
25191
|
try {
|
|
25163
|
-
resolved = await ensureFreshSession(identityName, { audience });
|
|
25192
|
+
resolved = await ensureFreshSession(identityName, { audience, minimumRemainingSeconds });
|
|
25164
25193
|
} catch (e) {
|
|
25165
25194
|
if (e instanceof IdpSessionMissingError) {
|
|
25166
25195
|
throw new Error(`No cached IdP session for "${identityName}". Run: astrale auth login --idp ${identity2.idp ?? "<idp>"}`);
|
|
@@ -15613,6 +15613,20 @@ function date4(params) {
|
|
|
15613
15613
|
|
|
15614
15614
|
// node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
15615
15615
|
config(en_default());
|
|
15616
|
+
// src/lib/credential-lifetime.ts
|
|
15617
|
+
function remainingCredentialLifetimeSeconds(expiresAtEpochSeconds, nowEpochSeconds = Math.ceil(Date.now() / 1000)) {
|
|
15618
|
+
if (!Number.isSafeInteger(expiresAtEpochSeconds) || !Number.isSafeInteger(nowEpochSeconds)) {
|
|
15619
|
+
throw new TypeError("Credential expiration and current time must be safe epoch seconds.");
|
|
15620
|
+
}
|
|
15621
|
+
return expiresAtEpochSeconds - nowEpochSeconds - 1;
|
|
15622
|
+
}
|
|
15623
|
+
function credentialLifetimeCovers(expiresAtEpochSeconds, minimumRemainingSeconds, nowEpochSeconds) {
|
|
15624
|
+
if (!Number.isSafeInteger(minimumRemainingSeconds) || minimumRemainingSeconds < 1) {
|
|
15625
|
+
throw new TypeError("Credential minimum lifetime must be a positive safe integer.");
|
|
15626
|
+
}
|
|
15627
|
+
return remainingCredentialLifetimeSeconds(expiresAtEpochSeconds, nowEpochSeconds) >= minimumRemainingSeconds;
|
|
15628
|
+
}
|
|
15629
|
+
|
|
15616
15630
|
// src/state/paths.ts
|
|
15617
15631
|
import { homedir } from "node:os";
|
|
15618
15632
|
import { join } from "node:path";
|
|
@@ -14446,6 +14446,20 @@ function date4(params) {
|
|
|
14446
14446
|
|
|
14447
14447
|
// node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
14448
14448
|
config(en_default());
|
|
14449
|
+
// src/lib/credential-lifetime.ts
|
|
14450
|
+
function remainingCredentialLifetimeSeconds(expiresAtEpochSeconds, nowEpochSeconds = Math.ceil(Date.now() / 1000)) {
|
|
14451
|
+
if (!Number.isSafeInteger(expiresAtEpochSeconds) || !Number.isSafeInteger(nowEpochSeconds)) {
|
|
14452
|
+
throw new TypeError("Credential expiration and current time must be safe epoch seconds.");
|
|
14453
|
+
}
|
|
14454
|
+
return expiresAtEpochSeconds - nowEpochSeconds - 1;
|
|
14455
|
+
}
|
|
14456
|
+
function credentialLifetimeCovers(expiresAtEpochSeconds, minimumRemainingSeconds, nowEpochSeconds) {
|
|
14457
|
+
if (!Number.isSafeInteger(minimumRemainingSeconds) || minimumRemainingSeconds < 1) {
|
|
14458
|
+
throw new TypeError("Credential minimum lifetime must be a positive safe integer.");
|
|
14459
|
+
}
|
|
14460
|
+
return remainingCredentialLifetimeSeconds(expiresAtEpochSeconds, nowEpochSeconds) >= minimumRemainingSeconds;
|
|
14461
|
+
}
|
|
14462
|
+
|
|
14449
14463
|
// src/state/paths.ts
|
|
14450
14464
|
import { homedir } from "node:os";
|
|
14451
14465
|
import { join } from "node:path";
|
|
@@ -26,6 +26,7 @@ export declare function resolveCredential(opts: {
|
|
|
26
26
|
as?: string;
|
|
27
27
|
creds?: string;
|
|
28
28
|
defaultIdentity?: string;
|
|
29
|
+
minimumRemainingSeconds?: number;
|
|
29
30
|
}, config: AstraleConfig, audience?: string, registrationKey?: string): Promise<string>;
|
|
30
31
|
export declare function resolveKeyIdentityAuthOptions(identity: Identity, config: AstraleConfig, audience?: string, registrationKey?: string): KeyIdentityAuthOptions;
|
|
31
32
|
export declare function classifyNoRefreshTokenError(requestedAudience: string, sourceAudience: string | undefined, error: IdpSessionNoRefreshTokenError): IdpSessionNoRefreshTokenError | IdpAudienceMismatchError;
|
|
@@ -7,7 +7,7 @@ export interface SourceCredentialResolver {
|
|
|
7
7
|
resolve(audience: IssuerId, signal: AbortSignal): Promise<string>;
|
|
8
8
|
}
|
|
9
9
|
/** Resolve source authority; an omitted Session delegation preserves exact current authority. */
|
|
10
|
-
export declare function createConnectionCredential(expectedSourceIssuer: IssuerId, source: SourceCredentialResolver): SessionAuth;
|
|
10
|
+
export declare function createConnectionCredential(expectedSourceIssuer: IssuerId, source: SourceCredentialResolver, ttlSeconds?: number): SessionAuth;
|
|
11
11
|
/** Bind CLI identity state and Core Auth delegation to one Session auth capability. */
|
|
12
12
|
export declare function createCliCredential(target: ConnectionTarget, options: ConnectionOptions, config: AstraleConfig, fetch?: Fetch, timeoutMs?: number): SessionAuth | undefined;
|
|
13
13
|
/** Reject contradictory explicit credential selections before identity or network access. */
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Cover the complete command deadline while preserving the existing short-command floor. */
|
|
2
|
+
export declare function invocationCredentialTtlSeconds(timeoutMs: number): number;
|
|
3
|
+
/** Leave the final carrier lifetime intact after source delegation and issuer exchange settle. */
|
|
4
|
+
export declare function exchangeCredentialTtlSeconds(timeoutMs: number): number;
|
|
5
|
+
/** Refresh a cached token before the final Session carrier can cross a second boundary. */
|
|
6
|
+
export declare function cachedCredentialTtlSeconds(timeoutMs: number): number;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Whole seconds safely available after second-boundary rounding and carrier handoff. */
|
|
2
|
+
export declare function remainingCredentialLifetimeSeconds(expiresAtEpochSeconds: number, nowEpochSeconds?: number): number;
|
|
3
|
+
export declare function credentialLifetimeCovers(expiresAtEpochSeconds: number, minimumRemainingSeconds: number, nowEpochSeconds?: number): boolean;
|
|
@@ -12,6 +12,8 @@ export declare class IdpSessionNoRefreshTokenError extends Error {
|
|
|
12
12
|
export type EnsureFreshSessionOptions = {
|
|
13
13
|
audience?: string;
|
|
14
14
|
organizationId?: string;
|
|
15
|
+
/** Minimum source-token lifetime required before the caller starts its operation. */
|
|
16
|
+
minimumRemainingSeconds?: number;
|
|
15
17
|
/**
|
|
16
18
|
* Org-hint resolver, consulted only when a refresh actually happens.
|
|
17
19
|
* Defaults to `fetchOrgHint`; injectable for tests.
|
package/dist/types/lib/idp.d.ts
CHANGED
|
@@ -196,7 +196,7 @@ export declare function isSessionExpired(session: Pick<IdpSession, 'expires_at'>
|
|
|
196
196
|
* top-level token's own `aud` claim. Without an `audience`, freshness of the
|
|
197
197
|
* top-level token is the only requirement.
|
|
198
198
|
*/
|
|
199
|
-
export declare function accessTokenForAudience(session: IdpSession, audience?: string): string | undefined;
|
|
199
|
+
export declare function accessTokenForAudience(session: IdpSession, audience?: string, minimumRemainingMs?: number): string | undefined;
|
|
200
200
|
/**
|
|
201
201
|
* Fold a freshly minted access token into the per-audience map under every
|
|
202
202
|
* `aud` it carries, dropping entries that have already expired.
|
|
@@ -21,7 +21,7 @@ export declare class ExchangeCredentialCache {
|
|
|
21
21
|
private readonly path;
|
|
22
22
|
private readonly refreshing;
|
|
23
23
|
constructor(path?: string);
|
|
24
|
-
getOrRefresh(key: exchange.Key, refresh: () => Promise<exchange.Entry>, now?: () => number): Promise<string>;
|
|
24
|
+
getOrRefresh(key: exchange.Key, minimumRemainingSeconds: number, refresh: () => Promise<exchange.Entry>, now?: () => number): Promise<string>;
|
|
25
25
|
deleteKernel(kernelIssuer: string): Promise<void>;
|
|
26
26
|
clear(): Promise<void>;
|
|
27
27
|
private getOrRefreshOnce;
|