@astrale-os/sdk 0.5.0-beta.53 → 0.5.0-beta.55

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.
@@ -7,7 +7,7 @@ import { bindSession, ClientSession } from '../../platform/client/session/index.
7
7
  import { NodeId } from '../../platform/graph/node/index.js';
8
8
  import { Path } from '../../platform/graph/path/index.js';
9
9
  import { projectError, protocolError } from '../invocation/result/index.js';
10
- import { composedIdentity, signCallback, signDomainCallback } from './callback.js';
10
+ import { composedIdentity, createCallbackCredentialResolver, maximumCallbackDelegationTtlSeconds, signDomainCallback, } from './callback.js';
11
11
  import { CredentialRejected, IssuerUnavailable, boundedFetch, callbackTtl, invocationUrl, admitEndpoint, requireLive, } from './errors.js';
12
12
  import { verifyCredential, verifyCredentialAgainst } from './jwks.js';
13
13
  const DEFAULT_ROUTE_AGE_MS = 60_000;
@@ -52,13 +52,21 @@ export async function authenticate(credentialInput, expectedKernel, execution, c
52
52
  if (established.verified.iss !== expectedKernel) {
53
53
  throw protocolError('AUTH_INVALID', 'Credential is invalid.');
54
54
  }
55
- const callbackCredential = await signCallback(configuration.signing, configuration.issuer, configuration.subject, expectedKernel, composedIdentity(established.delegation.credential), established.verified, execution);
55
+ const callbackIdentity = composedIdentity(established.delegation.credential);
56
+ const resolveCallback = createCallbackCredentialResolver(configuration.signing, configuration.issuer, configuration.subject, expectedKernel, callbackIdentity, established.verified, execution);
56
57
  const session = new ClientSession({
57
58
  kernel: expectedKernel,
58
59
  fetch: boundedFetch(configuration.fetch, execution.signal),
59
60
  auth: {
60
- ttlSeconds: callbackTtl(execution.deadline),
61
- resolve: () => ({ credential: callbackCredential }),
61
+ ttlSeconds: Math.min(callbackTtl(execution.deadline), maximumCallbackDelegationTtlSeconds),
62
+ async resolve(_call, signal) {
63
+ const callback = await resolveCallback(signal);
64
+ signal.throwIfAborted();
65
+ return {
66
+ credential: callback.credential,
67
+ delegate: { ttlSeconds: callback.delegateTtlSeconds },
68
+ };
69
+ },
62
70
  },
63
71
  policy: {
64
72
  maximumRouteAgeMs: DEFAULT_ROUTE_AGE_MS,
@@ -1,5 +1,12 @@
1
1
  import type { IssuerId, UnresolvedIdentityExpr, VerifiedCredential } from '../../platform/auth/index.js';
2
2
  import type { SigningMaterial } from './signing.js';
3
+ export declare const maximumCallbackTtlSeconds = 120;
4
+ export declare const maximumCallbackDelegationTtlSeconds = 60;
5
+ export interface SignedCallback {
6
+ readonly credential: string;
7
+ readonly expiresAt: number;
8
+ readonly delegateTtlSeconds: number;
9
+ }
3
10
  export declare function composedIdentity(nested: string | Uint8Array): UnresolvedIdentityExpr;
4
11
  export declare function signDomainCallback(signing: SigningMaterial, issuer: IssuerId, subject: string, audience: IssuerId, execution: {
5
12
  readonly deadline: number;
@@ -9,3 +16,8 @@ export declare function signCallback(signing: SigningMaterial, issuer: IssuerId,
9
16
  readonly deadline: number;
10
17
  readonly signal: AbortSignal;
11
18
  }): Promise<string>;
19
+ /** Reuse one exact callback authority partition, refreshing only near its bounded expiry. */
20
+ export declare function createCallbackCredentialResolver(signing: SigningMaterial, issuer: IssuerId, subject: string, audience: IssuerId, expression: UnresolvedIdentityExpr, carrier: VerifiedCredential, execution: {
21
+ readonly deadline: number;
22
+ readonly signal: AbortSignal;
23
+ }): (signal: AbortSignal) => Promise<SignedCallback>;
@@ -1,7 +1,9 @@
1
1
  import { SignJWT } from 'jose';
2
2
  import { protocolError } from '../invocation/result/index.js';
3
3
  import { requireLive } from './errors.js';
4
- const CALLBACK_TTL_SECONDS = 120;
4
+ export const maximumCallbackTtlSeconds = 120;
5
+ export const maximumCallbackDelegationTtlSeconds = 60;
6
+ const CALLBACK_DELEGATION_SKEW_SECONDS = 5;
5
7
  export function composedIdentity(nested) {
6
8
  return Object.freeze({
7
9
  kind: 'union',
@@ -12,20 +14,59 @@ export function composedIdentity(nested) {
12
14
  });
13
15
  }
14
16
  export async function signDomainCallback(signing, issuer, subject, audience, execution) {
15
- return sign(signing, issuer, subject, audience, { kind: 'identity', self: true }, execution);
17
+ return (await sign(signing, issuer, subject, audience, { kind: 'identity', self: true }, execution)).credential;
16
18
  }
17
19
  export async function signCallback(signing, issuer, subject, audience, expression, carrier, execution) {
18
20
  const maximum = carrier.exp ?? Number.POSITIVE_INFINITY;
19
- return sign(signing, issuer, subject, audience, expression, execution, maximum);
21
+ return (await sign(signing, issuer, subject, audience, expression, execution, maximum)).credential;
22
+ }
23
+ /** Reuse one exact callback authority partition, refreshing only near its bounded expiry. */
24
+ export function createCallbackCredentialResolver(signing, issuer, subject, audience, expression, carrier, execution) {
25
+ const maximum = carrier.exp ?? Number.POSITIVE_INFINITY;
26
+ let cached;
27
+ let pending;
28
+ return async (signal) => {
29
+ signal.throwIfAborted();
30
+ requireLive(execution);
31
+ const now = Math.floor(Date.now() / 1_000);
32
+ if (cached !== undefined &&
33
+ now <= cached.expiresAt - CALLBACK_DELEGATION_SKEW_SECONDS - cached.delegateTtlSeconds) {
34
+ return cached;
35
+ }
36
+ if (pending === undefined) {
37
+ const current = sign(signing, issuer, subject, audience, expression, execution, maximum).then((signed) => {
38
+ const delegateTtlSeconds = Math.min(maximumCallbackDelegationTtlSeconds, signed.expiresAt - signed.issuedAt - CALLBACK_DELEGATION_SKEW_SECONDS);
39
+ if (delegateTtlSeconds < 1) {
40
+ throw protocolError('AUTH_INVALID', 'Credential is invalid.');
41
+ }
42
+ return (cached = Object.freeze({
43
+ credential: signed.credential,
44
+ expiresAt: signed.expiresAt,
45
+ delegateTtlSeconds,
46
+ }));
47
+ });
48
+ pending = current;
49
+ void current.then(() => {
50
+ if (pending === current)
51
+ pending = undefined;
52
+ }, () => {
53
+ if (pending === current)
54
+ pending = undefined;
55
+ });
56
+ }
57
+ return withSignal(pending, signal);
58
+ };
20
59
  }
21
60
  async function sign(signing, issuer, subject, audience, expression, execution, maximum = Number.POSITIVE_INFINITY) {
61
+ requireLive(execution);
62
+ const privateKey = await withSignal(signing.privateKey, execution.signal);
22
63
  requireLive(execution);
23
64
  const now = Math.floor(Date.now() / 1_000);
24
- const expiresAt = Math.min(maximum, Math.floor(execution.deadline / 1_000), now + CALLBACK_TTL_SECONDS);
65
+ const expiresAt = Math.min(maximum, Math.floor(execution.deadline / 1_000), now + maximumCallbackTtlSeconds);
25
66
  if (!Number.isFinite(expiresAt) || expiresAt <= now) {
26
67
  throw protocolError('AUTH_INVALID', 'Credential is invalid.');
27
68
  }
28
- return new SignJWT({ grant: { v: 1, expr: expression } })
69
+ const credential = await withSignal(new SignJWT({ grant: { v: 1, expr: expression } })
29
70
  .setProtectedHeader({
30
71
  alg: signing.algorithm,
31
72
  ...(signing.keyId === undefined ? {} : { kid: signing.keyId }),
@@ -35,5 +76,26 @@ async function sign(signing, issuer, subject, audience, expression, execution, m
35
76
  .setAudience(audience)
36
77
  .setIssuedAt(now)
37
78
  .setExpirationTime(expiresAt)
38
- .sign(await signing.privateKey);
79
+ .sign(privateKey), execution.signal);
80
+ requireLive(execution);
81
+ return Object.freeze({ credential, issuedAt: now, expiresAt });
82
+ }
83
+ function withSignal(operation, signal) {
84
+ if (signal.aborted)
85
+ return Promise.reject(signal.reason);
86
+ return new Promise((resolve, reject) => {
87
+ const cleanup = () => signal.removeEventListener('abort', abort);
88
+ const abort = () => {
89
+ cleanup();
90
+ reject(signal.reason);
91
+ };
92
+ signal.addEventListener('abort', abort, { once: true });
93
+ operation.then((value) => {
94
+ cleanup();
95
+ resolve(value);
96
+ }, (error) => {
97
+ cleanup();
98
+ reject(error);
99
+ });
100
+ });
39
101
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/sdk",
3
- "version": "0.5.0-beta.53",
3
+ "version": "0.5.0-beta.55",
4
4
  "description": "Schema-first SDK for defining, composing, and deploying Astrale domains",
5
5
  "keywords": [
6
6
  "astrale",
@@ -213,11 +213,11 @@
213
213
  "registry": "https://registry.npmjs.org/"
214
214
  },
215
215
  "dependencies": {
216
- "@astrale-os/kernel-client": "0.6.0-beta.20",
217
- "@astrale-os/kernel-core": "0.9.0-beta.16",
218
- "@astrale-os/kernel-dsl": "0.2.0-beta.13",
219
- "@astrale-os/kernel-protocol": "0.5.0-beta.16",
220
- "@astrale-os/kernel-server": "0.5.0-beta.17",
216
+ "@astrale-os/kernel-client": "0.6.0-beta.22",
217
+ "@astrale-os/kernel-core": "0.9.0-beta.17",
218
+ "@astrale-os/kernel-dsl": "0.2.0-beta.14",
219
+ "@astrale-os/kernel-protocol": "0.5.0-beta.17",
220
+ "@astrale-os/kernel-server": "0.5.0-beta.18",
221
221
  "@typescript/native-preview": "7.0.0-dev.20260707.2",
222
222
  "hono": "^4.13.2",
223
223
  "jose": "^6.2.9",