@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
|
@@ -1405,11 +1405,13 @@ var ActionRuntime = class {
|
|
|
1405
1405
|
* Used to locate the return-path channel for dispatching results back to the action origin.
|
|
1406
1406
|
* Returns `undefined` if no handler matches (score > 0 required, i.e. at least id must match).
|
|
1407
1407
|
*
|
|
1408
|
-
* A handler that currently holds the origin's *live* connection always wins
|
|
1409
|
-
*
|
|
1410
|
-
*
|
|
1411
|
-
*
|
|
1412
|
-
*
|
|
1408
|
+
* A handler that currently holds the origin's *live* connection always wins, regardless of its
|
|
1409
|
+
* coordinate score — owning the live socket bound to the origin's exact coordinate (set from the
|
|
1410
|
+
* handshake) is a strictly more precise match than any env-level `peerClient` score. This lets one
|
|
1411
|
+
* server accept clients of *several* envs over a single acceptor (a multi-role Durable Object): the
|
|
1412
|
+
* result/push routes back over the carrier the client actually connected on even when the handler's
|
|
1413
|
+
* `clientEnv` is unset or names a different env. Only when no handler owns a live connection do we fall
|
|
1414
|
+
* back to the plain best-coordinate-score pick (the offline-return and connector-only cases).
|
|
1413
1415
|
*/
|
|
1414
1416
|
getReturnHandlerForOrigin(originClient) {
|
|
1415
1417
|
if (originClient.envId === "_unset_") return void 0;
|
|
@@ -1424,12 +1426,12 @@ var ActionRuntime = class {
|
|
|
1424
1426
|
bestScore = score;
|
|
1425
1427
|
bestHandler = handler;
|
|
1426
1428
|
}
|
|
1427
|
-
if (
|
|
1429
|
+
if (handler.ownsLiveConnectionFor(originClient) && score > bestOwnedScore) {
|
|
1428
1430
|
bestOwnedScore = score;
|
|
1429
1431
|
bestOwnedHandler = handler;
|
|
1430
1432
|
}
|
|
1431
1433
|
}
|
|
1432
|
-
if (bestOwnedHandler != null
|
|
1434
|
+
if (bestOwnedHandler != null) return bestOwnedHandler;
|
|
1433
1435
|
return bestScore > 0 ? bestHandler : void 0;
|
|
1434
1436
|
}
|
|
1435
1437
|
resetRuntime() {
|
|
@@ -1732,6 +1734,16 @@ function createInMemoryTofuVerifyKeyResolver() {
|
|
|
1732
1734
|
* Storage-backed trust-on-first-use resolver: pins survive process restarts / Durable Object eviction
|
|
1733
1735
|
* (e.g. back it with `createDurableObjectStorageAdapter`). Same policy as the in-memory variant — trust
|
|
1734
1736
|
* + pin the first verify key per client identity, reject a different one thereafter.
|
|
1737
|
+
*
|
|
1738
|
+
* Fail-closed by construction: a thrown storage read (`getJson`) or first-pin write (`updateJsonWithDef`)
|
|
1739
|
+
* propagates out of `resolve`, which makes `onProve` reject the handshake — a storage error can never be
|
|
1740
|
+
* mistaken for "first use" and silently trusted. (A genuine `undefined`/absent read is the only path to
|
|
1741
|
+
* a fresh pin; the underlying adapters never coerce a thrown read to `undefined`.) Keep it that way: do
|
|
1742
|
+
* not wrap the storage calls in a `try/catch` that swallows the error.
|
|
1743
|
+
*
|
|
1744
|
+
* On an *eventually-consistent* store a stale "absent" read re-pins the **same** verify key the client
|
|
1745
|
+
* just presented (it is signature-verified before this runs), so the worst case is a harmless re-write,
|
|
1746
|
+
* never a weakened trust decision. Cross-isolate-strong pinning still wants a strongly-consistent store.
|
|
1735
1747
|
*/
|
|
1736
1748
|
function createStorageTofuVerifyKeyResolver(storageAdapter) {
|
|
1737
1749
|
const storage = (0, _nice_code_util.createTypedStorage)({ storageAdapter });
|
|
@@ -1785,6 +1797,13 @@ function createClientHandshake(config) {
|
|
|
1785
1797
|
bindVerifyKeysIntoDerivation: true
|
|
1786
1798
|
} : {}
|
|
1787
1799
|
});
|
|
1800
|
+
const encryptionKeyMaterial = wantsEncryption && welcome.exchangePublicKey != null ? {
|
|
1801
|
+
verifyPublicKey: welcome.verifyPublicKey,
|
|
1802
|
+
exchangePublicKey: welcome.exchangePublicKey,
|
|
1803
|
+
saltString: sessionSalt(clientNonce, welcome.serverNonce),
|
|
1804
|
+
infoString: handshakeInfo(dictionaryVersion),
|
|
1805
|
+
bindVerifyKeysIntoDerivation: true
|
|
1806
|
+
} : void 0;
|
|
1788
1807
|
const challenge = buildHandshakeChallenge({
|
|
1789
1808
|
securityLevel,
|
|
1790
1809
|
dictionaryVersion,
|
|
@@ -1800,7 +1819,8 @@ function createClientHandshake(config) {
|
|
|
1800
1819
|
pending = {
|
|
1801
1820
|
linkedServerId,
|
|
1802
1821
|
server: welcome.server,
|
|
1803
|
-
challenge
|
|
1822
|
+
challenge,
|
|
1823
|
+
encryptionKeyMaterial
|
|
1804
1824
|
};
|
|
1805
1825
|
return {
|
|
1806
1826
|
t: "prove",
|
|
@@ -1819,7 +1839,8 @@ function createClientHandshake(config) {
|
|
|
1819
1839
|
return {
|
|
1820
1840
|
linkedClientId: pending.linkedServerId,
|
|
1821
1841
|
remote: pending.server,
|
|
1822
|
-
securityLevel
|
|
1842
|
+
securityLevel,
|
|
1843
|
+
encryptionKeyMaterial: pending.encryptionKeyMaterial
|
|
1823
1844
|
};
|
|
1824
1845
|
}
|
|
1825
1846
|
};
|
|
@@ -1914,6 +1935,16 @@ function createServerHandshake(config) {
|
|
|
1914
1935
|
/** The completed handshake result once `onProve` has accepted, else `undefined`. */
|
|
1915
1936
|
getResult() {
|
|
1916
1937
|
return result;
|
|
1938
|
+
},
|
|
1939
|
+
exportPending() {
|
|
1940
|
+
return pending;
|
|
1941
|
+
},
|
|
1942
|
+
async restorePending(restored) {
|
|
1943
|
+
pending = restored;
|
|
1944
|
+
await link.linkClient({
|
|
1945
|
+
linkedClientId: restored.linkedClientId,
|
|
1946
|
+
verifyPublicKey: restored.clientVerifyKey
|
|
1947
|
+
});
|
|
1917
1948
|
}
|
|
1918
1949
|
};
|
|
1919
1950
|
}
|
|
@@ -1944,14 +1975,16 @@ function parseJsonActionFrame(message) {
|
|
|
1944
1975
|
const ENCRYPTED_ENVELOPE_LENGTH = 2;
|
|
1945
1976
|
/**
|
|
1946
1977
|
* Build the encrypt/decrypt transform for a connection whose handshake settled on the `encrypted`
|
|
1947
|
-
* level.
|
|
1978
|
+
* level. The shared key is derived once from {@link IActionFrameCryptoConfig.keyMaterial} and captured
|
|
1979
|
+
* for this connection alone, decoupling it from the link's shared key cache.
|
|
1948
1980
|
*/
|
|
1949
|
-
function createActionFrameCrypto({ link,
|
|
1981
|
+
function createActionFrameCrypto({ link, keyMaterial }) {
|
|
1982
|
+
const keyPromise = link.deriveSharedAesGcmKey(keyMaterial);
|
|
1950
1983
|
return {
|
|
1951
1984
|
async encryptFrame(frame) {
|
|
1952
|
-
const { nonce, ciphertext } = await
|
|
1953
|
-
|
|
1954
|
-
|
|
1985
|
+
const { nonce, ciphertext } = await (0, _nice_code_util.encryptBytesWithAesGcmKey)({
|
|
1986
|
+
dataToEncrypt: frame,
|
|
1987
|
+
aesGcmKey: await keyPromise
|
|
1955
1988
|
});
|
|
1956
1989
|
return (0, msgpackr.pack)([nonce, ciphertext]);
|
|
1957
1990
|
},
|
|
@@ -1961,12 +1994,12 @@ function createActionFrameCrypto({ link, linkedClientId }) {
|
|
|
1961
1994
|
if (!Array.isArray(envelope) || envelope.length !== ENCRYPTED_ENVELOPE_LENGTH) throw new Error("[ws-crypto] malformed encrypted frame envelope");
|
|
1962
1995
|
const [nonce, ciphertext] = envelope;
|
|
1963
1996
|
if (!(nonce instanceof Uint8Array) || !(ciphertext instanceof Uint8Array)) throw new Error("[ws-crypto] malformed encrypted frame fields");
|
|
1964
|
-
return await
|
|
1965
|
-
linkedClientId,
|
|
1997
|
+
return await (0, _nice_code_util.decryptBytesWithAesGcmKey)({
|
|
1966
1998
|
dataToDecrypt: {
|
|
1967
1999
|
nonce,
|
|
1968
2000
|
ciphertext
|
|
1969
|
-
}
|
|
2001
|
+
},
|
|
2002
|
+
aesGcmKey: await keyPromise
|
|
1970
2003
|
});
|
|
1971
2004
|
}
|
|
1972
2005
|
};
|
|
@@ -2077,9 +2110,9 @@ var AcceptorSecureSession = class {
|
|
|
2077
2110
|
_complete(result) {
|
|
2078
2111
|
this._authed = true;
|
|
2079
2112
|
this._handshake = void 0;
|
|
2080
|
-
if (result.securityLevel === "encrypted") this._pipe = this._buildPipe(createActionFrameCrypto({
|
|
2113
|
+
if (result.securityLevel === "encrypted" && result.encryptionKeyMaterial != null) this._pipe = this._buildPipe(createActionFrameCrypto({
|
|
2081
2114
|
link: this.config.link,
|
|
2082
|
-
|
|
2115
|
+
keyMaterial: result.encryptionKeyMaterial
|
|
2083
2116
|
}));
|
|
2084
2117
|
this.config.onAuthenticated({
|
|
2085
2118
|
client: new RuntimeCoordinate(result.remote),
|
|
@@ -2098,17 +2131,10 @@ var AcceptorSecureSession = class {
|
|
|
2098
2131
|
this._authed = true;
|
|
2099
2132
|
if (state.securityLevel !== "encrypted" || state.keyMaterial == null) return;
|
|
2100
2133
|
const { link } = this.config;
|
|
2101
|
-
const {
|
|
2102
|
-
const cryptoReady = link.initialize().then(() =>
|
|
2103
|
-
linkedClientId,
|
|
2104
|
-
verifyPublicKey: keyMaterial.verifyPublicKey,
|
|
2105
|
-
exchangePublicKey: keyMaterial.exchangePublicKey,
|
|
2106
|
-
saltString: keyMaterial.saltString,
|
|
2107
|
-
infoString: keyMaterial.infoString,
|
|
2108
|
-
bindVerifyKeysIntoDerivation: keyMaterial.bindVerifyKeysIntoDerivation
|
|
2109
|
-
})).then(() => createActionFrameCrypto({
|
|
2134
|
+
const { keyMaterial } = state;
|
|
2135
|
+
const cryptoReady = link.initialize().then(() => createActionFrameCrypto({
|
|
2110
2136
|
link,
|
|
2111
|
-
|
|
2137
|
+
keyMaterial
|
|
2112
2138
|
}));
|
|
2113
2139
|
cryptoReady.catch((err) => console.error("[ws-server] failed to restore encrypted session", err));
|
|
2114
2140
|
this._pipe = this._buildPipe(cryptoReady);
|
|
@@ -2172,7 +2198,7 @@ var AcceptorHandler = class extends PeerLinkHandler {
|
|
|
2172
2198
|
_codecByConn = /* @__PURE__ */ new Map();
|
|
2173
2199
|
_sessionByConn = /* @__PURE__ */ new Map();
|
|
2174
2200
|
constructor(options) {
|
|
2175
|
-
super(options.clientEnv);
|
|
2201
|
+
super(options.clientEnv ?? RuntimeCoordinate.unknown);
|
|
2176
2202
|
this._formatMessage = options.formatMessage;
|
|
2177
2203
|
this._createFormatMessage = options.createFormatMessage;
|
|
2178
2204
|
this._send = options.send;
|
|
@@ -2887,11 +2913,9 @@ async function runConnectorExchangeHandshake(carrier, secure) {
|
|
|
2887
2913
|
dictionaryVersion: secure.dictionaryVersion,
|
|
2888
2914
|
securityLevel: secure.securityLevel
|
|
2889
2915
|
});
|
|
2890
|
-
const hsid = (0, nanoid.nanoid)();
|
|
2891
2916
|
const hello = await handshake.createHello();
|
|
2892
2917
|
const welcomeReply = decodeExchangeReply(asString(await carrier.exchange(encodeExchange({
|
|
2893
2918
|
k: "hs",
|
|
2894
|
-
hsid,
|
|
2895
2919
|
m: encodeHandshakeMessage(hello)
|
|
2896
2920
|
}))));
|
|
2897
2921
|
if (welcomeReply?.k !== "hs") throw new Error("[exchange-handshake] expected a welcome reply");
|
|
@@ -2899,11 +2923,12 @@ async function runConnectorExchangeHandshake(carrier, secure) {
|
|
|
2899
2923
|
if (welcome == null) throw new Error("[exchange-handshake] malformed welcome");
|
|
2900
2924
|
if (welcome.t === "reject") throw new Error(`[exchange-handshake] rejected by peer: ${welcome.reason}`);
|
|
2901
2925
|
if (welcome.t !== "welcome") throw new Error(`[exchange-handshake] expected welcome, got ${welcome.t}`);
|
|
2926
|
+
if (welcomeReply.hsc == null) throw new Error("[exchange-handshake] welcome missing handshake continuation token");
|
|
2902
2927
|
const prove = await handshake.onWelcome(welcome);
|
|
2903
2928
|
const acceptReply = decodeExchangeReply(asString(await carrier.exchange(encodeExchange({
|
|
2904
2929
|
k: "hs",
|
|
2905
|
-
|
|
2906
|
-
|
|
2930
|
+
m: encodeHandshakeMessage(prove),
|
|
2931
|
+
hsc: welcomeReply.hsc
|
|
2907
2932
|
}))));
|
|
2908
2933
|
if (acceptReply?.k !== "hs") throw new Error("[exchange-handshake] expected an accept reply");
|
|
2909
2934
|
const accept = decodeHandshakeMessage(acceptReply.m);
|
|
@@ -2912,9 +2937,9 @@ async function runConnectorExchangeHandshake(carrier, secure) {
|
|
|
2912
2937
|
if (accept.t !== "accept") throw new Error(`[exchange-handshake] expected accept, got ${accept.t}`);
|
|
2913
2938
|
if (acceptReply.t == null) throw new Error("[exchange-handshake] accept missing session token");
|
|
2914
2939
|
const result = await handshake.onAccept(accept);
|
|
2915
|
-
const crypto = result.securityLevel === "encrypted" ? createActionFrameCrypto({
|
|
2940
|
+
const crypto = result.securityLevel === "encrypted" && result.encryptionKeyMaterial != null ? createActionFrameCrypto({
|
|
2916
2941
|
link: secure.link,
|
|
2917
|
-
|
|
2942
|
+
keyMaterial: result.encryptionKeyMaterial
|
|
2918
2943
|
}) : void 0;
|
|
2919
2944
|
return {
|
|
2920
2945
|
token: acceptReply.t,
|
|
@@ -3222,9 +3247,9 @@ async function runClientHandshake(channel, secure, nextHandshakeMessage) {
|
|
|
3222
3247
|
if (accept.t === "reject") throw new Error(`[link-handshake] rejected by peer: ${accept.reason}`);
|
|
3223
3248
|
if (accept.t !== "accept") throw new Error(`[link-handshake] expected accept, got ${accept.t}`);
|
|
3224
3249
|
const result = await handshake.onAccept(accept);
|
|
3225
|
-
return result.securityLevel === "encrypted" ? createActionFrameCrypto({
|
|
3250
|
+
return result.securityLevel === "encrypted" && result.encryptionKeyMaterial != null ? createActionFrameCrypto({
|
|
3226
3251
|
link: secure.link,
|
|
3227
|
-
|
|
3252
|
+
keyMaterial: result.encryptionKeyMaterial
|
|
3228
3253
|
}) : void 0;
|
|
3229
3254
|
}
|
|
3230
3255
|
function buildSendMethods(ctx, pipe, disconnectListeners, abortSet) {
|
|
@@ -3378,82 +3403,154 @@ var LinkTransport = class LinkTransport extends Transport {
|
|
|
3378
3403
|
}
|
|
3379
3404
|
};
|
|
3380
3405
|
//#endregion
|
|
3406
|
+
//#region src/ActionRuntime/Transport/SecureSession/exchangeTicketSeal.ts
|
|
3407
|
+
const SEALED_ENVELOPE_LENGTH = 2;
|
|
3408
|
+
function createExchangeTicketSealer(link, options = {}) {
|
|
3409
|
+
let keyPromise;
|
|
3410
|
+
const getKey = () => {
|
|
3411
|
+
if (keyPromise == null) keyPromise = link.deriveLocalSealKey({ version: options.version }).catch((err) => {
|
|
3412
|
+
keyPromise = void 0;
|
|
3413
|
+
throw err;
|
|
3414
|
+
});
|
|
3415
|
+
return keyPromise;
|
|
3416
|
+
};
|
|
3417
|
+
return {
|
|
3418
|
+
async seal(value, ttlMs) {
|
|
3419
|
+
const { nonce, ciphertext } = await (0, _nice_code_util.encryptBytesWithAesGcmKey)({
|
|
3420
|
+
dataToEncrypt: (0, msgpackr.pack)({
|
|
3421
|
+
v: value,
|
|
3422
|
+
exp: Date.now() + ttlMs
|
|
3423
|
+
}),
|
|
3424
|
+
aesGcmKey: await getKey()
|
|
3425
|
+
});
|
|
3426
|
+
return bytesToBase64((0, msgpackr.pack)([nonce, ciphertext]));
|
|
3427
|
+
},
|
|
3428
|
+
async open(blob) {
|
|
3429
|
+
try {
|
|
3430
|
+
const envelope = (0, msgpackr.unpack)(base64ToBytes(blob));
|
|
3431
|
+
if (!Array.isArray(envelope) || envelope.length !== SEALED_ENVELOPE_LENGTH) return void 0;
|
|
3432
|
+
const [nonce, ciphertext] = envelope;
|
|
3433
|
+
if (!(nonce instanceof Uint8Array) || !(ciphertext instanceof Uint8Array)) return void 0;
|
|
3434
|
+
const payload = (0, msgpackr.unpack)(await (0, _nice_code_util.decryptBytesWithAesGcmKey)({
|
|
3435
|
+
dataToDecrypt: {
|
|
3436
|
+
nonce,
|
|
3437
|
+
ciphertext
|
|
3438
|
+
},
|
|
3439
|
+
aesGcmKey: await getKey()
|
|
3440
|
+
}));
|
|
3441
|
+
if (payload == null || typeof payload.exp !== "number" || payload.exp < Date.now()) return;
|
|
3442
|
+
return payload.v;
|
|
3443
|
+
} catch {
|
|
3444
|
+
return;
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
};
|
|
3448
|
+
}
|
|
3449
|
+
//#endregion
|
|
3381
3450
|
//#region src/ActionRuntime/Transport/SecureSession/exchangeAcceptor.ts
|
|
3451
|
+
/** The default session-ticket lifetime — see {@link IExchangeAcceptorConfig.sessionTtlMs}. */
|
|
3452
|
+
const DEFAULT_SESSION_TTL_MS = 720 * 60 * 1e3;
|
|
3453
|
+
/** The handshake continuation (`hsc`) only bridges the two handshake POSTs, so it expires quickly. */
|
|
3454
|
+
const HANDSHAKE_CONTINUATION_TTL_MS = 120 * 1e3;
|
|
3382
3455
|
const textEncoder = new TextEncoder();
|
|
3383
3456
|
const textDecoder = new TextDecoder();
|
|
3384
3457
|
/**
|
|
3385
3458
|
* Acceptor (accept-in) side of the secure exchange protocol — the HTTP counterpart to
|
|
3386
3459
|
* {@link AcceptorSecureSession}. Each POST body is one {@link decodeExchangeRequest} envelope; the
|
|
3387
|
-
* acceptor drives the server handshake over the two `hs` POSTs
|
|
3388
|
-
*
|
|
3389
|
-
*
|
|
3390
|
-
* and returns the (encrypted) result inline as the reply.
|
|
3460
|
+
* acceptor drives the server handshake over the two `hs` POSTs, mints a session **token** on accept, and
|
|
3461
|
+
* on every later `act` POST resolves the session by token, decrypts the body (at `encrypted`), routes it
|
|
3462
|
+
* through the runtime, and returns the (encrypted) result inline as the reply.
|
|
3391
3463
|
*
|
|
3392
|
-
*
|
|
3393
|
-
*
|
|
3394
|
-
*
|
|
3464
|
+
* **Stateless.** It holds no in-memory handshakes or sessions: the in-flight handshake `pending` is
|
|
3465
|
+
* sealed into the `hsc` continuation token returned on `welcome` and echoed back on `prove`, and the live
|
|
3466
|
+
* session is sealed into the `t` token replayed on every `act`. Both are sealed under the acceptor's own
|
|
3467
|
+
* persisted identity ({@link createExchangeTicketSealer}), so any isolate that loaded the same identity
|
|
3468
|
+
* can serve any POST — no request needs to co-locate with another (no Durable Object required just to
|
|
3469
|
+
* pin a handshake to one instance). A tampered, wrong-key, or expired token opens to "no valid session".
|
|
3395
3470
|
*/
|
|
3396
3471
|
var ExchangeAcceptor = class {
|
|
3397
3472
|
_security;
|
|
3398
3473
|
_runtime;
|
|
3399
3474
|
_allowedLevels;
|
|
3400
3475
|
_noneAllowed;
|
|
3401
|
-
|
|
3402
|
-
|
|
3476
|
+
_sealer;
|
|
3477
|
+
_sessionTtlMs;
|
|
3403
3478
|
constructor(config) {
|
|
3404
3479
|
this._security = config.security;
|
|
3405
3480
|
this._runtime = config.runtime;
|
|
3406
3481
|
this._allowedLevels = Array.isArray(config.security.securityLevel) ? config.security.securityLevel : [config.security.securityLevel];
|
|
3407
3482
|
this._noneAllowed = this._allowedLevels.includes("none");
|
|
3483
|
+
this._sealer = createExchangeTicketSealer(config.security.link);
|
|
3484
|
+
this._sessionTtlMs = config.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS;
|
|
3408
3485
|
}
|
|
3409
3486
|
/** Process one POST body (an exchange envelope), returning the reply body to send back. */
|
|
3410
3487
|
async handlePost(body) {
|
|
3411
3488
|
const request = decodeExchangeRequest(body);
|
|
3412
3489
|
if (request == null) return this._err("malformed exchange request");
|
|
3490
|
+
await this._security.link.initialize();
|
|
3413
3491
|
if (request.k === "hs") return encodeExchange(await this._handleHandshake(request));
|
|
3414
3492
|
return encodeExchange(await this._handleAction(request));
|
|
3415
3493
|
}
|
|
3494
|
+
_makeHandshake() {
|
|
3495
|
+
const security = this._security;
|
|
3496
|
+
return createServerHandshake({
|
|
3497
|
+
link: security.link,
|
|
3498
|
+
localCoordinate: security.localCoordinate,
|
|
3499
|
+
dictionaryVersion: security.dictionaryVersion,
|
|
3500
|
+
securityLevel: security.securityLevel,
|
|
3501
|
+
verifyKeyResolver: security.verifyKeyResolver
|
|
3502
|
+
});
|
|
3503
|
+
}
|
|
3416
3504
|
async _handleHandshake(request) {
|
|
3417
3505
|
const message = decodeHandshakeMessage(request.m);
|
|
3418
3506
|
if (message == null) return {
|
|
3419
3507
|
k: "err",
|
|
3420
3508
|
message: "malformed handshake message"
|
|
3421
3509
|
};
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3510
|
+
if (message.t === "hello") {
|
|
3511
|
+
const handshake = this._makeHandshake();
|
|
3512
|
+
const reply = await handshake.onHello(message);
|
|
3513
|
+
if (reply.t === "reject") return {
|
|
3514
|
+
k: "hs",
|
|
3515
|
+
m: encodeHandshakeMessage(reply)
|
|
3516
|
+
};
|
|
3517
|
+
const pending = handshake.exportPending();
|
|
3518
|
+
if (pending == null) return {
|
|
3519
|
+
k: "err",
|
|
3520
|
+
message: "handshake produced no continuation state"
|
|
3521
|
+
};
|
|
3522
|
+
const hsc = await this._sealer.seal(pending, HANDSHAKE_CONTINUATION_TTL_MS);
|
|
3523
|
+
return {
|
|
3524
|
+
k: "hs",
|
|
3525
|
+
m: encodeHandshakeMessage(reply),
|
|
3526
|
+
hsc
|
|
3527
|
+
};
|
|
3434
3528
|
}
|
|
3435
|
-
if (message.t === "hello") return {
|
|
3436
|
-
k: "hs",
|
|
3437
|
-
m: encodeHandshakeMessage(await handshake.onHello(message))
|
|
3438
|
-
};
|
|
3439
3529
|
if (message.t === "prove") {
|
|
3530
|
+
if (request.hsc == null) return {
|
|
3531
|
+
k: "err",
|
|
3532
|
+
message: "prove missing continuation token"
|
|
3533
|
+
};
|
|
3534
|
+
const pending = await this._sealer.open(request.hsc);
|
|
3535
|
+
if (pending == null) return {
|
|
3536
|
+
k: "err",
|
|
3537
|
+
message: "invalid or expired continuation token"
|
|
3538
|
+
};
|
|
3539
|
+
const handshake = this._makeHandshake();
|
|
3540
|
+
await handshake.restorePending(pending);
|
|
3440
3541
|
const reply = await handshake.onProve(message);
|
|
3441
|
-
this._pendingHandshakes.delete(request.hsid);
|
|
3442
3542
|
const result = handshake.getResult();
|
|
3443
3543
|
if (reply.t === "accept" && result != null) {
|
|
3444
|
-
const
|
|
3445
|
-
|
|
3446
|
-
client: new RuntimeCoordinate(result.remote),
|
|
3544
|
+
const ticket = {
|
|
3545
|
+
client: result.remote,
|
|
3447
3546
|
securityLevel: result.securityLevel,
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
}) : void 0
|
|
3452
|
-
});
|
|
3547
|
+
keyMaterial: result.encryptionKeyMaterial
|
|
3548
|
+
};
|
|
3549
|
+
const t = await this._sealer.seal(ticket, this._sessionTtlMs);
|
|
3453
3550
|
return {
|
|
3454
3551
|
k: "hs",
|
|
3455
3552
|
m: encodeHandshakeMessage(reply),
|
|
3456
|
-
t
|
|
3553
|
+
t
|
|
3457
3554
|
};
|
|
3458
3555
|
}
|
|
3459
3556
|
return {
|
|
@@ -3467,20 +3564,26 @@ var ExchangeAcceptor = class {
|
|
|
3467
3564
|
};
|
|
3468
3565
|
}
|
|
3469
3566
|
async _handleAction(request) {
|
|
3470
|
-
let
|
|
3567
|
+
let client;
|
|
3568
|
+
let crypto;
|
|
3471
3569
|
let candidate;
|
|
3472
3570
|
if (request.t != null) {
|
|
3473
|
-
|
|
3474
|
-
if (
|
|
3571
|
+
const ticket = await this._sealer.open(request.t);
|
|
3572
|
+
if (ticket == null) return {
|
|
3475
3573
|
k: "err",
|
|
3476
3574
|
message: "unknown or expired session token"
|
|
3477
3575
|
};
|
|
3576
|
+
client = new RuntimeCoordinate(ticket.client);
|
|
3577
|
+
crypto = ticket.securityLevel === "encrypted" && ticket.keyMaterial != null ? createActionFrameCrypto({
|
|
3578
|
+
link: this._security.link,
|
|
3579
|
+
keyMaterial: ticket.keyMaterial
|
|
3580
|
+
}) : void 0;
|
|
3478
3581
|
if ("c" in request) {
|
|
3479
|
-
if (
|
|
3582
|
+
if (crypto == null) return {
|
|
3480
3583
|
k: "err",
|
|
3481
3584
|
message: "session is not encrypted"
|
|
3482
3585
|
};
|
|
3483
|
-
const plain = await
|
|
3586
|
+
const plain = await crypto.decryptFrame(base64ToBytes(request.c));
|
|
3484
3587
|
candidate = JSON.parse(textDecoder.decode(plain));
|
|
3485
3588
|
} else candidate = request.w;
|
|
3486
3589
|
} else {
|
|
@@ -3495,11 +3598,11 @@ var ExchangeAcceptor = class {
|
|
|
3495
3598
|
message: "malformed action wire"
|
|
3496
3599
|
};
|
|
3497
3600
|
const wire = candidate;
|
|
3498
|
-
if (
|
|
3601
|
+
if (client != null && wire.type === "request") wire.context.originClient = client.toJsonObject();
|
|
3499
3602
|
const resultWire = (await (await this._runtime.handleActionPayloadWire(wire)).waitForResultPayload()).toJsonObject();
|
|
3500
|
-
if (
|
|
3603
|
+
if (crypto != null && "c" in request) return {
|
|
3501
3604
|
k: "act",
|
|
3502
|
-
c: bytesToBase64(await
|
|
3605
|
+
c: bytesToBase64(await crypto.encryptFrame(textEncoder.encode(JSON.stringify(resultWire))))
|
|
3503
3606
|
};
|
|
3504
3607
|
return {
|
|
3505
3608
|
k: "act",
|
|
@@ -4076,4 +4179,4 @@ Object.defineProperty(exports, "runtimeLinkId", {
|
|
|
4076
4179
|
}
|
|
4077
4180
|
});
|
|
4078
4181
|
|
|
4079
|
-
//# sourceMappingURL=createHibernatableWsServerAdapter-
|
|
4182
|
+
//# sourceMappingURL=createHibernatableWsServerAdapter-j96U9vgo.cjs.map
|