@astrale-os/kernel-server 0.5.0-beta.1 → 0.5.0-beta.10
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/capture.d.ts +0 -1
- package/dist/capture.js +0 -1
- package/dist/cors/cors.d.ts +0 -1
- package/dist/cors/cors.js +0 -1
- package/dist/cors/index.d.ts +0 -1
- package/dist/cors/index.js +0 -1
- package/dist/cors/types.d.ts +0 -1
- package/dist/cors/types.js +0 -1
- package/dist/errors/availability.d.ts +3 -4
- package/dist/errors/availability.js +4 -5
- package/dist/errors/classify.d.ts +2 -3
- package/dist/errors/classify.js +4 -217
- package/dist/errors/index.d.ts +1 -2
- package/dist/errors/index.js +1 -2
- package/dist/errors/request-body.d.ts +0 -1
- package/dist/errors/request-body.js +0 -1
- package/dist/errors/response-body.d.ts +0 -1
- package/dist/errors/response-body.js +0 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/invocation/context.d.ts +2 -3
- package/dist/invocation/context.js +2 -4
- package/dist/invocation/identity.d.ts +9 -0
- package/dist/invocation/identity.js +11 -0
- package/dist/invocation/index.d.ts +3 -2
- package/dist/invocation/index.js +2 -1
- package/dist/invocation/publication-route.d.ts +13 -0
- package/dist/invocation/publication-route.js +44 -0
- package/dist/invocation/types.d.ts +38 -10
- package/dist/invocation/types.js +0 -1
- package/dist/issuer/exchange.d.ts +12 -0
- package/dist/issuer/exchange.js +136 -0
- package/dist/issuer/index.d.ts +1 -1
- package/dist/issuer/index.js +0 -1
- package/dist/issuer/routes.d.ts +3 -3
- package/dist/issuer/routes.js +25 -14
- package/dist/issuer/types.d.ts +2 -2
- package/dist/issuer/types.js +0 -1
- package/dist/lifecycle/active.d.ts +0 -1
- package/dist/lifecycle/active.js +0 -1
- package/dist/lifecycle/index.d.ts +0 -1
- package/dist/lifecycle/index.js +0 -1
- package/dist/publication/delivery/handler.d.ts +9 -0
- package/dist/{installation → publication/delivery}/handler.js +29 -28
- package/dist/publication/delivery/index.d.ts +2 -0
- package/dist/publication/delivery/index.js +1 -0
- package/dist/publication/delivery/types.d.ts +11 -0
- package/dist/publication/delivery/types.js +1 -0
- package/dist/publication/index.d.ts +1 -1
- package/dist/publication/index.js +1 -1
- package/dist/publication/snapshot.d.ts +5 -3
- package/dist/publication/snapshot.js +14 -7
- package/dist/publication/table.d.ts +4 -0
- package/dist/publication/table.js +16 -0
- package/dist/publication/types.d.ts +5 -2
- package/dist/publication/types.js +0 -1
- package/dist/routes/index.d.ts +0 -1
- package/dist/routes/index.js +0 -1
- package/dist/routes/routes.d.ts +6 -5
- package/dist/routes/routes.js +51 -41
- package/dist/server.d.ts +0 -1
- package/dist/server.js +56 -16
- package/dist/transport/body.d.ts +0 -1
- package/dist/transport/body.js +0 -1
- package/dist/transport/codecs.d.ts +1 -2
- package/dist/transport/codecs.js +1 -2
- package/dist/transport/credential.d.ts +1 -2
- package/dist/transport/credential.js +1 -2
- package/dist/transport/http/handler.d.ts +2 -1
- package/dist/transport/http/handler.js +47 -37
- package/dist/transport/http/index.d.ts +0 -1
- package/dist/transport/http/index.js +0 -1
- package/dist/transport/http/render.d.ts +13 -11
- package/dist/transport/http/render.js +50 -39
- package/dist/transport/index.d.ts +0 -1
- package/dist/transport/index.js +0 -1
- package/dist/transport/router.d.ts +0 -1
- package/dist/transport/router.js +0 -1
- package/dist/transport/stream-error.d.ts +2 -3
- package/dist/transport/stream-error.js +3 -4
- package/dist/transport/types.d.ts +1 -2
- package/dist/transport/types.js +0 -1
- package/dist/transport/websocket/index.d.ts +0 -1
- package/dist/transport/websocket/index.js +0 -1
- package/dist/transport/websocket/session.d.ts +4 -3
- package/dist/transport/websocket/session.js +46 -39
- package/dist/transport/websocket/upgrade.d.ts +2 -1
- package/dist/transport/websocket/upgrade.js +2 -2
- package/dist/types.d.ts +13 -10
- package/dist/types.js +0 -1
- package/package.json +23 -11
- package/dist/capture.d.ts.map +0 -1
- package/dist/capture.js.map +0 -1
- package/dist/cors/cors.d.ts.map +0 -1
- package/dist/cors/cors.js.map +0 -1
- package/dist/cors/index.d.ts.map +0 -1
- package/dist/cors/index.js.map +0 -1
- package/dist/cors/types.d.ts.map +0 -1
- package/dist/cors/types.js.map +0 -1
- package/dist/errors/availability.d.ts.map +0 -1
- package/dist/errors/availability.js.map +0 -1
- package/dist/errors/classify.d.ts.map +0 -1
- package/dist/errors/classify.js.map +0 -1
- package/dist/errors/index.d.ts.map +0 -1
- package/dist/errors/index.js.map +0 -1
- package/dist/errors/request-body.d.ts.map +0 -1
- package/dist/errors/request-body.js.map +0 -1
- package/dist/errors/response-body.d.ts.map +0 -1
- package/dist/errors/response-body.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/installation/handler.d.ts +0 -10
- package/dist/installation/handler.d.ts.map +0 -1
- package/dist/installation/handler.js.map +0 -1
- package/dist/installation/index.d.ts +0 -3
- package/dist/installation/index.d.ts.map +0 -1
- package/dist/installation/index.js +0 -2
- package/dist/installation/index.js.map +0 -1
- package/dist/installation/types.d.ts +0 -12
- package/dist/installation/types.d.ts.map +0 -1
- package/dist/installation/types.js +0 -2
- package/dist/installation/types.js.map +0 -1
- package/dist/invocation/context.d.ts.map +0 -1
- package/dist/invocation/context.js.map +0 -1
- package/dist/invocation/index.d.ts.map +0 -1
- package/dist/invocation/index.js.map +0 -1
- package/dist/invocation/types.d.ts.map +0 -1
- package/dist/invocation/types.js.map +0 -1
- package/dist/issuer/index.d.ts.map +0 -1
- package/dist/issuer/index.js.map +0 -1
- package/dist/issuer/routes.d.ts.map +0 -1
- package/dist/issuer/routes.js.map +0 -1
- package/dist/issuer/types.d.ts.map +0 -1
- package/dist/issuer/types.js.map +0 -1
- package/dist/lifecycle/active.d.ts.map +0 -1
- package/dist/lifecycle/active.js.map +0 -1
- package/dist/lifecycle/index.d.ts.map +0 -1
- package/dist/lifecycle/index.js.map +0 -1
- package/dist/publication/index.d.ts.map +0 -1
- package/dist/publication/index.js.map +0 -1
- package/dist/publication/snapshot.d.ts.map +0 -1
- package/dist/publication/snapshot.js.map +0 -1
- package/dist/publication/types.d.ts.map +0 -1
- package/dist/publication/types.js.map +0 -1
- package/dist/routes/index.d.ts.map +0 -1
- package/dist/routes/index.js.map +0 -1
- package/dist/routes/routes.d.ts.map +0 -1
- package/dist/routes/routes.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/transport/body.d.ts.map +0 -1
- package/dist/transport/body.js.map +0 -1
- package/dist/transport/codecs.d.ts.map +0 -1
- package/dist/transport/codecs.js.map +0 -1
- package/dist/transport/credential.d.ts.map +0 -1
- package/dist/transport/credential.js.map +0 -1
- package/dist/transport/http/handler.d.ts.map +0 -1
- package/dist/transport/http/handler.js.map +0 -1
- package/dist/transport/http/index.d.ts.map +0 -1
- package/dist/transport/http/index.js.map +0 -1
- package/dist/transport/http/render.d.ts.map +0 -1
- package/dist/transport/http/render.js.map +0 -1
- package/dist/transport/index.d.ts.map +0 -1
- package/dist/transport/index.js.map +0 -1
- package/dist/transport/router.d.ts.map +0 -1
- package/dist/transport/router.js.map +0 -1
- package/dist/transport/stream-error.d.ts.map +0 -1
- package/dist/transport/stream-error.js.map +0 -1
- package/dist/transport/types.d.ts.map +0 -1
- package/dist/transport/types.js.map +0 -1
- package/dist/transport/websocket/index.d.ts.map +0 -1
- package/dist/transport/websocket/index.js.map +0 -1
- package/dist/transport/websocket/session.d.ts.map +0 -1
- package/dist/transport/websocket/session.js.map +0 -1
- package/dist/transport/websocket/upgrade.d.ts.map +0 -1
- package/dist/transport/websocket/upgrade.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { inspect as inspectCredential } from '@astrale-os/kernel-core/auth/credential';
|
|
2
|
+
import * as errors from '@astrale-os/kernel-protocol/errors';
|
|
3
|
+
import * as exchangeProtocol from '@astrale-os/kernel-protocol/issuer/exchange';
|
|
4
|
+
import { paths as issuerPaths } from '@astrale-os/kernel-protocol/issuer/paths';
|
|
5
|
+
const PROVIDER_CODES = new Set([
|
|
6
|
+
errors.CODES.AUTH_INVALID,
|
|
7
|
+
errors.CODES.AUTH_EXPIRED,
|
|
8
|
+
errors.CODES.BACKEND_UNAVAILABLE,
|
|
9
|
+
errors.CODES.DEADLINE_EXCEEDED,
|
|
10
|
+
]);
|
|
11
|
+
export function capture(input) {
|
|
12
|
+
if (input === null || typeof input !== 'object' || typeof input.exchange !== 'function') {
|
|
13
|
+
throw new TypeError('Token-exchange provider is invalid.');
|
|
14
|
+
}
|
|
15
|
+
const exchange = input.exchange;
|
|
16
|
+
return Object.freeze({
|
|
17
|
+
exchange: (request, context) => exchange.call(input, request, context),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export function mount(app, issuer, provider, cors, active, timeoutMs) {
|
|
21
|
+
const path = issuerPaths(issuer).tokenExchange;
|
|
22
|
+
if (cors.enabled) {
|
|
23
|
+
app.options(path, (context) => cors.preflight(context.req.raw, { methods: ['POST'], headers: ['authorization', 'accept'] }));
|
|
24
|
+
}
|
|
25
|
+
app.all(path, async (context) => {
|
|
26
|
+
const request = context.req.raw;
|
|
27
|
+
const respond = (payload, extra) => json(payload, errors.httpStatusOf(payload.error), request, cors, extra);
|
|
28
|
+
let admitted;
|
|
29
|
+
try {
|
|
30
|
+
admitted = exchangeProtocol.acceptHttpRequest({
|
|
31
|
+
method: request.method,
|
|
32
|
+
url: request.url,
|
|
33
|
+
authorization: request.headers.get('authorization'),
|
|
34
|
+
accept: request.headers.get('accept'),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch (cause) {
|
|
38
|
+
if (cause instanceof errors.ProtocolError) {
|
|
39
|
+
return respond(exchangeProtocol.acceptErrorResponse({ error: cause.payload }), cause.payload.code === errors.CODES.METHOD_NOT_ALLOWED ? { allow: 'POST' } : undefined);
|
|
40
|
+
}
|
|
41
|
+
throw cause;
|
|
42
|
+
}
|
|
43
|
+
if (!active.isAccepting) {
|
|
44
|
+
return respond(error('BACKEND_UNAVAILABLE', 'Token exchange is unavailable.'));
|
|
45
|
+
}
|
|
46
|
+
const operation = active.begin(timeoutMs, request.signal);
|
|
47
|
+
if (operation === undefined) {
|
|
48
|
+
return respond(error('LIMIT_EXCEEDED', 'Token-exchange limit exceeded.'));
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
try {
|
|
52
|
+
await requireEmptyBody(request, operation.signal);
|
|
53
|
+
}
|
|
54
|
+
catch (cause) {
|
|
55
|
+
if (operation.timedOut) {
|
|
56
|
+
return respond(error('DEADLINE_EXCEEDED', 'Token-exchange deadline elapsed.'));
|
|
57
|
+
}
|
|
58
|
+
if (cause instanceof errors.ProtocolError) {
|
|
59
|
+
return respond(exchangeProtocol.acceptErrorResponse({ error: cause.payload }));
|
|
60
|
+
}
|
|
61
|
+
throw cause;
|
|
62
|
+
}
|
|
63
|
+
let output;
|
|
64
|
+
try {
|
|
65
|
+
output = await provider.exchange(admitted, { signal: operation.signal });
|
|
66
|
+
}
|
|
67
|
+
catch (cause) {
|
|
68
|
+
if (operation.timedOut) {
|
|
69
|
+
return respond(error('DEADLINE_EXCEEDED', 'Token-exchange deadline elapsed.'));
|
|
70
|
+
}
|
|
71
|
+
if (cause instanceof errors.ProtocolError && PROVIDER_CODES.has(cause.payload.code)) {
|
|
72
|
+
return respond(exchangeProtocol.acceptErrorResponse({ error: cause.payload }));
|
|
73
|
+
}
|
|
74
|
+
throw cause;
|
|
75
|
+
}
|
|
76
|
+
if (operation.timedOut) {
|
|
77
|
+
return respond(error('DEADLINE_EXCEEDED', 'Token-exchange deadline elapsed.'));
|
|
78
|
+
}
|
|
79
|
+
const response = exchangeProtocol.acceptResponse(output);
|
|
80
|
+
if (inspectCredential(response.token).claims.exp !== response.expiresAt) {
|
|
81
|
+
throw new TypeError('Token-exchange provider returned inconsistent expiry evidence.');
|
|
82
|
+
}
|
|
83
|
+
return json(response, 200, request, cors);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return respond(error('INTERNAL_ERROR', 'Internal token-exchange failure.'));
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
operation.settle();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async function requireEmptyBody(request, signal) {
|
|
94
|
+
const declared = request.headers.get('content-length');
|
|
95
|
+
if (declared !== null && declared !== '0')
|
|
96
|
+
invalidRequest();
|
|
97
|
+
const reader = request.body?.getReader();
|
|
98
|
+
if (reader === undefined)
|
|
99
|
+
return;
|
|
100
|
+
const abort = () => void reader.cancel(signal.reason).catch(() => undefined);
|
|
101
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
102
|
+
try {
|
|
103
|
+
if (signal.aborted)
|
|
104
|
+
throw signal.reason;
|
|
105
|
+
const first = await reader.read();
|
|
106
|
+
if (!first.done) {
|
|
107
|
+
await reader.cancel('token exchange requires an empty body').catch(() => undefined);
|
|
108
|
+
invalidRequest();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
signal.removeEventListener('abort', abort);
|
|
113
|
+
reader.releaseLock();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function invalidRequest() {
|
|
117
|
+
throw new errors.ProtocolError({
|
|
118
|
+
code: errors.CODES.INVALID_REQUEST,
|
|
119
|
+
message: 'Token exchange requires an empty request body.',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function error(code, message) {
|
|
123
|
+
return exchangeProtocol.acceptErrorResponse({
|
|
124
|
+
error: { code: errors.CODES[code], message },
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function json(value, status, request, cors, extra) {
|
|
128
|
+
const headers = new Headers(exchangeProtocol.responseHeaders());
|
|
129
|
+
for (const [name, value] of Object.entries(cors.response(request)))
|
|
130
|
+
headers.set(name, value);
|
|
131
|
+
if (extra !== undefined) {
|
|
132
|
+
for (const [name, value] of new Headers(extra))
|
|
133
|
+
headers.set(name, value);
|
|
134
|
+
}
|
|
135
|
+
return new Response(JSON.stringify(value), { status, headers });
|
|
136
|
+
}
|
package/dist/issuer/index.d.ts
CHANGED
package/dist/issuer/index.js
CHANGED
package/dist/issuer/routes.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { CorsPolicy } from '../cors/index.js';
|
|
2
2
|
import type { ActiveOperations } from '../lifecycle/index.js';
|
|
3
3
|
import type { ServerRouter } from '../transport/index.js';
|
|
4
|
+
import type { Provider } from './exchange.js';
|
|
4
5
|
import type { Issuer } from './types.js';
|
|
5
6
|
export interface IssuerRouteReservation {
|
|
6
7
|
readonly path: string;
|
|
7
8
|
readonly methods: readonly string[];
|
|
8
9
|
}
|
|
9
|
-
export declare function issuerRouteReservations(input: Issuer): readonly IssuerRouteReservation[];
|
|
10
|
-
export declare function mountIssuer(app: ServerRouter, input: Issuer, cors: CorsPolicy, active: ActiveOperations, timeoutMs: number): void;
|
|
10
|
+
export declare function issuerRouteReservations(input: Issuer, tokenExchange?: boolean): readonly IssuerRouteReservation[];
|
|
11
|
+
export declare function mountIssuer(app: ServerRouter, input: Issuer, cors: CorsPolicy, active: ActiveOperations, timeoutMs: number, tokenExchange?: Provider): void;
|
|
11
12
|
/** Admit static discovery configuration before Runtime Boot performs any durable work. */
|
|
12
13
|
export declare function admitIssuer(input: Issuer): Issuer;
|
|
13
|
-
//# sourceMappingURL=routes.d.ts.map
|
package/dist/issuer/routes.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { configuration } from '@astrale-os/kernel-protocol/issuer/configuration';
|
|
2
|
+
import { hosted } from '@astrale-os/kernel-protocol/issuer/hosted';
|
|
3
|
+
import { keySet } from '@astrale-os/kernel-protocol/issuer/key-set';
|
|
4
|
+
import { paths as issuerPaths } from '@astrale-os/kernel-protocol/issuer/paths';
|
|
2
5
|
import { IssuerKeysUnavailableError } from '../errors/index.js';
|
|
3
|
-
|
|
4
|
-
|
|
6
|
+
import { mount } from './exchange.js';
|
|
7
|
+
export function issuerRouteReservations(input, tokenExchange = false) {
|
|
8
|
+
const paths = issuerPaths(input.issuer);
|
|
5
9
|
const reservations = [
|
|
6
10
|
Object.freeze({ path: paths.configuration, methods: Object.freeze(['GET']) }),
|
|
7
11
|
Object.freeze({ path: paths.keys, methods: Object.freeze(['GET']) }),
|
|
8
12
|
];
|
|
13
|
+
if (tokenExchange) {
|
|
14
|
+
reservations.push(Object.freeze({ path: paths.tokenExchange, methods: Object.freeze(['POST']) }));
|
|
15
|
+
}
|
|
9
16
|
if (input.loadIssuerPublicKeys !== undefined) {
|
|
10
17
|
const base = new URL(input.issuer).pathname.replace(/\/+$/u, '');
|
|
11
18
|
reservations.push(Object.freeze({
|
|
@@ -18,16 +25,18 @@ export function issuerRouteReservations(input) {
|
|
|
18
25
|
}
|
|
19
26
|
return Object.freeze(reservations);
|
|
20
27
|
}
|
|
21
|
-
export function mountIssuer(app, input, cors, active, timeoutMs) {
|
|
28
|
+
export function mountIssuer(app, input, cors, active, timeoutMs, tokenExchange) {
|
|
22
29
|
const issuer = input.issuer;
|
|
23
|
-
const paths =
|
|
30
|
+
const paths = issuerPaths(issuer);
|
|
24
31
|
const headers = (request) => ({
|
|
25
32
|
...cors.response(request),
|
|
26
33
|
'cache-control': 'no-cache',
|
|
27
34
|
});
|
|
28
35
|
const algorithm = input.signingAlgorithm ?? 'ES256';
|
|
29
|
-
const
|
|
30
|
-
|
|
36
|
+
const discovery = configuration(issuer, [algorithm], {
|
|
37
|
+
tokenExchange: tokenExchange !== undefined,
|
|
38
|
+
});
|
|
39
|
+
app.get(paths.configuration, (context) => context.json(discovery, 200, headers(context.req.raw)));
|
|
31
40
|
if (cors.enabled) {
|
|
32
41
|
app.options(paths.configuration, (context) => cors.preflight(context.req.raw, { methods: ['GET'] }));
|
|
33
42
|
app.options(paths.keys, (context) => cors.preflight(context.req.raw, { methods: ['GET'] }));
|
|
@@ -39,7 +48,7 @@ export function mountIssuer(app, input, cors, active, timeoutMs) {
|
|
|
39
48
|
}
|
|
40
49
|
try {
|
|
41
50
|
const keys = await input.loadPublicKeys(operation.signal);
|
|
42
|
-
return context.json(
|
|
51
|
+
return context.json(keySet(keys, [algorithm]), 200, headers(context.req.raw));
|
|
43
52
|
}
|
|
44
53
|
catch (cause) {
|
|
45
54
|
if (operation.signal.aborted || cause instanceof IssuerKeysUnavailableError) {
|
|
@@ -51,6 +60,9 @@ export function mountIssuer(app, input, cors, active, timeoutMs) {
|
|
|
51
60
|
operation.settle();
|
|
52
61
|
}
|
|
53
62
|
});
|
|
63
|
+
if (tokenExchange !== undefined) {
|
|
64
|
+
mount(app, issuer, tokenExchange, cors, active, timeoutMs);
|
|
65
|
+
}
|
|
54
66
|
const loadIssuerPublicKeys = input.loadIssuerPublicKeys;
|
|
55
67
|
if (loadIssuerPublicKeys !== undefined) {
|
|
56
68
|
const base = new URL(issuer).pathname.replace(/\/+$/, '');
|
|
@@ -59,7 +71,7 @@ export function mountIssuer(app, input, cors, active, timeoutMs) {
|
|
|
59
71
|
app.options(`${base}/iss/:thumbprint/.well-known/jwks.json`, (context) => cors.preflight(context.req.raw, { methods: ['GET'] }));
|
|
60
72
|
}
|
|
61
73
|
app.get(`${base}/iss/:thumbprint/.well-known/openid-configuration`, async (context) => {
|
|
62
|
-
const hostedIssuer =
|
|
74
|
+
const hostedIssuer = hosted(issuer, context.req.param('thumbprint'));
|
|
63
75
|
if (hostedIssuer === undefined)
|
|
64
76
|
return context.json({ error: 'Issuer not found.' }, 404, headers(context.req.raw));
|
|
65
77
|
const operation = active.begin(timeoutMs, context.req.raw.signal);
|
|
@@ -70,7 +82,7 @@ export function mountIssuer(app, input, cors, active, timeoutMs) {
|
|
|
70
82
|
const keys = await hostedKeys(loadIssuerPublicKeys, hostedIssuer, operation.signal, algorithm);
|
|
71
83
|
if (keys.length === 0)
|
|
72
84
|
return context.json({ error: 'Issuer not found.' }, 404, headers(context.req.raw));
|
|
73
|
-
return context.json(
|
|
85
|
+
return context.json(configuration(hostedIssuer, [algorithm]), 200, headers(context.req.raw));
|
|
74
86
|
}
|
|
75
87
|
catch (cause) {
|
|
76
88
|
if (operation.signal.aborted || cause instanceof IssuerKeysUnavailableError) {
|
|
@@ -83,7 +95,7 @@ export function mountIssuer(app, input, cors, active, timeoutMs) {
|
|
|
83
95
|
}
|
|
84
96
|
});
|
|
85
97
|
app.get(`${base}/iss/:thumbprint/.well-known/jwks.json`, async (context) => {
|
|
86
|
-
const hostedIssuer =
|
|
98
|
+
const hostedIssuer = hosted(issuer, context.req.param('thumbprint'));
|
|
87
99
|
if (hostedIssuer === undefined)
|
|
88
100
|
return context.json({ error: 'Issuer not found.' }, 404, headers(context.req.raw));
|
|
89
101
|
const operation = active.begin(timeoutMs, context.req.raw.signal);
|
|
@@ -109,12 +121,12 @@ export function mountIssuer(app, input, cors, active, timeoutMs) {
|
|
|
109
121
|
}
|
|
110
122
|
}
|
|
111
123
|
async function hostedKeys(load, issuer, signal, algorithm) {
|
|
112
|
-
return
|
|
124
|
+
return keySet(await load(issuer, signal), [algorithm]).keys;
|
|
113
125
|
}
|
|
114
126
|
/** Admit static discovery configuration before Runtime Boot performs any durable work. */
|
|
115
127
|
export function admitIssuer(input) {
|
|
116
128
|
const signingAlgorithm = input.signingAlgorithm ?? 'ES256';
|
|
117
|
-
const issuer =
|
|
129
|
+
const issuer = configuration(input.issuer, [signingAlgorithm]).issuer;
|
|
118
130
|
if (typeof input.loadPublicKeys !== 'function') {
|
|
119
131
|
throw new TypeError('Issuer public-key loader is invalid.');
|
|
120
132
|
}
|
|
@@ -135,4 +147,3 @@ export function admitIssuer(input) {
|
|
|
135
147
|
}),
|
|
136
148
|
});
|
|
137
149
|
}
|
|
138
|
-
//# sourceMappingURL=routes.js.map
|
package/dist/issuer/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { IssuerId
|
|
1
|
+
import type { IssuerId } from '@astrale-os/kernel-core/auth/issuer';
|
|
2
|
+
import type { JsonWebKey } from '@astrale-os/kernel-core/auth/jwk';
|
|
2
3
|
/** Public issuer material; Server never receives private signing material. */
|
|
3
4
|
export interface Issuer {
|
|
4
5
|
readonly issuer: IssuerId;
|
|
@@ -8,4 +9,3 @@ export interface Issuer {
|
|
|
8
9
|
/** Optional hosted-key discovery for issuers below `<issuer>/iss/:thumbprint`. */
|
|
9
10
|
readonly loadIssuerPublicKeys?: (issuer: IssuerId, signal: AbortSignal) => Promise<readonly JsonWebKey[]>;
|
|
10
11
|
}
|
|
11
|
-
//# sourceMappingURL=types.d.ts.map
|
package/dist/issuer/types.js
CHANGED
package/dist/lifecycle/active.js
CHANGED
package/dist/lifecycle/index.js
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ActiveOperations } from '../../lifecycle/index.js';
|
|
2
|
+
import type { PreparedPublicationSnapshot } from '../snapshot.js';
|
|
3
|
+
import type { Provider } from './types.js';
|
|
4
|
+
export interface Limits {
|
|
5
|
+
readonly maximumDeliveryBytes: number;
|
|
6
|
+
readonly invocationTimeoutMs: number;
|
|
7
|
+
}
|
|
8
|
+
/** Serve one delivery request against the exact Publication snapshot that selected its URL. */
|
|
9
|
+
export declare function handleDelivery(request: Request, snapshot: PreparedPublicationSnapshot, provider: Provider | undefined, active: ActiveOperations, limits: Limits, cors: Readonly<Record<string, string>>): Promise<Response>;
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import * as codecs from '@astrale-os/kernel-protocol/codecs';
|
|
2
|
+
import * as protocolPublication from '@astrale-os/kernel-protocol/publication';
|
|
3
|
+
import { DeliveryUnavailableError } from '../../errors/index.js';
|
|
4
|
+
import { RequestBodyInvalidError, RequestBodyTooLargeError } from '../../errors/index.js';
|
|
5
|
+
import { readBoundedBody } from '../../transport/index.js';
|
|
6
|
+
const delivery = protocolPublication.delivery;
|
|
7
|
+
/** Serve one delivery request against the exact Publication snapshot that selected its URL. */
|
|
8
|
+
export async function handleDelivery(request, snapshot, provider, active, limits, cors) {
|
|
7
9
|
if (request.method !== 'POST') {
|
|
8
|
-
return response({ error: '
|
|
10
|
+
return response({ error: 'Delivery requires POST.' }, 405, cors);
|
|
9
11
|
}
|
|
10
12
|
if (provider === undefined) {
|
|
11
|
-
return response({ error: '
|
|
13
|
+
return response({ error: 'Delivery is unavailable.' }, 503, cors);
|
|
12
14
|
}
|
|
13
|
-
const advertised = snapshot.publication.
|
|
15
|
+
const advertised = snapshot.publication.delivery;
|
|
14
16
|
if (advertised === undefined) {
|
|
15
|
-
return response({ error: '
|
|
17
|
+
return response({ error: 'Delivery is unavailable.' }, 404, cors);
|
|
16
18
|
}
|
|
17
19
|
const operation = active.begin(limits.invocationTimeoutMs, request.signal);
|
|
18
20
|
if (operation === undefined)
|
|
@@ -24,21 +26,21 @@ export async function handleInstallation(request, snapshot, provider, active, li
|
|
|
24
26
|
return response({ error: 'Bearer authentication is required.' }, 401, cors);
|
|
25
27
|
}
|
|
26
28
|
if (advertised.authentication.kind === 'public' && authorization !== null) {
|
|
27
|
-
return response({ error: 'This
|
|
29
|
+
return response({ error: 'This delivery endpoint is public.' }, 400, cors);
|
|
28
30
|
}
|
|
29
31
|
let body;
|
|
30
32
|
try {
|
|
31
|
-
body = await readBoundedBody(request, limits.
|
|
33
|
+
body = await readBoundedBody(request, limits.maximumDeliveryBytes, operation.signal);
|
|
32
34
|
}
|
|
33
35
|
catch (cause) {
|
|
34
36
|
if (cause instanceof RequestBodyTooLargeError) {
|
|
35
|
-
return response({ error: '
|
|
37
|
+
return response({ error: 'Delivery request is too large.' }, 413, cors);
|
|
36
38
|
}
|
|
37
39
|
if (cause instanceof RequestBodyInvalidError) {
|
|
38
|
-
return response({ error: '
|
|
40
|
+
return response({ error: 'Delivery request is invalid.' }, 400, cors);
|
|
39
41
|
}
|
|
40
42
|
if (operation.signal.aborted) {
|
|
41
|
-
return response({ error: '
|
|
43
|
+
return response({ error: 'Delivery request was cancelled.' }, 503, cors);
|
|
42
44
|
}
|
|
43
45
|
throw cause;
|
|
44
46
|
}
|
|
@@ -48,17 +50,17 @@ export async function handleInstallation(request, snapshot, provider, active, li
|
|
|
48
50
|
}
|
|
49
51
|
catch (cause) {
|
|
50
52
|
if (cause instanceof codecs.DecodeError) {
|
|
51
|
-
return response({ error: '
|
|
53
|
+
return response({ error: 'Delivery request is invalid.' }, 400, cors);
|
|
52
54
|
}
|
|
53
55
|
throw cause;
|
|
54
56
|
}
|
|
55
57
|
let input;
|
|
56
58
|
try {
|
|
57
|
-
input =
|
|
59
|
+
input = delivery.acceptRequest(decoded);
|
|
58
60
|
}
|
|
59
61
|
catch (cause) {
|
|
60
|
-
if (cause instanceof
|
|
61
|
-
return response({ error: '
|
|
62
|
+
if (cause instanceof delivery.Error && cause.code === 'DELIVERY_REQUEST_INVALID') {
|
|
63
|
+
return response({ error: 'Delivery request is invalid.' }, 400, cors);
|
|
62
64
|
}
|
|
63
65
|
throw cause;
|
|
64
66
|
}
|
|
@@ -73,27 +75,27 @@ export async function handleInstallation(request, snapshot, provider, active, li
|
|
|
73
75
|
});
|
|
74
76
|
}
|
|
75
77
|
catch (cause) {
|
|
76
|
-
if (cause instanceof
|
|
77
|
-
return response({ error: '
|
|
78
|
+
if (cause instanceof DeliveryUnavailableError) {
|
|
79
|
+
return response({ error: 'Delivery provider is unavailable.' }, 503, cors);
|
|
78
80
|
}
|
|
79
81
|
throw cause;
|
|
80
82
|
}
|
|
81
83
|
let created;
|
|
82
84
|
try {
|
|
83
|
-
created =
|
|
85
|
+
created = delivery.accept(candidate);
|
|
84
86
|
}
|
|
85
87
|
catch (cause) {
|
|
86
|
-
if (cause instanceof
|
|
87
|
-
return response({ error: '
|
|
88
|
+
if (cause instanceof delivery.Error) {
|
|
89
|
+
return response({ error: 'Delivery provider returned an invalid response.' }, 500, cors);
|
|
88
90
|
}
|
|
89
91
|
throw cause;
|
|
90
92
|
}
|
|
91
93
|
if (created.publication.etag !== snapshot.publication.etag) {
|
|
92
|
-
return response({ error: '
|
|
94
|
+
return response({ error: 'Delivery provider returned a different Publication.' }, 500, cors);
|
|
93
95
|
}
|
|
94
96
|
const encoded = codecs.json.encode(created);
|
|
95
|
-
if (encodedSize(encoded) > limits.
|
|
96
|
-
return response({ error: '
|
|
97
|
+
if (encodedSize(encoded) > limits.maximumDeliveryBytes) {
|
|
98
|
+
return response({ error: 'Delivery response exceeds its serving limit.' }, 500, cors);
|
|
97
99
|
}
|
|
98
100
|
return new Response(encodedBody(encoded), {
|
|
99
101
|
headers: { ...cors, 'content-type': 'application/json' },
|
|
@@ -123,4 +125,3 @@ function encodedSize(input) {
|
|
|
123
125
|
function toBody(input) {
|
|
124
126
|
return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
|
|
125
127
|
}
|
|
126
|
-
//# sourceMappingURL=handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { handleDelivery } from './handler.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type * as publication from '@astrale-os/kernel-protocol/publication';
|
|
2
|
+
export interface Context {
|
|
3
|
+
/** Omitted for a public endpoint; otherwise the exact admitted retrieval credential. */
|
|
4
|
+
readonly token?: publication.delivery.Token;
|
|
5
|
+
readonly signal: AbortSignal;
|
|
6
|
+
}
|
|
7
|
+
/** Provider-owned retrieval, construction, and attestation signing. */
|
|
8
|
+
export interface Provider {
|
|
9
|
+
/** Throw DeliveryUnavailableError only for an expected dependency outage. */
|
|
10
|
+
create(request: publication.delivery.Request, context: Context): Promise<publication.delivery.Delivery>;
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { preparePublicationSnapshot, readPreparedPublicationSnapshot, type PreparedPublicationSnapshot, } from './snapshot.js';
|
|
2
2
|
export type { PublicationSnapshot, PublicationSnapshotInput, PublicationSource } from './types.js';
|
|
3
|
-
|
|
3
|
+
export * as delivery from './delivery/index.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { preparePublicationSnapshot, readPreparedPublicationSnapshot, } from './snapshot.js';
|
|
2
|
-
|
|
2
|
+
export * as delivery from './delivery/index.js';
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Domain } from '@astrale-os/kernel-dsl/v1';
|
|
2
|
+
import type * as publication from '@astrale-os/kernel-protocol/publication';
|
|
3
|
+
import type * as routes from '@astrale-os/kernel-protocol/routes';
|
|
2
4
|
import type { PublicationSnapshot, PublicationSnapshotInput } from './types.js';
|
|
3
5
|
export interface PreparedPublicationSnapshot {
|
|
4
6
|
readonly snapshot: PublicationSnapshot;
|
|
5
7
|
readonly publication: publication.Publication;
|
|
6
8
|
readonly publicationBytes: Uint8Array;
|
|
7
9
|
readonly bundle: Uint8Array;
|
|
8
|
-
readonly
|
|
10
|
+
readonly table: routes.Table;
|
|
11
|
+
readonly domain: Domain;
|
|
9
12
|
}
|
|
10
13
|
/** Admit and detach one revision-coherent serving snapshot before publication. */
|
|
11
14
|
export declare function preparePublicationSnapshot(input: PublicationSnapshotInput): PublicationSnapshot;
|
|
12
15
|
/** Internal proof that a PublicationSource returned a Server-prepared snapshot without cloning it. */
|
|
13
16
|
export declare function readPreparedPublicationSnapshot(input: unknown): PreparedPublicationSnapshot | undefined;
|
|
14
|
-
//# sourceMappingURL=snapshot.d.ts.map
|
|
@@ -1,22 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { verifyBundleBytes } from '@astrale-os/kernel-protocol/publication/bundle-evidence';
|
|
2
|
+
import { accept as acceptPublication, encode as encodePublication, } from '@astrale-os/kernel-protocol/publication/publication';
|
|
3
|
+
import { admitTable } from './table.js';
|
|
2
4
|
const snapshots = new WeakMap();
|
|
3
5
|
/** Admit and detach one revision-coherent serving snapshot before publication. */
|
|
4
6
|
export function preparePublicationSnapshot(input) {
|
|
5
7
|
if (input === null || typeof input !== 'object' || !(input.bundle instanceof Uint8Array)) {
|
|
6
8
|
throw new TypeError('Publication snapshot input is invalid.');
|
|
7
9
|
}
|
|
8
|
-
const admittedPublication =
|
|
10
|
+
const admittedPublication = acceptPublication(input.publication);
|
|
9
11
|
const bundleBytes = new Uint8Array(input.bundle);
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
12
|
+
verifyBundleBytes(admittedPublication, bundleBytes);
|
|
13
|
+
const domain = input.domain;
|
|
14
|
+
if (domain.origin !== admittedPublication.origin ||
|
|
15
|
+
domain.revision !== admittedPublication.schema.revision) {
|
|
16
|
+
throw new TypeError('Publication snapshot Domain identity differs from its Publication.');
|
|
17
|
+
}
|
|
18
|
+
const table = admitTable(admittedPublication, input.table, domain);
|
|
19
|
+
const publicationBytes = encodePublication(admittedPublication);
|
|
13
20
|
const snapshot = Object.freeze({ publication: admittedPublication });
|
|
14
21
|
const prepared = Object.freeze({
|
|
15
22
|
snapshot,
|
|
16
23
|
publication: admittedPublication,
|
|
17
24
|
publicationBytes,
|
|
18
25
|
bundle: bundleBytes,
|
|
19
|
-
|
|
26
|
+
table,
|
|
27
|
+
domain,
|
|
20
28
|
});
|
|
21
29
|
snapshots.set(snapshot, prepared);
|
|
22
30
|
return snapshot;
|
|
@@ -27,4 +35,3 @@ export function readPreparedPublicationSnapshot(input) {
|
|
|
27
35
|
return undefined;
|
|
28
36
|
return snapshots.get(input);
|
|
29
37
|
}
|
|
30
|
-
//# sourceMappingURL=snapshot.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Domain } from '@astrale-os/kernel-dsl/v1';
|
|
2
|
+
import type * as publication from '@astrale-os/kernel-protocol/publication';
|
|
3
|
+
import type * as routes from '@astrale-os/kernel-protocol/routes';
|
|
4
|
+
export declare function admitTable(deployed: publication.Publication, input: routes.Table | undefined, domain: Domain): routes.Table;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { reference } from '@astrale-os/kernel-protocol/publication/reference';
|
|
2
|
+
import { acceptTable } from '@astrale-os/kernel-protocol/routes/admission';
|
|
3
|
+
import { verifyTable } from '@astrale-os/kernel-protocol/routes/evidence';
|
|
4
|
+
export function admitTable(deployed, input, domain) {
|
|
5
|
+
if (input === undefined) {
|
|
6
|
+
if (deployed.routes.length !== 0) {
|
|
7
|
+
throw new TypeError('A Publication with native routes requires a Route Table.');
|
|
8
|
+
}
|
|
9
|
+
return verifyTable(Object.freeze({
|
|
10
|
+
publication: reference(deployed),
|
|
11
|
+
entries: Object.freeze([]),
|
|
12
|
+
}), deployed, domain);
|
|
13
|
+
}
|
|
14
|
+
const table = acceptTable(input);
|
|
15
|
+
return verifyTable(table, deployed, domain);
|
|
16
|
+
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Domain } from '@astrale-os/kernel-dsl/v1';
|
|
2
|
+
import type * as publication from '@astrale-os/kernel-protocol/publication';
|
|
3
|
+
import type * as routes from '@astrale-os/kernel-protocol/routes';
|
|
2
4
|
declare const PUBLICATION_SNAPSHOT: unique symbol;
|
|
3
5
|
export interface PublicationSnapshotInput {
|
|
4
6
|
readonly publication: publication.Publication;
|
|
5
7
|
/** Exact artifact served at Publication.schema.bundle.href. */
|
|
6
8
|
readonly bundle: Uint8Array;
|
|
9
|
+
readonly table?: routes.Table;
|
|
10
|
+
readonly domain: Domain;
|
|
7
11
|
}
|
|
8
12
|
/** Immutable admitted serving identity; bundle and compiled routes remain Server-private. */
|
|
9
13
|
export interface PublicationSnapshot {
|
|
@@ -16,4 +20,3 @@ export interface PublicationSource {
|
|
|
16
20
|
current(): PublicationSnapshot | undefined;
|
|
17
21
|
}
|
|
18
22
|
export {};
|
|
19
|
-
//# sourceMappingURL=types.d.ts.map
|
package/dist/routes/index.d.ts
CHANGED
package/dist/routes/index.js
CHANGED
package/dist/routes/routes.d.ts
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
import type { CorsPolicy } from '../cors/index.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { NextInvocationId } from '../invocation/index.js';
|
|
3
3
|
import type { ActiveOperations } from '../lifecycle/index.js';
|
|
4
|
+
import type { Provider as DeliveryProvider } from '../publication/delivery/index.js';
|
|
4
5
|
import type { PublicationSource } from '../publication/index.js';
|
|
5
6
|
import { type ApplicationHost, type Handler } from '../invocation/index.js';
|
|
6
7
|
import { type PreparedPublicationSnapshot } from '../publication/index.js';
|
|
7
|
-
import { type ServerRouter } from '../transport/
|
|
8
|
+
import { type ServerRouter } from '../transport/router.js';
|
|
8
9
|
export interface RouteLimits {
|
|
9
10
|
readonly maximumRequestBytes: number;
|
|
10
11
|
readonly maximumPublicationBytes: number;
|
|
11
12
|
readonly maximumBundleBytes: number;
|
|
12
|
-
readonly
|
|
13
|
+
readonly maximumDeliveryBytes: number;
|
|
13
14
|
readonly invocationTimeoutMs: number;
|
|
14
15
|
}
|
|
15
16
|
export interface RouteReservation {
|
|
16
17
|
readonly path: string;
|
|
17
18
|
readonly methods: readonly string[];
|
|
18
19
|
}
|
|
19
|
-
export declare function mountRoutes(app: ServerRouter, source: PublicationSource,
|
|
20
|
+
export declare function mountRoutes(app: ServerRouter, source: PublicationSource, deliveryProvider: DeliveryProvider | undefined, handler: Handler, active: ActiveOperations, nextInvocation: NextInvocationId, limits: RouteLimits, cors: CorsPolicy, hostFor: (request: Request) => ApplicationHost | undefined, reservations: readonly RouteReservation[], externalIssuer?: string): void;
|
|
20
21
|
export declare function currentPublicationSnapshot(source: PublicationSource, limits: Pick<RouteLimits, 'maximumPublicationBytes' | 'maximumBundleBytes'>, serving?: {
|
|
21
22
|
readonly origin: string;
|
|
23
|
+
readonly issuer?: string;
|
|
22
24
|
readonly reservations: readonly RouteReservation[];
|
|
23
25
|
}): PreparedPublicationSnapshot | undefined;
|
|
24
|
-
//# sourceMappingURL=routes.d.ts.map
|