@nice-code/action 0.25.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 +52 -0
- package/build/{AcceptorHandler-BizUtq4u.d.mts → AcceptorHandler-CLbwu2Pa.d.mts} +74 -16
- package/build/{AcceptorHandler-CxPfZtIl.d.cts → AcceptorHandler-Du292dpC.d.cts} +74 -16
- package/build/{ActionDevtoolsCore-D9KBBI2V.d.cts → ActionDevtoolsCore-DGwzONZT.d.mts} +2 -2
- package/build/{ActionDevtoolsCore-xZjAtB4H.d.mts → ActionDevtoolsCore-dH4K4w3B.d.cts} +2 -2
- package/build/advanced/index.cjs +1 -1
- package/build/advanced/index.d.cts +12 -6
- package/build/advanced/index.d.mts +12 -6
- package/build/advanced/index.mjs +1 -1
- package/build/{createHibernatableWsServerAdapter-BkjESd01.mjs → createHibernatableWsServerAdapter-BD5n-Ev9.mjs} +176 -75
- package/build/createHibernatableWsServerAdapter-BD5n-Ev9.mjs.map +1 -0
- package/build/{createHibernatableWsServerAdapter-FSDWrxoF.cjs → createHibernatableWsServerAdapter-j96U9vgo.cjs} +175 -74
- package/build/{createHibernatableWsServerAdapter-BkjESd01.mjs.map → createHibernatableWsServerAdapter-j96U9vgo.cjs.map} +1 -1
- 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-BQYaXI9j.cjs → httpAcceptorCarrier-By0Qa__L.cjs} +2 -2
- package/build/httpAcceptorCarrier-By0Qa__L.cjs.map +1 -0
- package/build/{httpAcceptorCarrier-DWqsCz3h.mjs → httpAcceptorCarrier-moSmtBxr.mjs} +2 -2
- package/build/httpAcceptorCarrier-moSmtBxr.mjs.map +1 -0
- package/build/index.cjs +2 -2
- package/build/index.cjs.map +1 -1
- package/build/index.d.cts +1 -1
- package/build/index.d.mts +1 -1
- package/build/index.mjs +2 -2
- package/build/index.mjs.map +1 -1
- package/build/platform/cloudflare/index.cjs +1 -1
- package/build/platform/cloudflare/index.d.cts +1 -1
- package/build/platform/cloudflare/index.d.mts +1 -1
- package/build/platform/cloudflare/index.mjs +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-FSDWrxoF.cjs.map +0 -1
- package/build/httpAcceptorCarrier-BQYaXI9j.cjs.map +0 -1
- package/build/httpAcceptorCarrier-DWqsCz3h.mjs.map +0 -1
|
@@ -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_";
|
|
@@ -1732,6 +1732,16 @@ function createInMemoryTofuVerifyKeyResolver() {
|
|
|
1732
1732
|
* Storage-backed trust-on-first-use resolver: pins survive process restarts / Durable Object eviction
|
|
1733
1733
|
* (e.g. back it with `createDurableObjectStorageAdapter`). Same policy as the in-memory variant — trust
|
|
1734
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.
|
|
1735
1745
|
*/
|
|
1736
1746
|
function createStorageTofuVerifyKeyResolver(storageAdapter) {
|
|
1737
1747
|
const storage = createTypedStorage({ storageAdapter });
|
|
@@ -1785,6 +1795,13 @@ function createClientHandshake(config) {
|
|
|
1785
1795
|
bindVerifyKeysIntoDerivation: true
|
|
1786
1796
|
} : {}
|
|
1787
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;
|
|
1788
1805
|
const challenge = buildHandshakeChallenge({
|
|
1789
1806
|
securityLevel,
|
|
1790
1807
|
dictionaryVersion,
|
|
@@ -1800,7 +1817,8 @@ function createClientHandshake(config) {
|
|
|
1800
1817
|
pending = {
|
|
1801
1818
|
linkedServerId,
|
|
1802
1819
|
server: welcome.server,
|
|
1803
|
-
challenge
|
|
1820
|
+
challenge,
|
|
1821
|
+
encryptionKeyMaterial
|
|
1804
1822
|
};
|
|
1805
1823
|
return {
|
|
1806
1824
|
t: "prove",
|
|
@@ -1819,7 +1837,8 @@ function createClientHandshake(config) {
|
|
|
1819
1837
|
return {
|
|
1820
1838
|
linkedClientId: pending.linkedServerId,
|
|
1821
1839
|
remote: pending.server,
|
|
1822
|
-
securityLevel
|
|
1840
|
+
securityLevel,
|
|
1841
|
+
encryptionKeyMaterial: pending.encryptionKeyMaterial
|
|
1823
1842
|
};
|
|
1824
1843
|
}
|
|
1825
1844
|
};
|
|
@@ -1914,6 +1933,16 @@ function createServerHandshake(config) {
|
|
|
1914
1933
|
/** The completed handshake result once `onProve` has accepted, else `undefined`. */
|
|
1915
1934
|
getResult() {
|
|
1916
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
|
+
});
|
|
1917
1946
|
}
|
|
1918
1947
|
};
|
|
1919
1948
|
}
|
|
@@ -1944,14 +1973,16 @@ function parseJsonActionFrame(message) {
|
|
|
1944
1973
|
const ENCRYPTED_ENVELOPE_LENGTH = 2;
|
|
1945
1974
|
/**
|
|
1946
1975
|
* Build the encrypt/decrypt transform for a connection whose handshake settled on the `encrypted`
|
|
1947
|
-
* 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.
|
|
1948
1978
|
*/
|
|
1949
|
-
function createActionFrameCrypto({ link,
|
|
1979
|
+
function createActionFrameCrypto({ link, keyMaterial }) {
|
|
1980
|
+
const keyPromise = link.deriveSharedAesGcmKey(keyMaterial);
|
|
1950
1981
|
return {
|
|
1951
1982
|
async encryptFrame(frame) {
|
|
1952
|
-
const { nonce, ciphertext } = await
|
|
1953
|
-
|
|
1954
|
-
|
|
1983
|
+
const { nonce, ciphertext } = await encryptBytesWithAesGcmKey({
|
|
1984
|
+
dataToEncrypt: frame,
|
|
1985
|
+
aesGcmKey: await keyPromise
|
|
1955
1986
|
});
|
|
1956
1987
|
return pack([nonce, ciphertext]);
|
|
1957
1988
|
},
|
|
@@ -1961,12 +1992,12 @@ function createActionFrameCrypto({ link, linkedClientId }) {
|
|
|
1961
1992
|
if (!Array.isArray(envelope) || envelope.length !== ENCRYPTED_ENVELOPE_LENGTH) throw new Error("[ws-crypto] malformed encrypted frame envelope");
|
|
1962
1993
|
const [nonce, ciphertext] = envelope;
|
|
1963
1994
|
if (!(nonce instanceof Uint8Array) || !(ciphertext instanceof Uint8Array)) throw new Error("[ws-crypto] malformed encrypted frame fields");
|
|
1964
|
-
return await
|
|
1965
|
-
linkedClientId,
|
|
1995
|
+
return await decryptBytesWithAesGcmKey({
|
|
1966
1996
|
dataToDecrypt: {
|
|
1967
1997
|
nonce,
|
|
1968
1998
|
ciphertext
|
|
1969
|
-
}
|
|
1999
|
+
},
|
|
2000
|
+
aesGcmKey: await keyPromise
|
|
1970
2001
|
});
|
|
1971
2002
|
}
|
|
1972
2003
|
};
|
|
@@ -2077,9 +2108,9 @@ var AcceptorSecureSession = class {
|
|
|
2077
2108
|
_complete(result) {
|
|
2078
2109
|
this._authed = true;
|
|
2079
2110
|
this._handshake = void 0;
|
|
2080
|
-
if (result.securityLevel === "encrypted") this._pipe = this._buildPipe(createActionFrameCrypto({
|
|
2111
|
+
if (result.securityLevel === "encrypted" && result.encryptionKeyMaterial != null) this._pipe = this._buildPipe(createActionFrameCrypto({
|
|
2081
2112
|
link: this.config.link,
|
|
2082
|
-
|
|
2113
|
+
keyMaterial: result.encryptionKeyMaterial
|
|
2083
2114
|
}));
|
|
2084
2115
|
this.config.onAuthenticated({
|
|
2085
2116
|
client: new RuntimeCoordinate(result.remote),
|
|
@@ -2098,17 +2129,10 @@ var AcceptorSecureSession = class {
|
|
|
2098
2129
|
this._authed = true;
|
|
2099
2130
|
if (state.securityLevel !== "encrypted" || state.keyMaterial == null) return;
|
|
2100
2131
|
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({
|
|
2132
|
+
const { keyMaterial } = state;
|
|
2133
|
+
const cryptoReady = link.initialize().then(() => createActionFrameCrypto({
|
|
2110
2134
|
link,
|
|
2111
|
-
|
|
2135
|
+
keyMaterial
|
|
2112
2136
|
}));
|
|
2113
2137
|
cryptoReady.catch((err) => console.error("[ws-server] failed to restore encrypted session", err));
|
|
2114
2138
|
this._pipe = this._buildPipe(cryptoReady);
|
|
@@ -2887,11 +2911,9 @@ async function runConnectorExchangeHandshake(carrier, secure) {
|
|
|
2887
2911
|
dictionaryVersion: secure.dictionaryVersion,
|
|
2888
2912
|
securityLevel: secure.securityLevel
|
|
2889
2913
|
});
|
|
2890
|
-
const hsid = nanoid();
|
|
2891
2914
|
const hello = await handshake.createHello();
|
|
2892
2915
|
const welcomeReply = decodeExchangeReply(asString(await carrier.exchange(encodeExchange({
|
|
2893
2916
|
k: "hs",
|
|
2894
|
-
hsid,
|
|
2895
2917
|
m: encodeHandshakeMessage(hello)
|
|
2896
2918
|
}))));
|
|
2897
2919
|
if (welcomeReply?.k !== "hs") throw new Error("[exchange-handshake] expected a welcome reply");
|
|
@@ -2899,11 +2921,12 @@ async function runConnectorExchangeHandshake(carrier, secure) {
|
|
|
2899
2921
|
if (welcome == null) throw new Error("[exchange-handshake] malformed welcome");
|
|
2900
2922
|
if (welcome.t === "reject") throw new Error(`[exchange-handshake] rejected by peer: ${welcome.reason}`);
|
|
2901
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");
|
|
2902
2925
|
const prove = await handshake.onWelcome(welcome);
|
|
2903
2926
|
const acceptReply = decodeExchangeReply(asString(await carrier.exchange(encodeExchange({
|
|
2904
2927
|
k: "hs",
|
|
2905
|
-
|
|
2906
|
-
|
|
2928
|
+
m: encodeHandshakeMessage(prove),
|
|
2929
|
+
hsc: welcomeReply.hsc
|
|
2907
2930
|
}))));
|
|
2908
2931
|
if (acceptReply?.k !== "hs") throw new Error("[exchange-handshake] expected an accept reply");
|
|
2909
2932
|
const accept = decodeHandshakeMessage(acceptReply.m);
|
|
@@ -2912,9 +2935,9 @@ async function runConnectorExchangeHandshake(carrier, secure) {
|
|
|
2912
2935
|
if (accept.t !== "accept") throw new Error(`[exchange-handshake] expected accept, got ${accept.t}`);
|
|
2913
2936
|
if (acceptReply.t == null) throw new Error("[exchange-handshake] accept missing session token");
|
|
2914
2937
|
const result = await handshake.onAccept(accept);
|
|
2915
|
-
const crypto = result.securityLevel === "encrypted" ? createActionFrameCrypto({
|
|
2938
|
+
const crypto = result.securityLevel === "encrypted" && result.encryptionKeyMaterial != null ? createActionFrameCrypto({
|
|
2916
2939
|
link: secure.link,
|
|
2917
|
-
|
|
2940
|
+
keyMaterial: result.encryptionKeyMaterial
|
|
2918
2941
|
}) : void 0;
|
|
2919
2942
|
return {
|
|
2920
2943
|
token: acceptReply.t,
|
|
@@ -3222,9 +3245,9 @@ async function runClientHandshake(channel, secure, nextHandshakeMessage) {
|
|
|
3222
3245
|
if (accept.t === "reject") throw new Error(`[link-handshake] rejected by peer: ${accept.reason}`);
|
|
3223
3246
|
if (accept.t !== "accept") throw new Error(`[link-handshake] expected accept, got ${accept.t}`);
|
|
3224
3247
|
const result = await handshake.onAccept(accept);
|
|
3225
|
-
return result.securityLevel === "encrypted" ? createActionFrameCrypto({
|
|
3248
|
+
return result.securityLevel === "encrypted" && result.encryptionKeyMaterial != null ? createActionFrameCrypto({
|
|
3226
3249
|
link: secure.link,
|
|
3227
|
-
|
|
3250
|
+
keyMaterial: result.encryptionKeyMaterial
|
|
3228
3251
|
}) : void 0;
|
|
3229
3252
|
}
|
|
3230
3253
|
function buildSendMethods(ctx, pipe, disconnectListeners, abortSet) {
|
|
@@ -3378,82 +3401,154 @@ var LinkTransport = class LinkTransport extends Transport {
|
|
|
3378
3401
|
}
|
|
3379
3402
|
};
|
|
3380
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
|
|
3381
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;
|
|
3382
3453
|
const textEncoder = new TextEncoder();
|
|
3383
3454
|
const textDecoder = new TextDecoder();
|
|
3384
3455
|
/**
|
|
3385
3456
|
* Acceptor (accept-in) side of the secure exchange protocol — the HTTP counterpart to
|
|
3386
3457
|
* {@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.
|
|
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.
|
|
3391
3461
|
*
|
|
3392
|
-
*
|
|
3393
|
-
*
|
|
3394
|
-
*
|
|
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".
|
|
3395
3468
|
*/
|
|
3396
3469
|
var ExchangeAcceptor = class {
|
|
3397
3470
|
_security;
|
|
3398
3471
|
_runtime;
|
|
3399
3472
|
_allowedLevels;
|
|
3400
3473
|
_noneAllowed;
|
|
3401
|
-
|
|
3402
|
-
|
|
3474
|
+
_sealer;
|
|
3475
|
+
_sessionTtlMs;
|
|
3403
3476
|
constructor(config) {
|
|
3404
3477
|
this._security = config.security;
|
|
3405
3478
|
this._runtime = config.runtime;
|
|
3406
3479
|
this._allowedLevels = Array.isArray(config.security.securityLevel) ? config.security.securityLevel : [config.security.securityLevel];
|
|
3407
3480
|
this._noneAllowed = this._allowedLevels.includes("none");
|
|
3481
|
+
this._sealer = createExchangeTicketSealer(config.security.link);
|
|
3482
|
+
this._sessionTtlMs = config.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS;
|
|
3408
3483
|
}
|
|
3409
3484
|
/** Process one POST body (an exchange envelope), returning the reply body to send back. */
|
|
3410
3485
|
async handlePost(body) {
|
|
3411
3486
|
const request = decodeExchangeRequest(body);
|
|
3412
3487
|
if (request == null) return this._err("malformed exchange request");
|
|
3488
|
+
await this._security.link.initialize();
|
|
3413
3489
|
if (request.k === "hs") return encodeExchange(await this._handleHandshake(request));
|
|
3414
3490
|
return encodeExchange(await this._handleAction(request));
|
|
3415
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
|
+
}
|
|
3416
3502
|
async _handleHandshake(request) {
|
|
3417
3503
|
const message = decodeHandshakeMessage(request.m);
|
|
3418
3504
|
if (message == null) return {
|
|
3419
3505
|
k: "err",
|
|
3420
3506
|
message: "malformed handshake message"
|
|
3421
3507
|
};
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
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
|
+
};
|
|
3434
3526
|
}
|
|
3435
|
-
if (message.t === "hello") return {
|
|
3436
|
-
k: "hs",
|
|
3437
|
-
m: encodeHandshakeMessage(await handshake.onHello(message))
|
|
3438
|
-
};
|
|
3439
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);
|
|
3440
3539
|
const reply = await handshake.onProve(message);
|
|
3441
|
-
this._pendingHandshakes.delete(request.hsid);
|
|
3442
3540
|
const result = handshake.getResult();
|
|
3443
3541
|
if (reply.t === "accept" && result != null) {
|
|
3444
|
-
const
|
|
3445
|
-
|
|
3446
|
-
client: new RuntimeCoordinate(result.remote),
|
|
3542
|
+
const ticket = {
|
|
3543
|
+
client: result.remote,
|
|
3447
3544
|
securityLevel: result.securityLevel,
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
}) : void 0
|
|
3452
|
-
});
|
|
3545
|
+
keyMaterial: result.encryptionKeyMaterial
|
|
3546
|
+
};
|
|
3547
|
+
const t = await this._sealer.seal(ticket, this._sessionTtlMs);
|
|
3453
3548
|
return {
|
|
3454
3549
|
k: "hs",
|
|
3455
3550
|
m: encodeHandshakeMessage(reply),
|
|
3456
|
-
t
|
|
3551
|
+
t
|
|
3457
3552
|
};
|
|
3458
3553
|
}
|
|
3459
3554
|
return {
|
|
@@ -3467,20 +3562,26 @@ var ExchangeAcceptor = class {
|
|
|
3467
3562
|
};
|
|
3468
3563
|
}
|
|
3469
3564
|
async _handleAction(request) {
|
|
3470
|
-
let
|
|
3565
|
+
let client;
|
|
3566
|
+
let crypto;
|
|
3471
3567
|
let candidate;
|
|
3472
3568
|
if (request.t != null) {
|
|
3473
|
-
|
|
3474
|
-
if (
|
|
3569
|
+
const ticket = await this._sealer.open(request.t);
|
|
3570
|
+
if (ticket == null) return {
|
|
3475
3571
|
k: "err",
|
|
3476
3572
|
message: "unknown or expired session token"
|
|
3477
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;
|
|
3478
3579
|
if ("c" in request) {
|
|
3479
|
-
if (
|
|
3580
|
+
if (crypto == null) return {
|
|
3480
3581
|
k: "err",
|
|
3481
3582
|
message: "session is not encrypted"
|
|
3482
3583
|
};
|
|
3483
|
-
const plain = await
|
|
3584
|
+
const plain = await crypto.decryptFrame(base64ToBytes(request.c));
|
|
3484
3585
|
candidate = JSON.parse(textDecoder.decode(plain));
|
|
3485
3586
|
} else candidate = request.w;
|
|
3486
3587
|
} else {
|
|
@@ -3495,11 +3596,11 @@ var ExchangeAcceptor = class {
|
|
|
3495
3596
|
message: "malformed action wire"
|
|
3496
3597
|
};
|
|
3497
3598
|
const wire = candidate;
|
|
3498
|
-
if (
|
|
3599
|
+
if (client != null && wire.type === "request") wire.context.originClient = client.toJsonObject();
|
|
3499
3600
|
const resultWire = (await (await this._runtime.handleActionPayloadWire(wire)).waitForResultPayload()).toJsonObject();
|
|
3500
|
-
if (
|
|
3601
|
+
if (crypto != null && "c" in request) return {
|
|
3501
3602
|
k: "act",
|
|
3502
|
-
c: bytesToBase64(await
|
|
3603
|
+
c: bytesToBase64(await crypto.encryptFrame(textEncoder.encode(JSON.stringify(resultWire))))
|
|
3503
3604
|
};
|
|
3504
3605
|
return {
|
|
3505
3606
|
k: "act",
|
|
@@ -3717,4 +3818,4 @@ function createHibernatableWsServerAdapter(options) {
|
|
|
3717
3818
|
//#endregion
|
|
3718
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 };
|
|
3719
3820
|
|
|
3720
|
-
//# sourceMappingURL=createHibernatableWsServerAdapter-
|
|
3821
|
+
//# sourceMappingURL=createHibernatableWsServerAdapter-BD5n-Ev9.mjs.map
|