@nice-code/action 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -6
- package/build/{AcceptorHandler-11-QMdx2.d.mts → AcceptorHandler-CLbwu2Pa.d.mts} +179 -18
- package/build/{AcceptorHandler-CxD0c1BE.d.cts → AcceptorHandler-Du292dpC.d.cts} +179 -18
- package/build/{ActionDevtoolsCore-37JP4bOG.d.cts → ActionDevtoolsCore-DGwzONZT.d.mts} +2 -2
- package/build/{ActionDevtoolsCore-Cgq-go1R.d.mts → ActionDevtoolsCore-dH4K4w3B.d.cts} +2 -2
- package/build/advanced/index.cjs +1 -1
- package/build/advanced/index.d.cts +12 -101
- package/build/advanced/index.d.mts +12 -101
- package/build/advanced/index.mjs +1 -1
- package/build/{createHibernatableWsServerAdapter-C07RfUTH.mjs → createHibernatableWsServerAdapter-BD5n-Ev9.mjs} +186 -83
- package/build/createHibernatableWsServerAdapter-BD5n-Ev9.mjs.map +1 -0
- package/build/{createHibernatableWsServerAdapter-BNi4k9j3.cjs → createHibernatableWsServerAdapter-j96U9vgo.cjs} +185 -82
- package/build/createHibernatableWsServerAdapter-j96U9vgo.cjs.map +1 -0
- package/build/devtools/browser/index.cjs.map +1 -1
- package/build/devtools/browser/index.d.cts +1 -1
- package/build/devtools/browser/index.d.mts +1 -1
- package/build/devtools/browser/index.mjs.map +1 -1
- package/build/devtools/server/index.d.cts +1 -1
- package/build/devtools/server/index.d.mts +1 -1
- package/build/{httpAcceptorCarrier-C3S_bDkL.cjs → httpAcceptorCarrier-By0Qa__L.cjs} +2 -2
- package/build/httpAcceptorCarrier-By0Qa__L.cjs.map +1 -0
- package/build/{httpAcceptorCarrier-DPBEuewS.mjs → httpAcceptorCarrier-moSmtBxr.mjs} +2 -2
- package/build/httpAcceptorCarrier-moSmtBxr.mjs.map +1 -0
- package/build/index.cjs +6 -2
- package/build/index.cjs.map +1 -1
- package/build/index.d.cts +2 -2
- package/build/index.d.mts +2 -2
- package/build/index.mjs +3 -3
- package/build/index.mjs.map +1 -1
- package/build/platform/cloudflare/index.cjs +45 -1
- package/build/platform/cloudflare/index.cjs.map +1 -1
- package/build/platform/cloudflare/index.d.cts +40 -2
- package/build/platform/cloudflare/index.d.mts +40 -2
- package/build/platform/cloudflare/index.mjs +45 -2
- package/build/platform/cloudflare/index.mjs.map +1 -1
- package/build/react-query/index.d.cts +1 -1
- package/build/react-query/index.d.mts +1 -1
- package/package.json +5 -4
- package/build/createHibernatableWsServerAdapter-BNi4k9j3.cjs.map +0 -1
- package/build/createHibernatableWsServerAdapter-C07RfUTH.mjs.map +0 -1
- package/build/httpAcceptorCarrier-C3S_bDkL.cjs.map +0 -1
- package/build/httpAcceptorCarrier-DPBEuewS.mjs.map +0 -1
package/build/advanced/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as encodeHandshakeMessage, C as EHandshakeMessageType, D as createServerHandshake, F as ConnectorHandler, I as createConnectorHandler, L as PeerLinkHandler, R as ETransportShape, T as createClientHandshake, _ as extractWirePayload, a as ExchangeAcceptor, b as createAcceptorHandler, c as decodeExchangeReply, d as Transport, f as createBinaryWireSessionFactory, g as buildActionRouteDictionary, h as assembleWireJson, i as createActionFetchHandler, k as decodeHandshakeMessage, l as decodeExchangeRequest, m as ReversePayloadType, n as ConnectionStateStore, o as LinkTransport, p as PayloadTypeToInt, r as createConnectionStateStore, s as ExchangeTransport, t as createHibernatableWsServerAdapter, u as encodeExchange, v as createSecureAcceptorHandler, x as createActionFrameCrypto, y as AcceptorHandler, z as ETransportStatus } from "../createHibernatableWsServerAdapter-
|
|
1
|
+
import { A as encodeHandshakeMessage, C as EHandshakeMessageType, D as createServerHandshake, F as ConnectorHandler, I as createConnectorHandler, L as PeerLinkHandler, R as ETransportShape, T as createClientHandshake, _ as extractWirePayload, a as ExchangeAcceptor, b as createAcceptorHandler, c as decodeExchangeReply, d as Transport, f as createBinaryWireSessionFactory, g as buildActionRouteDictionary, h as assembleWireJson, i as createActionFetchHandler, k as decodeHandshakeMessage, l as decodeExchangeRequest, m as ReversePayloadType, n as ConnectionStateStore, o as LinkTransport, p as PayloadTypeToInt, r as createConnectionStateStore, s as ExchangeTransport, t as createHibernatableWsServerAdapter, u as encodeExchange, v as createSecureAcceptorHandler, x as createActionFrameCrypto, y as AcceptorHandler, z as ETransportStatus } from "../createHibernatableWsServerAdapter-BD5n-Ev9.mjs";
|
|
2
2
|
import { pack, unpack } from "msgpackr";
|
|
3
3
|
//#region src/ActionRuntime/Transport/codec/createBinaryWireAdapter.ts
|
|
4
4
|
/**
|
|
@@ -3,7 +3,7 @@ import { nanoid } from "nanoid";
|
|
|
3
3
|
import { NiceError, castNiceError, err, err_nice, isNiceErrorObject } from "@nice-code/error";
|
|
4
4
|
import { extractMessageFromStandardSchema } from "@nice-code/common-errors";
|
|
5
5
|
import { runtime } from "std-env";
|
|
6
|
-
import { ClientCryptoKeyLink, createTypedStorage } from "@nice-code/util";
|
|
6
|
+
import { ClientCryptoKeyLink, createTypedStorage, decryptBytesWithAesGcmKey, encryptBytesWithAesGcmKey } from "@nice-code/util";
|
|
7
7
|
import * as v from "valibot";
|
|
8
8
|
import { pack, unpack } from "msgpackr";
|
|
9
9
|
const UNSET_RUNTIME_ENV_ID = "_unset_";
|
|
@@ -1403,11 +1403,13 @@ var ActionRuntime = class {
|
|
|
1403
1403
|
* Used to locate the return-path channel for dispatching results back to the action origin.
|
|
1404
1404
|
* Returns `undefined` if no handler matches (score > 0 required, i.e. at least id must match).
|
|
1405
1405
|
*
|
|
1406
|
-
* A handler that currently holds the origin's *live* connection always wins
|
|
1407
|
-
*
|
|
1408
|
-
*
|
|
1409
|
-
*
|
|
1410
|
-
*
|
|
1406
|
+
* A handler that currently holds the origin's *live* connection always wins, regardless of its
|
|
1407
|
+
* coordinate score — owning the live socket bound to the origin's exact coordinate (set from the
|
|
1408
|
+
* handshake) is a strictly more precise match than any env-level `peerClient` score. This lets one
|
|
1409
|
+
* server accept clients of *several* envs over a single acceptor (a multi-role Durable Object): the
|
|
1410
|
+
* result/push routes back over the carrier the client actually connected on even when the handler's
|
|
1411
|
+
* `clientEnv` is unset or names a different env. Only when no handler owns a live connection do we fall
|
|
1412
|
+
* back to the plain best-coordinate-score pick (the offline-return and connector-only cases).
|
|
1411
1413
|
*/
|
|
1412
1414
|
getReturnHandlerForOrigin(originClient) {
|
|
1413
1415
|
if (originClient.envId === "_unset_") return void 0;
|
|
@@ -1422,12 +1424,12 @@ var ActionRuntime = class {
|
|
|
1422
1424
|
bestScore = score;
|
|
1423
1425
|
bestHandler = handler;
|
|
1424
1426
|
}
|
|
1425
|
-
if (
|
|
1427
|
+
if (handler.ownsLiveConnectionFor(originClient) && score > bestOwnedScore) {
|
|
1426
1428
|
bestOwnedScore = score;
|
|
1427
1429
|
bestOwnedHandler = handler;
|
|
1428
1430
|
}
|
|
1429
1431
|
}
|
|
1430
|
-
if (bestOwnedHandler != null
|
|
1432
|
+
if (bestOwnedHandler != null) return bestOwnedHandler;
|
|
1431
1433
|
return bestScore > 0 ? bestHandler : void 0;
|
|
1432
1434
|
}
|
|
1433
1435
|
resetRuntime() {
|
|
@@ -1730,6 +1732,16 @@ function createInMemoryTofuVerifyKeyResolver() {
|
|
|
1730
1732
|
* Storage-backed trust-on-first-use resolver: pins survive process restarts / Durable Object eviction
|
|
1731
1733
|
* (e.g. back it with `createDurableObjectStorageAdapter`). Same policy as the in-memory variant — trust
|
|
1732
1734
|
* + pin the first verify key per client identity, reject a different one thereafter.
|
|
1735
|
+
*
|
|
1736
|
+
* Fail-closed by construction: a thrown storage read (`getJson`) or first-pin write (`updateJsonWithDef`)
|
|
1737
|
+
* propagates out of `resolve`, which makes `onProve` reject the handshake — a storage error can never be
|
|
1738
|
+
* mistaken for "first use" and silently trusted. (A genuine `undefined`/absent read is the only path to
|
|
1739
|
+
* a fresh pin; the underlying adapters never coerce a thrown read to `undefined`.) Keep it that way: do
|
|
1740
|
+
* not wrap the storage calls in a `try/catch` that swallows the error.
|
|
1741
|
+
*
|
|
1742
|
+
* On an *eventually-consistent* store a stale "absent" read re-pins the **same** verify key the client
|
|
1743
|
+
* just presented (it is signature-verified before this runs), so the worst case is a harmless re-write,
|
|
1744
|
+
* never a weakened trust decision. Cross-isolate-strong pinning still wants a strongly-consistent store.
|
|
1733
1745
|
*/
|
|
1734
1746
|
function createStorageTofuVerifyKeyResolver(storageAdapter) {
|
|
1735
1747
|
const storage = createTypedStorage({ storageAdapter });
|
|
@@ -1783,6 +1795,13 @@ function createClientHandshake(config) {
|
|
|
1783
1795
|
bindVerifyKeysIntoDerivation: true
|
|
1784
1796
|
} : {}
|
|
1785
1797
|
});
|
|
1798
|
+
const encryptionKeyMaterial = wantsEncryption && welcome.exchangePublicKey != null ? {
|
|
1799
|
+
verifyPublicKey: welcome.verifyPublicKey,
|
|
1800
|
+
exchangePublicKey: welcome.exchangePublicKey,
|
|
1801
|
+
saltString: sessionSalt(clientNonce, welcome.serverNonce),
|
|
1802
|
+
infoString: handshakeInfo(dictionaryVersion),
|
|
1803
|
+
bindVerifyKeysIntoDerivation: true
|
|
1804
|
+
} : void 0;
|
|
1786
1805
|
const challenge = buildHandshakeChallenge({
|
|
1787
1806
|
securityLevel,
|
|
1788
1807
|
dictionaryVersion,
|
|
@@ -1798,7 +1817,8 @@ function createClientHandshake(config) {
|
|
|
1798
1817
|
pending = {
|
|
1799
1818
|
linkedServerId,
|
|
1800
1819
|
server: welcome.server,
|
|
1801
|
-
challenge
|
|
1820
|
+
challenge,
|
|
1821
|
+
encryptionKeyMaterial
|
|
1802
1822
|
};
|
|
1803
1823
|
return {
|
|
1804
1824
|
t: "prove",
|
|
@@ -1817,7 +1837,8 @@ function createClientHandshake(config) {
|
|
|
1817
1837
|
return {
|
|
1818
1838
|
linkedClientId: pending.linkedServerId,
|
|
1819
1839
|
remote: pending.server,
|
|
1820
|
-
securityLevel
|
|
1840
|
+
securityLevel,
|
|
1841
|
+
encryptionKeyMaterial: pending.encryptionKeyMaterial
|
|
1821
1842
|
};
|
|
1822
1843
|
}
|
|
1823
1844
|
};
|
|
@@ -1912,6 +1933,16 @@ function createServerHandshake(config) {
|
|
|
1912
1933
|
/** The completed handshake result once `onProve` has accepted, else `undefined`. */
|
|
1913
1934
|
getResult() {
|
|
1914
1935
|
return result;
|
|
1936
|
+
},
|
|
1937
|
+
exportPending() {
|
|
1938
|
+
return pending;
|
|
1939
|
+
},
|
|
1940
|
+
async restorePending(restored) {
|
|
1941
|
+
pending = restored;
|
|
1942
|
+
await link.linkClient({
|
|
1943
|
+
linkedClientId: restored.linkedClientId,
|
|
1944
|
+
verifyPublicKey: restored.clientVerifyKey
|
|
1945
|
+
});
|
|
1915
1946
|
}
|
|
1916
1947
|
};
|
|
1917
1948
|
}
|
|
@@ -1942,14 +1973,16 @@ function parseJsonActionFrame(message) {
|
|
|
1942
1973
|
const ENCRYPTED_ENVELOPE_LENGTH = 2;
|
|
1943
1974
|
/**
|
|
1944
1975
|
* Build the encrypt/decrypt transform for a connection whose handshake settled on the `encrypted`
|
|
1945
|
-
* level.
|
|
1976
|
+
* level. The shared key is derived once from {@link IActionFrameCryptoConfig.keyMaterial} and captured
|
|
1977
|
+
* for this connection alone, decoupling it from the link's shared key cache.
|
|
1946
1978
|
*/
|
|
1947
|
-
function createActionFrameCrypto({ link,
|
|
1979
|
+
function createActionFrameCrypto({ link, keyMaterial }) {
|
|
1980
|
+
const keyPromise = link.deriveSharedAesGcmKey(keyMaterial);
|
|
1948
1981
|
return {
|
|
1949
1982
|
async encryptFrame(frame) {
|
|
1950
|
-
const { nonce, ciphertext } = await
|
|
1951
|
-
|
|
1952
|
-
|
|
1983
|
+
const { nonce, ciphertext } = await encryptBytesWithAesGcmKey({
|
|
1984
|
+
dataToEncrypt: frame,
|
|
1985
|
+
aesGcmKey: await keyPromise
|
|
1953
1986
|
});
|
|
1954
1987
|
return pack([nonce, ciphertext]);
|
|
1955
1988
|
},
|
|
@@ -1959,12 +1992,12 @@ function createActionFrameCrypto({ link, linkedClientId }) {
|
|
|
1959
1992
|
if (!Array.isArray(envelope) || envelope.length !== ENCRYPTED_ENVELOPE_LENGTH) throw new Error("[ws-crypto] malformed encrypted frame envelope");
|
|
1960
1993
|
const [nonce, ciphertext] = envelope;
|
|
1961
1994
|
if (!(nonce instanceof Uint8Array) || !(ciphertext instanceof Uint8Array)) throw new Error("[ws-crypto] malformed encrypted frame fields");
|
|
1962
|
-
return await
|
|
1963
|
-
linkedClientId,
|
|
1995
|
+
return await decryptBytesWithAesGcmKey({
|
|
1964
1996
|
dataToDecrypt: {
|
|
1965
1997
|
nonce,
|
|
1966
1998
|
ciphertext
|
|
1967
|
-
}
|
|
1999
|
+
},
|
|
2000
|
+
aesGcmKey: await keyPromise
|
|
1968
2001
|
});
|
|
1969
2002
|
}
|
|
1970
2003
|
};
|
|
@@ -2075,9 +2108,9 @@ var AcceptorSecureSession = class {
|
|
|
2075
2108
|
_complete(result) {
|
|
2076
2109
|
this._authed = true;
|
|
2077
2110
|
this._handshake = void 0;
|
|
2078
|
-
if (result.securityLevel === "encrypted") this._pipe = this._buildPipe(createActionFrameCrypto({
|
|
2111
|
+
if (result.securityLevel === "encrypted" && result.encryptionKeyMaterial != null) this._pipe = this._buildPipe(createActionFrameCrypto({
|
|
2079
2112
|
link: this.config.link,
|
|
2080
|
-
|
|
2113
|
+
keyMaterial: result.encryptionKeyMaterial
|
|
2081
2114
|
}));
|
|
2082
2115
|
this.config.onAuthenticated({
|
|
2083
2116
|
client: new RuntimeCoordinate(result.remote),
|
|
@@ -2096,17 +2129,10 @@ var AcceptorSecureSession = class {
|
|
|
2096
2129
|
this._authed = true;
|
|
2097
2130
|
if (state.securityLevel !== "encrypted" || state.keyMaterial == null) return;
|
|
2098
2131
|
const { link } = this.config;
|
|
2099
|
-
const {
|
|
2100
|
-
const cryptoReady = link.initialize().then(() =>
|
|
2101
|
-
linkedClientId,
|
|
2102
|
-
verifyPublicKey: keyMaterial.verifyPublicKey,
|
|
2103
|
-
exchangePublicKey: keyMaterial.exchangePublicKey,
|
|
2104
|
-
saltString: keyMaterial.saltString,
|
|
2105
|
-
infoString: keyMaterial.infoString,
|
|
2106
|
-
bindVerifyKeysIntoDerivation: keyMaterial.bindVerifyKeysIntoDerivation
|
|
2107
|
-
})).then(() => createActionFrameCrypto({
|
|
2132
|
+
const { keyMaterial } = state;
|
|
2133
|
+
const cryptoReady = link.initialize().then(() => createActionFrameCrypto({
|
|
2108
2134
|
link,
|
|
2109
|
-
|
|
2135
|
+
keyMaterial
|
|
2110
2136
|
}));
|
|
2111
2137
|
cryptoReady.catch((err) => console.error("[ws-server] failed to restore encrypted session", err));
|
|
2112
2138
|
this._pipe = this._buildPipe(cryptoReady);
|
|
@@ -2170,7 +2196,7 @@ var AcceptorHandler = class extends PeerLinkHandler {
|
|
|
2170
2196
|
_codecByConn = /* @__PURE__ */ new Map();
|
|
2171
2197
|
_sessionByConn = /* @__PURE__ */ new Map();
|
|
2172
2198
|
constructor(options) {
|
|
2173
|
-
super(options.clientEnv);
|
|
2199
|
+
super(options.clientEnv ?? RuntimeCoordinate.unknown);
|
|
2174
2200
|
this._formatMessage = options.formatMessage;
|
|
2175
2201
|
this._createFormatMessage = options.createFormatMessage;
|
|
2176
2202
|
this._send = options.send;
|
|
@@ -2885,11 +2911,9 @@ async function runConnectorExchangeHandshake(carrier, secure) {
|
|
|
2885
2911
|
dictionaryVersion: secure.dictionaryVersion,
|
|
2886
2912
|
securityLevel: secure.securityLevel
|
|
2887
2913
|
});
|
|
2888
|
-
const hsid = nanoid();
|
|
2889
2914
|
const hello = await handshake.createHello();
|
|
2890
2915
|
const welcomeReply = decodeExchangeReply(asString(await carrier.exchange(encodeExchange({
|
|
2891
2916
|
k: "hs",
|
|
2892
|
-
hsid,
|
|
2893
2917
|
m: encodeHandshakeMessage(hello)
|
|
2894
2918
|
}))));
|
|
2895
2919
|
if (welcomeReply?.k !== "hs") throw new Error("[exchange-handshake] expected a welcome reply");
|
|
@@ -2897,11 +2921,12 @@ async function runConnectorExchangeHandshake(carrier, secure) {
|
|
|
2897
2921
|
if (welcome == null) throw new Error("[exchange-handshake] malformed welcome");
|
|
2898
2922
|
if (welcome.t === "reject") throw new Error(`[exchange-handshake] rejected by peer: ${welcome.reason}`);
|
|
2899
2923
|
if (welcome.t !== "welcome") throw new Error(`[exchange-handshake] expected welcome, got ${welcome.t}`);
|
|
2924
|
+
if (welcomeReply.hsc == null) throw new Error("[exchange-handshake] welcome missing handshake continuation token");
|
|
2900
2925
|
const prove = await handshake.onWelcome(welcome);
|
|
2901
2926
|
const acceptReply = decodeExchangeReply(asString(await carrier.exchange(encodeExchange({
|
|
2902
2927
|
k: "hs",
|
|
2903
|
-
|
|
2904
|
-
|
|
2928
|
+
m: encodeHandshakeMessage(prove),
|
|
2929
|
+
hsc: welcomeReply.hsc
|
|
2905
2930
|
}))));
|
|
2906
2931
|
if (acceptReply?.k !== "hs") throw new Error("[exchange-handshake] expected an accept reply");
|
|
2907
2932
|
const accept = decodeHandshakeMessage(acceptReply.m);
|
|
@@ -2910,9 +2935,9 @@ async function runConnectorExchangeHandshake(carrier, secure) {
|
|
|
2910
2935
|
if (accept.t !== "accept") throw new Error(`[exchange-handshake] expected accept, got ${accept.t}`);
|
|
2911
2936
|
if (acceptReply.t == null) throw new Error("[exchange-handshake] accept missing session token");
|
|
2912
2937
|
const result = await handshake.onAccept(accept);
|
|
2913
|
-
const crypto = result.securityLevel === "encrypted" ? createActionFrameCrypto({
|
|
2938
|
+
const crypto = result.securityLevel === "encrypted" && result.encryptionKeyMaterial != null ? createActionFrameCrypto({
|
|
2914
2939
|
link: secure.link,
|
|
2915
|
-
|
|
2940
|
+
keyMaterial: result.encryptionKeyMaterial
|
|
2916
2941
|
}) : void 0;
|
|
2917
2942
|
return {
|
|
2918
2943
|
token: acceptReply.t,
|
|
@@ -3220,9 +3245,9 @@ async function runClientHandshake(channel, secure, nextHandshakeMessage) {
|
|
|
3220
3245
|
if (accept.t === "reject") throw new Error(`[link-handshake] rejected by peer: ${accept.reason}`);
|
|
3221
3246
|
if (accept.t !== "accept") throw new Error(`[link-handshake] expected accept, got ${accept.t}`);
|
|
3222
3247
|
const result = await handshake.onAccept(accept);
|
|
3223
|
-
return result.securityLevel === "encrypted" ? createActionFrameCrypto({
|
|
3248
|
+
return result.securityLevel === "encrypted" && result.encryptionKeyMaterial != null ? createActionFrameCrypto({
|
|
3224
3249
|
link: secure.link,
|
|
3225
|
-
|
|
3250
|
+
keyMaterial: result.encryptionKeyMaterial
|
|
3226
3251
|
}) : void 0;
|
|
3227
3252
|
}
|
|
3228
3253
|
function buildSendMethods(ctx, pipe, disconnectListeners, abortSet) {
|
|
@@ -3376,82 +3401,154 @@ var LinkTransport = class LinkTransport extends Transport {
|
|
|
3376
3401
|
}
|
|
3377
3402
|
};
|
|
3378
3403
|
//#endregion
|
|
3404
|
+
//#region src/ActionRuntime/Transport/SecureSession/exchangeTicketSeal.ts
|
|
3405
|
+
const SEALED_ENVELOPE_LENGTH = 2;
|
|
3406
|
+
function createExchangeTicketSealer(link, options = {}) {
|
|
3407
|
+
let keyPromise;
|
|
3408
|
+
const getKey = () => {
|
|
3409
|
+
if (keyPromise == null) keyPromise = link.deriveLocalSealKey({ version: options.version }).catch((err) => {
|
|
3410
|
+
keyPromise = void 0;
|
|
3411
|
+
throw err;
|
|
3412
|
+
});
|
|
3413
|
+
return keyPromise;
|
|
3414
|
+
};
|
|
3415
|
+
return {
|
|
3416
|
+
async seal(value, ttlMs) {
|
|
3417
|
+
const { nonce, ciphertext } = await encryptBytesWithAesGcmKey({
|
|
3418
|
+
dataToEncrypt: pack({
|
|
3419
|
+
v: value,
|
|
3420
|
+
exp: Date.now() + ttlMs
|
|
3421
|
+
}),
|
|
3422
|
+
aesGcmKey: await getKey()
|
|
3423
|
+
});
|
|
3424
|
+
return bytesToBase64(pack([nonce, ciphertext]));
|
|
3425
|
+
},
|
|
3426
|
+
async open(blob) {
|
|
3427
|
+
try {
|
|
3428
|
+
const envelope = unpack(base64ToBytes(blob));
|
|
3429
|
+
if (!Array.isArray(envelope) || envelope.length !== SEALED_ENVELOPE_LENGTH) return void 0;
|
|
3430
|
+
const [nonce, ciphertext] = envelope;
|
|
3431
|
+
if (!(nonce instanceof Uint8Array) || !(ciphertext instanceof Uint8Array)) return void 0;
|
|
3432
|
+
const payload = unpack(await decryptBytesWithAesGcmKey({
|
|
3433
|
+
dataToDecrypt: {
|
|
3434
|
+
nonce,
|
|
3435
|
+
ciphertext
|
|
3436
|
+
},
|
|
3437
|
+
aesGcmKey: await getKey()
|
|
3438
|
+
}));
|
|
3439
|
+
if (payload == null || typeof payload.exp !== "number" || payload.exp < Date.now()) return;
|
|
3440
|
+
return payload.v;
|
|
3441
|
+
} catch {
|
|
3442
|
+
return;
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
};
|
|
3446
|
+
}
|
|
3447
|
+
//#endregion
|
|
3379
3448
|
//#region src/ActionRuntime/Transport/SecureSession/exchangeAcceptor.ts
|
|
3449
|
+
/** The default session-ticket lifetime — see {@link IExchangeAcceptorConfig.sessionTtlMs}. */
|
|
3450
|
+
const DEFAULT_SESSION_TTL_MS = 720 * 60 * 1e3;
|
|
3451
|
+
/** The handshake continuation (`hsc`) only bridges the two handshake POSTs, so it expires quickly. */
|
|
3452
|
+
const HANDSHAKE_CONTINUATION_TTL_MS = 120 * 1e3;
|
|
3380
3453
|
const textEncoder = new TextEncoder();
|
|
3381
3454
|
const textDecoder = new TextDecoder();
|
|
3382
3455
|
/**
|
|
3383
3456
|
* Acceptor (accept-in) side of the secure exchange protocol — the HTTP counterpart to
|
|
3384
3457
|
* {@link AcceptorSecureSession}. Each POST body is one {@link decodeExchangeRequest} envelope; the
|
|
3385
|
-
* acceptor drives the server handshake over the two `hs` POSTs
|
|
3386
|
-
*
|
|
3387
|
-
*
|
|
3388
|
-
* and returns the (encrypted) result inline as the reply.
|
|
3458
|
+
* acceptor drives the server handshake over the two `hs` POSTs, mints a session **token** on accept, and
|
|
3459
|
+
* on every later `act` POST resolves the session by token, decrypts the body (at `encrypted`), routes it
|
|
3460
|
+
* through the runtime, and returns the (encrypted) result inline as the reply.
|
|
3389
3461
|
*
|
|
3390
|
-
*
|
|
3391
|
-
*
|
|
3392
|
-
*
|
|
3462
|
+
* **Stateless.** It holds no in-memory handshakes or sessions: the in-flight handshake `pending` is
|
|
3463
|
+
* sealed into the `hsc` continuation token returned on `welcome` and echoed back on `prove`, and the live
|
|
3464
|
+
* session is sealed into the `t` token replayed on every `act`. Both are sealed under the acceptor's own
|
|
3465
|
+
* persisted identity ({@link createExchangeTicketSealer}), so any isolate that loaded the same identity
|
|
3466
|
+
* can serve any POST — no request needs to co-locate with another (no Durable Object required just to
|
|
3467
|
+
* pin a handshake to one instance). A tampered, wrong-key, or expired token opens to "no valid session".
|
|
3393
3468
|
*/
|
|
3394
3469
|
var ExchangeAcceptor = class {
|
|
3395
3470
|
_security;
|
|
3396
3471
|
_runtime;
|
|
3397
3472
|
_allowedLevels;
|
|
3398
3473
|
_noneAllowed;
|
|
3399
|
-
|
|
3400
|
-
|
|
3474
|
+
_sealer;
|
|
3475
|
+
_sessionTtlMs;
|
|
3401
3476
|
constructor(config) {
|
|
3402
3477
|
this._security = config.security;
|
|
3403
3478
|
this._runtime = config.runtime;
|
|
3404
3479
|
this._allowedLevels = Array.isArray(config.security.securityLevel) ? config.security.securityLevel : [config.security.securityLevel];
|
|
3405
3480
|
this._noneAllowed = this._allowedLevels.includes("none");
|
|
3481
|
+
this._sealer = createExchangeTicketSealer(config.security.link);
|
|
3482
|
+
this._sessionTtlMs = config.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS;
|
|
3406
3483
|
}
|
|
3407
3484
|
/** Process one POST body (an exchange envelope), returning the reply body to send back. */
|
|
3408
3485
|
async handlePost(body) {
|
|
3409
3486
|
const request = decodeExchangeRequest(body);
|
|
3410
3487
|
if (request == null) return this._err("malformed exchange request");
|
|
3488
|
+
await this._security.link.initialize();
|
|
3411
3489
|
if (request.k === "hs") return encodeExchange(await this._handleHandshake(request));
|
|
3412
3490
|
return encodeExchange(await this._handleAction(request));
|
|
3413
3491
|
}
|
|
3492
|
+
_makeHandshake() {
|
|
3493
|
+
const security = this._security;
|
|
3494
|
+
return createServerHandshake({
|
|
3495
|
+
link: security.link,
|
|
3496
|
+
localCoordinate: security.localCoordinate,
|
|
3497
|
+
dictionaryVersion: security.dictionaryVersion,
|
|
3498
|
+
securityLevel: security.securityLevel,
|
|
3499
|
+
verifyKeyResolver: security.verifyKeyResolver
|
|
3500
|
+
});
|
|
3501
|
+
}
|
|
3414
3502
|
async _handleHandshake(request) {
|
|
3415
3503
|
const message = decodeHandshakeMessage(request.m);
|
|
3416
3504
|
if (message == null) return {
|
|
3417
3505
|
k: "err",
|
|
3418
3506
|
message: "malformed handshake message"
|
|
3419
3507
|
};
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3508
|
+
if (message.t === "hello") {
|
|
3509
|
+
const handshake = this._makeHandshake();
|
|
3510
|
+
const reply = await handshake.onHello(message);
|
|
3511
|
+
if (reply.t === "reject") return {
|
|
3512
|
+
k: "hs",
|
|
3513
|
+
m: encodeHandshakeMessage(reply)
|
|
3514
|
+
};
|
|
3515
|
+
const pending = handshake.exportPending();
|
|
3516
|
+
if (pending == null) return {
|
|
3517
|
+
k: "err",
|
|
3518
|
+
message: "handshake produced no continuation state"
|
|
3519
|
+
};
|
|
3520
|
+
const hsc = await this._sealer.seal(pending, HANDSHAKE_CONTINUATION_TTL_MS);
|
|
3521
|
+
return {
|
|
3522
|
+
k: "hs",
|
|
3523
|
+
m: encodeHandshakeMessage(reply),
|
|
3524
|
+
hsc
|
|
3525
|
+
};
|
|
3432
3526
|
}
|
|
3433
|
-
if (message.t === "hello") return {
|
|
3434
|
-
k: "hs",
|
|
3435
|
-
m: encodeHandshakeMessage(await handshake.onHello(message))
|
|
3436
|
-
};
|
|
3437
3527
|
if (message.t === "prove") {
|
|
3528
|
+
if (request.hsc == null) return {
|
|
3529
|
+
k: "err",
|
|
3530
|
+
message: "prove missing continuation token"
|
|
3531
|
+
};
|
|
3532
|
+
const pending = await this._sealer.open(request.hsc);
|
|
3533
|
+
if (pending == null) return {
|
|
3534
|
+
k: "err",
|
|
3535
|
+
message: "invalid or expired continuation token"
|
|
3536
|
+
};
|
|
3537
|
+
const handshake = this._makeHandshake();
|
|
3538
|
+
await handshake.restorePending(pending);
|
|
3438
3539
|
const reply = await handshake.onProve(message);
|
|
3439
|
-
this._pendingHandshakes.delete(request.hsid);
|
|
3440
3540
|
const result = handshake.getResult();
|
|
3441
3541
|
if (reply.t === "accept" && result != null) {
|
|
3442
|
-
const
|
|
3443
|
-
|
|
3444
|
-
client: new RuntimeCoordinate(result.remote),
|
|
3542
|
+
const ticket = {
|
|
3543
|
+
client: result.remote,
|
|
3445
3544
|
securityLevel: result.securityLevel,
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
}) : void 0
|
|
3450
|
-
});
|
|
3545
|
+
keyMaterial: result.encryptionKeyMaterial
|
|
3546
|
+
};
|
|
3547
|
+
const t = await this._sealer.seal(ticket, this._sessionTtlMs);
|
|
3451
3548
|
return {
|
|
3452
3549
|
k: "hs",
|
|
3453
3550
|
m: encodeHandshakeMessage(reply),
|
|
3454
|
-
t
|
|
3551
|
+
t
|
|
3455
3552
|
};
|
|
3456
3553
|
}
|
|
3457
3554
|
return {
|
|
@@ -3465,20 +3562,26 @@ var ExchangeAcceptor = class {
|
|
|
3465
3562
|
};
|
|
3466
3563
|
}
|
|
3467
3564
|
async _handleAction(request) {
|
|
3468
|
-
let
|
|
3565
|
+
let client;
|
|
3566
|
+
let crypto;
|
|
3469
3567
|
let candidate;
|
|
3470
3568
|
if (request.t != null) {
|
|
3471
|
-
|
|
3472
|
-
if (
|
|
3569
|
+
const ticket = await this._sealer.open(request.t);
|
|
3570
|
+
if (ticket == null) return {
|
|
3473
3571
|
k: "err",
|
|
3474
3572
|
message: "unknown or expired session token"
|
|
3475
3573
|
};
|
|
3574
|
+
client = new RuntimeCoordinate(ticket.client);
|
|
3575
|
+
crypto = ticket.securityLevel === "encrypted" && ticket.keyMaterial != null ? createActionFrameCrypto({
|
|
3576
|
+
link: this._security.link,
|
|
3577
|
+
keyMaterial: ticket.keyMaterial
|
|
3578
|
+
}) : void 0;
|
|
3476
3579
|
if ("c" in request) {
|
|
3477
|
-
if (
|
|
3580
|
+
if (crypto == null) return {
|
|
3478
3581
|
k: "err",
|
|
3479
3582
|
message: "session is not encrypted"
|
|
3480
3583
|
};
|
|
3481
|
-
const plain = await
|
|
3584
|
+
const plain = await crypto.decryptFrame(base64ToBytes(request.c));
|
|
3482
3585
|
candidate = JSON.parse(textDecoder.decode(plain));
|
|
3483
3586
|
} else candidate = request.w;
|
|
3484
3587
|
} else {
|
|
@@ -3493,11 +3596,11 @@ var ExchangeAcceptor = class {
|
|
|
3493
3596
|
message: "malformed action wire"
|
|
3494
3597
|
};
|
|
3495
3598
|
const wire = candidate;
|
|
3496
|
-
if (
|
|
3599
|
+
if (client != null && wire.type === "request") wire.context.originClient = client.toJsonObject();
|
|
3497
3600
|
const resultWire = (await (await this._runtime.handleActionPayloadWire(wire)).waitForResultPayload()).toJsonObject();
|
|
3498
|
-
if (
|
|
3601
|
+
if (crypto != null && "c" in request) return {
|
|
3499
3602
|
k: "act",
|
|
3500
|
-
c: bytesToBase64(await
|
|
3603
|
+
c: bytesToBase64(await crypto.encryptFrame(textEncoder.encode(JSON.stringify(resultWire))))
|
|
3501
3604
|
};
|
|
3502
3605
|
return {
|
|
3503
3606
|
k: "act",
|
|
@@ -3715,4 +3818,4 @@ function createHibernatableWsServerAdapter(options) {
|
|
|
3715
3818
|
//#endregion
|
|
3716
3819
|
export { ActionPayload_Request as $, encodeHandshakeMessage as A, EErrId_NiceTransport as B, EHandshakeMessageType as C, createServerHandshake as D, createInMemoryTofuVerifyKeyResolver as E, ConnectorHandler as F, ActionSchema as G, err_nice_external_client as H, createConnectorHandler as I, isActionPayload_Result_JsonObject as J, EActionResponseMode as K, PeerLinkHandler as L, ActionLocalHandler as M, createLocalHandler as N, createStorageTofuVerifyKeyResolver as O, ActionRuntime as P, RunningAction as Q, ETransportShape as R, decodeActionFrame as S, createClientHandshake as T, isActionPayload_Any_JsonObject as U, err_nice_transport as V, isActionPayload_Request_JsonObject as W, EErrId_NiceAction as X, isAction_Base_JsonObject as Y, err_nice_action as Z, extractWirePayload as _, ExchangeAcceptor as a, RuntimeCoordinate as at, createAcceptorHandler as b, decodeExchangeReply as c, Transport as d, ActionPayload_Result as et, createBinaryWireSessionFactory as f, buildActionRouteDictionary as g, assembleWireJson as h, createActionFetchHandler as i, ActionBase as it, runtimeLinkId as j, decodeHandshakeMessage as k, decodeExchangeRequest as l, ReversePayloadType as m, ConnectionStateStore as n, EActionProgressType as nt, LinkTransport as o, runtimeCoordinateToStringIds as ot, PayloadTypeToInt as p, actionSchema as q, createConnectionStateStore as r, ActionPayload as rt, ExchangeTransport as s, createHibernatableWsServerAdapter as t, EActionPayloadType as tt, encodeExchange as u, createSecureAcceptorHandler as v, ESecurityLevel as w, createActionFrameCrypto as x, AcceptorHandler as y, ETransportStatus as z };
|
|
3717
3820
|
|
|
3718
|
-
//# sourceMappingURL=createHibernatableWsServerAdapter-
|
|
3821
|
+
//# sourceMappingURL=createHibernatableWsServerAdapter-BD5n-Ev9.mjs.map
|