@astrale-os/kernel-client 0.6.0-beta.70 → 0.6.0-beta.71
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/auth/auth.d.ts +17 -5
- package/dist/auth/auth.js +3 -4
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.js +0 -1
- package/dist/session/admission.d.ts +18 -0
- package/dist/session/admission.js +117 -0
- package/dist/session/bound.d.ts +2 -1
- package/dist/session/bound.js +4 -2
- package/dist/session/discovery/index.d.ts +1 -0
- package/dist/session/discovery/index.js +1 -0
- package/dist/session/reference.d.ts +1 -0
- package/dist/session/reference.js +1 -1
- package/dist/session/session.d.ts +2 -0
- package/dist/session/session.js +23 -3
- package/dist/session/session.typecheck.js +7 -0
- package/package.json +6 -6
package/dist/auth/auth.d.ts
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
|
-
import type { Identity, Authentication, IdentityId, MintedCredential, RegisterResult } from '@astrale-os/kernel-core/auth';
|
|
2
|
-
import type { PathLike } from '@astrale-os/kernel-core/path';
|
|
3
|
-
import type {
|
|
1
|
+
import type { Identity, Authentication, IdentityId, MintedCredential, RegisterResult, UnresolvedIdentityExpr } from '@astrale-os/kernel-core/auth';
|
|
2
|
+
import type { NodePath, PathLike } from '@astrale-os/kernel-core/path';
|
|
3
|
+
import type { Key } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
4
|
+
import type { Call, Input } from '../client/index.js';
|
|
4
5
|
import type { RequestOptions } from '../transport/index.js';
|
|
5
6
|
import type { PolicyDecisionInput } from './authorization.js';
|
|
6
7
|
import type { DelegateOptions, MintOptions, ImpersonateOptions } from './credential.js';
|
|
7
8
|
import type { WhoisInput } from './identity.js';
|
|
8
9
|
import type { RegisterRequest } from './register.js';
|
|
9
10
|
export type AuthCall = (target: PathLike, input: Input, options?: RequestOptions) => Promise<Input>;
|
|
11
|
+
/** Current admission of one remote Call; the target has not been executed. */
|
|
12
|
+
export interface FunctionAdmission {
|
|
13
|
+
readonly callable: Key.Callable;
|
|
14
|
+
readonly receiver?: NodePath;
|
|
15
|
+
}
|
|
16
|
+
export interface AdmissionOptions extends RequestOptions {
|
|
17
|
+
/** Intersect the caller's Grant; defaults to explicit session attenuation, then self. */
|
|
18
|
+
readonly attenuation?: UnresolvedIdentityExpr;
|
|
19
|
+
}
|
|
10
20
|
export interface AuthApi {
|
|
21
|
+
/** Check access and input without executing an installed remote callable. */
|
|
22
|
+
admit(call: Call, options?: AdmissionOptions): Promise<FunctionAdmission>;
|
|
11
23
|
register(input: RegisterRequest, options?: RequestOptions): Promise<RegisterResult>;
|
|
12
24
|
whoami(options?: RequestOptions): Promise<Identity & Authentication>;
|
|
13
25
|
whois(input: WhoisInput, options?: RequestOptions): Promise<(Identity & Authentication) | null>;
|
|
@@ -18,5 +30,5 @@ export interface AuthApi {
|
|
|
18
30
|
mint(input: MintOptions, options?: RequestOptions): Promise<MintedCredential>;
|
|
19
31
|
delegate(identity: IdentityId, input: DelegateOptions, options?: RequestOptions): Promise<MintedCredential>;
|
|
20
32
|
}
|
|
21
|
-
/**
|
|
22
|
-
export declare function createAuth(call: AuthCall): AuthApi;
|
|
33
|
+
/** Internal composition over callbacks owned by the Session or its bound view. */
|
|
34
|
+
export declare function createAuth(call: AuthCall, admit: AuthApi['admit']): AuthApi;
|
package/dist/auth/auth.js
CHANGED
|
@@ -2,11 +2,10 @@ import { authorizationApi } from './authorization.js';
|
|
|
2
2
|
import { credentialApi } from './credential.js';
|
|
3
3
|
import { identityApi } from './identity.js';
|
|
4
4
|
import { registerApi } from './register.js';
|
|
5
|
-
/**
|
|
6
|
-
export function createAuth(call) {
|
|
7
|
-
if (typeof call !== 'function')
|
|
8
|
-
throw new TypeError('Auth call capability is invalid.');
|
|
5
|
+
/** Internal composition over callbacks owned by the Session or its bound view. */
|
|
6
|
+
export function createAuth(call, admit) {
|
|
9
7
|
return Object.freeze({
|
|
8
|
+
admit,
|
|
10
9
|
...registerApi(call),
|
|
11
10
|
...identityApi(call),
|
|
12
11
|
...authorizationApi(call),
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export type { AuthApi, AdmissionOptions, FunctionAdmission } from './auth.js';
|
|
2
2
|
export { IDENTITY_CAN_PATH, type PolicyDecisionInput } from './authorization.js';
|
|
3
3
|
export { IDENTITY_MINT_PATH, IDENTITY_IMPERSONATE_PATH, type DelegateOptions, type MintOptions, type ImpersonateOptions, } from './credential.js';
|
|
4
4
|
export { IDENTITY_WHOAMI_PATH, IDENTITY_WHOIS_PATH, type WhoisInput } from './identity.js';
|
package/dist/auth/index.js
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { invocation } from '@astrale-os/kernel-protocol';
|
|
2
|
+
import type { AdmissionOptions, FunctionAdmission } from '../auth/index.js';
|
|
3
|
+
import type { Client } from '../client/index.js';
|
|
4
|
+
import type { SchemaOptions } from '../schema/schema.js';
|
|
5
|
+
import type { Fetch } from '../transport/http/index.js';
|
|
6
|
+
import type { SessionOperation, SessionPolicy } from './context/index.js';
|
|
7
|
+
import type { SessionAuth } from './routing/index.js';
|
|
8
|
+
export interface FunctionAdmissionDependencies {
|
|
9
|
+
readonly kernel: string;
|
|
10
|
+
readonly auth?: SessionAuth;
|
|
11
|
+
readonly source: () => Client;
|
|
12
|
+
readonly fetch: Fetch;
|
|
13
|
+
readonly policy: Readonly<SessionPolicy>;
|
|
14
|
+
readonly limits: SchemaOptions;
|
|
15
|
+
readonly maximumResponseBytes: number;
|
|
16
|
+
}
|
|
17
|
+
/** Check a remote Call through the existing Domain exchange and Kernel admission protocols. */
|
|
18
|
+
export declare function admitFunction(dependencies: FunctionAdmissionDependencies, call: invocation.Call, options: AdmissionOptions | undefined, operation: SessionOperation): Promise<FunctionAdmission>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { grant } from '@astrale-os/kernel-core/auth';
|
|
2
|
+
import { targetPath } from '@astrale-os/kernel-core/callable';
|
|
3
|
+
import { Path } from '@astrale-os/kernel-core/path';
|
|
4
|
+
import { admitOutput } from '@astrale-os/kernel-core/schema/kernel/syscalls/contracts';
|
|
5
|
+
import { function as functions } from '@astrale-os/kernel-core/schema/kernel/syscalls/references';
|
|
6
|
+
import { Key } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
7
|
+
import { codecs, issuer } from '@astrale-os/kernel-protocol';
|
|
8
|
+
import { credentialApi } from '../auth/credential.js';
|
|
9
|
+
import { identityApi } from '../auth/identity.js';
|
|
10
|
+
import { ClientError } from '../errors/index.js';
|
|
11
|
+
import { createSchema } from '../schema/index.js';
|
|
12
|
+
import { allowEndpoint, runSessionOperation, sessionTransportOptions } from './context/index.js';
|
|
13
|
+
import { boundedResponse, requireExactResponse } from './discovery/index.js';
|
|
14
|
+
import { matchesCallable } from './reference.js';
|
|
15
|
+
import { requireResultKind, resolveAuth } from './routing/index.js';
|
|
16
|
+
const ADMIT = Path.staticMethod(Path.project(functions.admit.owner), functions.admit.name);
|
|
17
|
+
/** Check a remote Call through the existing Domain exchange and Kernel admission protocols. */
|
|
18
|
+
export async function admitFunction(dependencies, call, options, operation) {
|
|
19
|
+
const target = Path.parse(targetPath(call.target));
|
|
20
|
+
const origin = callableOrigin(target);
|
|
21
|
+
const attenuation = options?.attenuation === undefined
|
|
22
|
+
? undefined
|
|
23
|
+
: grant.acceptUnresolved({ v: 1, expr: options.attenuation }).expr;
|
|
24
|
+
const effective = await resolveAuth(dependencies.auth, call, operation);
|
|
25
|
+
if (effective.credential === undefined || effective.delegate === undefined) {
|
|
26
|
+
throw new ClientError('Function admission requires an authenticated session.');
|
|
27
|
+
}
|
|
28
|
+
// Resolve once for the intended Call, then retain that exact caller across every helper syscall.
|
|
29
|
+
const client = dependencies.source();
|
|
30
|
+
const caller = client.as(effective.credential);
|
|
31
|
+
const dispatch = (input) => runSessionOperation(operation, () => caller.dispatch(input, {
|
|
32
|
+
...sessionTransportOptions(operation),
|
|
33
|
+
delegate: effective.delegate,
|
|
34
|
+
}));
|
|
35
|
+
const callIdentity = async (target, input) => requireResultKind('value', await dispatch({ target, input })).value;
|
|
36
|
+
const identity = identityApi(callIdentity);
|
|
37
|
+
const credentials = credentialApi(callIdentity);
|
|
38
|
+
const schema = createSchema(dispatch, dependencies.limits);
|
|
39
|
+
const [domain, self] = await Promise.all([schema.inspect(origin), identity.whoami()]);
|
|
40
|
+
const remote = domain.publication;
|
|
41
|
+
if (remote === null || remote.identity.issuer === dependencies.kernel) {
|
|
42
|
+
throw new ClientError('Function admission through token exchange requires a remote Domain.');
|
|
43
|
+
}
|
|
44
|
+
const configuration = issuer.acceptConfiguration(await fetchJson(dependencies, new URL(issuer.paths(remote.identity.issuer).configuration, remote.identity.issuer).href, operation), remote.identity.issuer);
|
|
45
|
+
const endpoint = configuration.token_exchange_endpoint;
|
|
46
|
+
if (endpoint === undefined) {
|
|
47
|
+
throw new ClientError('The installed Domain does not advertise token exchange.');
|
|
48
|
+
}
|
|
49
|
+
allowEndpoint(endpoint, dependencies.policy);
|
|
50
|
+
const credential = await credentials.delegate(self.id, {
|
|
51
|
+
audience: remote.identity.issuer,
|
|
52
|
+
ttlSeconds: effective.delegate.ttlSeconds,
|
|
53
|
+
attenuation: attenuation ?? effective.delegate.attenuation ?? { kind: 'identity', self: true },
|
|
54
|
+
});
|
|
55
|
+
const exchanged = issuer.exchange.acceptResponse(await fetchJson(dependencies, endpoint, operation, credential));
|
|
56
|
+
if (exchanged.expiresAt * 1_000 <= Date.now()) {
|
|
57
|
+
throw new ClientError('Domain token exchange returned an expired credential.');
|
|
58
|
+
}
|
|
59
|
+
const result = await runSessionOperation(operation, () => client
|
|
60
|
+
.as(exchanged.token)
|
|
61
|
+
.dispatch({ target: ADMIT, input: { call, credential } }, { ...sessionTransportOptions(operation), delegate: effective.delegate }));
|
|
62
|
+
const output = admitOutput.parse(requireResultKind('value', result).value);
|
|
63
|
+
const callable = Key(output.callable);
|
|
64
|
+
if (!matchesCallable(target, callable)) {
|
|
65
|
+
throw new ClientError('Function admission returned a different callable.');
|
|
66
|
+
}
|
|
67
|
+
const receiver = output.receiver === undefined ? undefined : Path.parse(output.receiver);
|
|
68
|
+
const method = target.ast.steps.at(-1);
|
|
69
|
+
const instance = method?.kind === 'method' && method.dispatch === 'instance';
|
|
70
|
+
if (instance !== (receiver !== undefined) ||
|
|
71
|
+
(receiver !== undefined &&
|
|
72
|
+
(receiver.ast.anchor.kind !== 'id' ||
|
|
73
|
+
receiver.ast.steps.length !== 0 ||
|
|
74
|
+
(target.ast.anchor.kind === 'id' &&
|
|
75
|
+
target.ast.steps.length === 1 &&
|
|
76
|
+
receiver.ast.anchor.id !== target.ast.anchor.id)))) {
|
|
77
|
+
throw new ClientError('Function admission returned a different receiver.');
|
|
78
|
+
}
|
|
79
|
+
return Object.freeze({ callable, ...(receiver === undefined ? {} : { receiver: receiver.raw }) });
|
|
80
|
+
}
|
|
81
|
+
function callableOrigin(path) {
|
|
82
|
+
const last = path.ast.steps.at(-1);
|
|
83
|
+
if (last?.kind === 'method' && last.dispatch === 'instance')
|
|
84
|
+
return Key.origin(last.class);
|
|
85
|
+
if (path.ast.anchor.kind === 'domain')
|
|
86
|
+
return path.ast.anchor.origin;
|
|
87
|
+
throw new ClientError('Function admission requires a Domain-qualified callable target.');
|
|
88
|
+
}
|
|
89
|
+
async function fetchJson(dependencies, url, operation, credential) {
|
|
90
|
+
allowEndpoint(url, dependencies.policy);
|
|
91
|
+
return runSessionOperation(operation, async (signal) => {
|
|
92
|
+
const init = {
|
|
93
|
+
method: credential === undefined ? 'GET' : 'POST',
|
|
94
|
+
headers: {
|
|
95
|
+
accept: 'application/json',
|
|
96
|
+
...(credential === undefined ? {} : { authorization: `Bearer ${credential}` }),
|
|
97
|
+
},
|
|
98
|
+
credentials: 'omit',
|
|
99
|
+
cache: 'no-store',
|
|
100
|
+
referrerPolicy: 'no-referrer',
|
|
101
|
+
redirect: 'manual',
|
|
102
|
+
signal,
|
|
103
|
+
};
|
|
104
|
+
const response = await dependencies.fetch(url, init);
|
|
105
|
+
try {
|
|
106
|
+
requireExactResponse(response, url, 'Domain token exchange');
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
throw new ClientError(`Domain ${credential === undefined ? 'discovery' : 'token exchange'} returned HTTP ${response.status}.`);
|
|
109
|
+
}
|
|
110
|
+
return codecs.json.decode(await boundedResponse(response, dependencies.maximumResponseBytes, signal));
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
// Also release unread bodies rejected by URL, status, or declared-size validation.
|
|
114
|
+
void response.body?.cancel().catch(() => undefined);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
package/dist/session/bound.d.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import type { IssuerId } from '@astrale-os/kernel-core/auth';
|
|
2
|
+
import type { AuthApi } from '../auth/index.js';
|
|
2
3
|
import type { SchemaApi } from '../schema/index.js';
|
|
3
4
|
import type { SessionControlOptions } from './context/index.js';
|
|
4
5
|
import type { CallableReference, InputOfReference, OutputOfReference } from './reference.js';
|
|
5
6
|
import type { SessionRequestOptions } from './routing/index.js';
|
|
6
7
|
import type { ClientSession } from './session.js';
|
|
7
8
|
import type { ViewCatalog } from './views/index.js';
|
|
8
|
-
import { type AuthApi } from '../auth/index.js';
|
|
9
9
|
import { type Binary, type Call, type Input, type Result, type Stream } from '../client/index.js';
|
|
10
10
|
import { type GraphApi } from '../graph/index.js';
|
|
11
11
|
/** Exact live Session surface captured by one credential-bound Kernel view. */
|
|
12
12
|
export interface BoundSessionSource {
|
|
13
13
|
readonly kernel: IssuerId;
|
|
14
14
|
readonly schema: SchemaApi;
|
|
15
|
+
readonly auth: Pick<AuthApi, 'admit'>;
|
|
15
16
|
dispatch(call: Call, options?: SessionRequestOptions): Promise<Result>;
|
|
16
17
|
call(call: Call, options?: SessionRequestOptions): Promise<Input>;
|
|
17
18
|
invoke<const Reference extends CallableReference<unknown>>(reference: Reference, input: InputOfReference<Reference>, options?: SessionRequestOptions): Promise<OutputOfReference<Reference>>;
|
package/dist/session/bound.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createAuth } from '../auth/
|
|
1
|
+
import { createAuth } from '../auth/auth.js';
|
|
2
2
|
import { call as createCall, } from '../client/index.js';
|
|
3
3
|
import { createGraph } from '../graph/index.js';
|
|
4
4
|
/** Capture one Session into the ordinary graph, auth, and invocation Kernel journey. */
|
|
@@ -7,6 +7,7 @@ export function bindSession(source, controls) {
|
|
|
7
7
|
invalid();
|
|
8
8
|
const admittedControls = admitControls(controls);
|
|
9
9
|
const dispatchSource = capture(source, 'dispatch');
|
|
10
|
+
const admitSource = capture(source.auth, 'admit');
|
|
10
11
|
const callSource = capture(source, 'call');
|
|
11
12
|
const invokeSource = capture(source, 'invoke');
|
|
12
13
|
const streamSource = capture(source, 'stream');
|
|
@@ -14,6 +15,7 @@ export function bindSession(source, controls) {
|
|
|
14
15
|
const viewsForSource = capture(source, 'viewsFor');
|
|
15
16
|
const schemaSource = captureSchema(source.schema);
|
|
16
17
|
const dispatch = (input, options) => dispatchSource(input, requestOptions(options, admittedControls));
|
|
18
|
+
const admit = (input, options) => admitSource(input, mergeOptions(options, admittedControls));
|
|
17
19
|
const call = (input, options) => callSource(input, requestOptions(options, admittedControls));
|
|
18
20
|
const invoke = (reference, input, options) => invokeSource(reference, input, requestOptions(options, admittedControls));
|
|
19
21
|
const stream = (input, options) => streamSource(input, requestOptions(options, admittedControls));
|
|
@@ -26,7 +28,7 @@ export function bindSession(source, controls) {
|
|
|
26
28
|
const dependencies = (origin, options) => schemaSource.dependencies(origin, controlOptions(options, admittedControls));
|
|
27
29
|
const schema = Object.freeze({ install, uninstall, inspect, bundle, dependencies });
|
|
28
30
|
const graph = createGraph(call);
|
|
29
|
-
const auth = createAuth((target, input, options) => call(createCall(target, input), options));
|
|
31
|
+
const auth = createAuth((target, input, options) => call(createCall(target, input), options), admit);
|
|
30
32
|
return Object.freeze({
|
|
31
33
|
...graph,
|
|
32
34
|
kernel: source.kernel,
|
|
@@ -4,6 +4,7 @@ import type { Fetch } from '../../transport/http/index.js';
|
|
|
4
4
|
import type { SessionOperation, SessionPolicy } from '../context/index.js';
|
|
5
5
|
import type { SessionSnapshot } from './snapshot.js';
|
|
6
6
|
export type { SessionSnapshot } from './snapshot.js';
|
|
7
|
+
export { boundedResponse, requireExactResponse } from './publication.js';
|
|
7
8
|
export interface SessionDiscovery {
|
|
8
9
|
readonly kernel: IssuerId;
|
|
9
10
|
publication(operation: SessionOperation): Promise<publication.Publication>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BundleDiscovery } from './bundle.js';
|
|
2
2
|
import { SessionDiscovery as SessionDiscoveryImplementation } from './discovery.js';
|
|
3
3
|
import { PublicationDiscovery } from './publication.js';
|
|
4
|
+
export { boundedResponse, requireExactResponse } from './publication.js';
|
|
4
5
|
export function createSessionDiscovery(options) {
|
|
5
6
|
return new SessionDiscoveryImplementation(options.kernel, options.policy, new PublicationDiscovery({
|
|
6
7
|
kernel: options.kernel,
|
|
@@ -37,4 +37,5 @@ export declare function reference<const Callable extends ResolvedFunction | Exec
|
|
|
37
37
|
/** Project one exact resolved instance Method into a receiver-bound typed Client reference. */
|
|
38
38
|
export declare function reference<const Callable extends ExecutableInstanceMethod>(domain: Pick<Domain, 'origin' | 'revision'>, callable: Callable): InstanceCallableReference<ReferenceInput<Callable>, ReferenceOutput<Callable>>;
|
|
39
39
|
export declare function admitReference(input: CallableReference<unknown>): CallableReference<unknown>;
|
|
40
|
+
export declare function matchesCallable(target: PathValue, callable: KeyType.Callable): boolean;
|
|
40
41
|
export {};
|
|
@@ -52,7 +52,7 @@ export function admitReference(input) {
|
|
|
52
52
|
outputMode: input.outputMode,
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
|
-
function matchesCallable(target, callable) {
|
|
55
|
+
export function matchesCallable(target, callable) {
|
|
56
56
|
const coordinate = callableAddress(callable);
|
|
57
57
|
const { anchor, steps } = target.ast;
|
|
58
58
|
const last = steps.at(-1);
|
|
@@ -2,6 +2,7 @@ import type { IssuerId } from '@astrale-os/kernel-core/auth/issuer';
|
|
|
2
2
|
import type { PathLike } from '@astrale-os/kernel-core/path';
|
|
3
3
|
import type { Domain } from '@astrale-os/kernel-dsl/v1/domain';
|
|
4
4
|
import type { publication } from '@astrale-os/kernel-protocol';
|
|
5
|
+
import type { AuthApi } from '../auth/index.js';
|
|
5
6
|
import type { Call, Input, Result } from '../client/index.js';
|
|
6
7
|
import type { DomainBinding, GraphApi } from '../graph/index.js';
|
|
7
8
|
import type { ClientFactory, ClientPool as ClientPoolType } from '../pool/index.js';
|
|
@@ -42,6 +43,7 @@ export declare class ClientSession {
|
|
|
42
43
|
readonly kernel: IssuerId;
|
|
43
44
|
readonly schema: SchemaApi;
|
|
44
45
|
readonly graph: GraphApi;
|
|
46
|
+
readonly auth: AuthApi;
|
|
45
47
|
private readonly ownedPool?;
|
|
46
48
|
private readonly discovery;
|
|
47
49
|
private readonly dispatcher;
|
package/dist/session/session.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Path } from '@astrale-os/kernel-core/path';
|
|
2
2
|
import { callableFingerprint } from '@astrale-os/kernel-dsl/v1/schema/fingerprint';
|
|
3
|
-
import {
|
|
3
|
+
import { createAuth } from '../auth/auth.js';
|
|
4
|
+
import { Client, acceptCall } from '../client/index.js';
|
|
4
5
|
import { SessionError } from '../errors/index.js';
|
|
5
6
|
import { createDomainBinding } from '../graph/domain/binding.js';
|
|
6
7
|
import { createGraph } from '../graph/index.js';
|
|
@@ -9,7 +10,8 @@ import { createSchema, DEFAULT_SCHEMA_LIFECYCLE_TIMEOUT_MS } from '../schema/ind
|
|
|
9
10
|
import { AutoTransport } from '../transport/auto/index.js';
|
|
10
11
|
import { HttpTransport } from '../transport/http/index.js';
|
|
11
12
|
import { WebSocketTransport } from '../transport/websocket/index.js';
|
|
12
|
-
import {
|
|
13
|
+
import { admitFunction } from './admission.js';
|
|
14
|
+
import { admitControlOptions, admitPolicy, createSessionOperation, DEFAULT_MAXIMUM_BUNDLE_BYTES, DEFAULT_MAXIMUM_PUBLICATION_BYTES, DEFAULT_MAXIMUM_RESPONSE_BYTES, DEFAULT_TIMEOUT_MS, admitKernel, allowEndpoint, kernelInvocationEndpoint, platformDuration, positive, } from './context/index.js';
|
|
13
15
|
import { createSessionDiscovery } from './discovery/index.js';
|
|
14
16
|
import { admitReference } from './reference.js';
|
|
15
17
|
import { admitEnvelopeTransport, admitRequestOptions, admitSessionAuth, requireResultKind, RouteCache, SessionDispatcher, } from './routing/index.js';
|
|
@@ -20,6 +22,7 @@ export class ClientSession {
|
|
|
20
22
|
kernel;
|
|
21
23
|
schema;
|
|
22
24
|
graph;
|
|
25
|
+
auth;
|
|
23
26
|
ownedPool;
|
|
24
27
|
discovery;
|
|
25
28
|
dispatcher;
|
|
@@ -80,12 +83,25 @@ export class ClientSession {
|
|
|
80
83
|
acquisitionOperation: () => this.operation(undefined),
|
|
81
84
|
});
|
|
82
85
|
this.routes = new RouteCache(policy.maximumRouteAgeMs, undefined, options.routeStore);
|
|
86
|
+
const auth = admitSessionAuth(options.auth);
|
|
87
|
+
const admission = {
|
|
88
|
+
kernel: this.kernel,
|
|
89
|
+
auth,
|
|
90
|
+
source: () => {
|
|
91
|
+
allowEndpoint(sourceEndpoint, policy);
|
|
92
|
+
return pool.clientFor({ transport: 'http', url: sourceEndpoint });
|
|
93
|
+
},
|
|
94
|
+
fetch: fetchImpl,
|
|
95
|
+
policy,
|
|
96
|
+
limits: { maximumPublicationBytes, maximumBundleBytes },
|
|
97
|
+
maximumResponseBytes,
|
|
98
|
+
};
|
|
83
99
|
this.dispatcher = new SessionDispatcher({
|
|
84
100
|
kernel: this.kernel,
|
|
85
101
|
sourceEndpoint,
|
|
86
102
|
pool,
|
|
87
103
|
websocketAvailable,
|
|
88
|
-
auth
|
|
104
|
+
auth,
|
|
89
105
|
cache: this.routes,
|
|
90
106
|
policy,
|
|
91
107
|
envelopeTransport: admitEnvelopeTransport(options.envelopeTransport ?? 'http'),
|
|
@@ -97,6 +113,10 @@ export class ClientSession {
|
|
|
97
113
|
lifecycleTimeoutMs: schemaLifecycleTimeoutMs,
|
|
98
114
|
});
|
|
99
115
|
this.graph = createGraph((call, options) => this.call(call, options));
|
|
116
|
+
this.auth = createAuth((target, input, options) => this.call({ target, input }, options), (call, options) => {
|
|
117
|
+
const operation = this.operation(admitControlOptions(options));
|
|
118
|
+
return admitFunction(admission, acceptCall(call), options, operation);
|
|
119
|
+
});
|
|
100
120
|
}
|
|
101
121
|
/** Bind one admitted DSL Domain to authenticated graph projection and Method dispatch. */
|
|
102
122
|
bind(domain) {
|
|
@@ -12,6 +12,13 @@ void request;
|
|
|
12
12
|
void snapshot;
|
|
13
13
|
const observedPublication = session.publication(control);
|
|
14
14
|
void observedPublication;
|
|
15
|
+
const admitted = session.auth.admit({
|
|
16
|
+
target: Path.parse('/:example.test:function.write'),
|
|
17
|
+
input: {},
|
|
18
|
+
}, { attenuation: { kind: 'identity', self: true }, ...control });
|
|
19
|
+
void admitted;
|
|
20
|
+
// @ts-expect-error Admission belongs to Auth, not the top-level Session.
|
|
21
|
+
void session.admit({ target: Path.parse('/:example.test:function.write'), input: {} });
|
|
15
22
|
const credentials = createSessionCredentialProvider({
|
|
16
23
|
ttlSeconds: 60,
|
|
17
24
|
mint: async () => minted,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrale-os/kernel-client",
|
|
3
|
-
"version": "0.6.0-beta.
|
|
3
|
+
"version": "0.6.0-beta.71",
|
|
4
4
|
"description": "Universal Client — protocol-first, transport-agnostic",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astrale",
|
|
@@ -65,18 +65,18 @@
|
|
|
65
65
|
"access": "public"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@astrale-os/kernel-core": "0.9.0-beta.
|
|
69
|
-
"@astrale-os/kernel-dsl": "0.2.0-beta.
|
|
70
|
-
"@astrale-os/kernel-protocol": "0.5.0-beta.
|
|
68
|
+
"@astrale-os/kernel-core": "0.9.0-beta.54",
|
|
69
|
+
"@astrale-os/kernel-dsl": "0.2.0-beta.39",
|
|
70
|
+
"@astrale-os/kernel-protocol": "0.5.0-beta.60",
|
|
71
71
|
"@tanstack/pacer": "0.20.1",
|
|
72
72
|
"msgpackr": "^2.0.5"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
|
-
"@astrale-os/kernel-server": "0.5.0-beta.
|
|
75
|
+
"@astrale-os/kernel-server": "0.5.0-beta.62",
|
|
76
76
|
"@astrale/typescript-config": "npm:@jsr/astrale__typescript-config@~1.1.0",
|
|
77
77
|
"@types/node": "^22.0.0",
|
|
78
78
|
"typescript": "7.0.2",
|
|
79
|
-
"vitest": "^
|
|
79
|
+
"vitest": "^5.0.1"
|
|
80
80
|
},
|
|
81
81
|
"scripts": {
|
|
82
82
|
"build": "tsc",
|