From ebcb1eab81287d8a98af5410257280442588e114 Mon Sep 17 00:00:00 2001 From: Skylar Sadlier Date: Fri, 22 May 2026 14:40:00 -0600 Subject: [PATCH] Upgrade to matrix-js-sdk 41.5.0; add device verification Upgrades matrix-js-sdk from 34.13.0 to 41.5.0. This crosses the v37 removal of the legacy libolm crypto stack, so E2EE is migrated to the Rust crypto implementation. Also adds device verification, cross-signing setup, and authenticated media support. Dependencies - Bump matrix-js-sdk ^34.13.0 -> ^41.5.0; require Node.js >= 22. - Drop the `olm` dependency (legacy crypto only); add `fake-indexeddb`. Rust crypto - Replace initCrypto() with initRustCrypto(); the legacy crypto stack was removed upstream in v37. - Add src/matrix-crypto-store.js: the Rust crypto store requires IndexedDB, absent in Node.js, so it is backed by fake-indexeddb and snapshotted to disk (rust-crypto-store.v8) to survive restarts. - Migrate existing libolm crypto state into the Rust store on first run, and discard the stored crypto state when the device ID changes. Homeserver discovery - Resolve the homeserver via .well-known, so a delegating domain (e.g. example.org) works as the configured server URL. Cross-signing & secure backup - Add a secured /matrix-chat/secure-backup admin endpoint and a modal dialog on the server config node: check status, unlock an existing secure backup with its recovery key, or reset and create a new one. Device verification (new nodes) - matrix-verification: event source emitting verification requests and phase changes, with on-node filters (phase, initiated by, type, self-verification, user allowlist, room). - matrix-verification-action: request, accept, start SAS, confirm, mismatch, or cancel an in-flight verification. Authenticated media - matrix-receive and matrix-crypt-file use the authenticated media endpoints, send a bearer token via msg.headers, and fall back between the v3 and v1 media endpoints on a 404. Fixes - Surface connection/auth errors in the log; node.error() calls were passed an empty msg object, which routed the error and suppressed console logging. - matrix-get-user: await getProfileInfo()/getPresence(). - matrix-invite-room: pass the reason as the third invite() argument (the removed callback parameter was shifting it out). - Guard the verification handlers so a throwing SDK getter cannot crash Node-RED. Docs - Add the device-verification example flow; update the READMEs and node help, correcting stale claims that device verification, secure backup, and encrypted file uploads were unsupported. --- README.md | 19 +- examples/README.md | 21 + examples/device-verification-flow.json | 548 ++++++++++++ examples/device-verification-flow.png | Bin 0 -> 142087 bytes package-lock.json | 195 ++--- package.json | 15 +- src/matrix-crypt-file.html | 12 +- src/matrix-crypt-file.js | 55 +- src/matrix-crypto-store.js | 175 ++++ src/matrix-get-user.js | 8 +- src/matrix-invite-room.js | 5 +- src/matrix-receive.html | 7 +- src/matrix-receive.js | 36 +- src/matrix-server-config.html | 329 ++++++-- src/matrix-server-config.js | 1062 +++++++++++++++++------- src/matrix-verification-action.html | 126 +++ src/matrix-verification-action.js | 139 ++++ src/matrix-verification.html | 199 +++++ src/matrix-verification.js | 113 +++ 19 files changed, 2528 insertions(+), 536 deletions(-) create mode 100644 examples/device-verification-flow.json create mode 100644 examples/device-verification-flow.png create mode 100644 src/matrix-crypto-store.js create mode 100644 src/matrix-verification-action.html create mode 100644 src/matrix-verification-action.js create mode 100644 src/matrix-verification.html create mode 100644 src/matrix-verification.js diff --git a/README.md b/README.md index 55e0f04..04d8b44 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,13 @@ Join our public Matrix room for help: [#node-red-contrib-matrix-chat:skylar.tech Supported functionality in this package includes: -- **End-to-end encryption (E2EE)** - - [Work in progress](#end-to-end-encryption-notes) - - Alternative: Use [Pantalaimon](https://github.com/matrix-org/pantalaimon) for E2EE key synchronization across sessions +- **End-to-end encryption (E2EE)** — send and receive encrypted messages (see the [encryption notes](#end-to-end-encryption-notes)) +- **Cross-signing & secure backup** — interactive setup from the server config node so the bot's own device shows as verified +- **Device verification** — flow-driven SAS (emoji) verification via the `matrix-verification` and `matrix-verification-action` nodes - **Receive events** from rooms: Messages, reactions, images, audio, locations, files, encrypted or unencrypted - **Fetch/modify room state**: Update room settings - **Paginate room history** -- **Send files** (encryption support for files coming soon) +- **Send files** to rooms, encrypted or unencrypted - **Send/edit messages** (supports plain text and HTML formats) - **Send typing notifications** - **Delete events** (messages, reactions, etc.) @@ -33,6 +33,8 @@ These features allow you to easily build bots, set up chat relays, or even admin ### Installing +**Requires Node.js 22 or newer** (this is a requirement of the bundled `matrix-js-sdk`). + Install through Node-RED's UI by searching for `node-red-contrib-matrix-chat`, or use the following command inside your Node-RED directory: ```bash @@ -51,11 +53,12 @@ You're not limited to just the nodes we've created. Enable global access in your ### End-to-End Encryption Notes -- This module doesn't handle encryption key synchronization between devices. It’s recommended to use the bot exclusively in Node-RED to prevent issues with E2EE messages. -- **Storage:** Keys for E2EE are saved in a folder called `matrix-client-storage` within your Node-RED directory. Back up this folder regularly! If lost, you won’t be able to decrypt messages from E2EE rooms. +- E2EE uses the Rust crypto stack from `matrix-js-sdk`. The first time a bot starts after upgrading from an older version, any existing (legacy libolm) crypto state is migrated automatically. +- **Storage:** E2EE state is saved in a folder called `matrix-client-storage` within your Node-RED directory. Each account's Rust crypto store is persisted there as `rust-crypto-store.v8` (snapshotted on shutdown and every 5 minutes). Back up this folder regularly! If lost, you won’t be able to decrypt messages from E2EE rooms. - To move your bot to a different installation, migrate this folder and ensure the old and new clients don't run simultaneously. - -Interested in helping? Contributions to finalize E2EE support are welcome! +- It’s simplest to dedicate the account to the bot and run it only within Node-RED. The account can also be signed in elsewhere — if so, verify those sessions against the bot (see below) so they trust each other and share keys. +- **Cross-signing & secure backup:** open the server config node and use the **Set up secure backup & cross-signing** button. It checks the account and lets you unlock an existing secure backup with its recovery key, or create a fresh one — after which the bot's own device is cross-signed and shows as verified to others. +- **Device verification:** the `matrix-verification` node emits verification requests and phase changes, and `matrix-verification-action` accepts, starts, confirms, or cancels them — so you can build your own approval flow (e.g. emailing the SAS emoji for a human to confirm). See the [device verification example](https://github.com/Skylar-Tech/node-red-contrib-matrix-chat/tree/master/examples#device-verification). ### Registering a User diff --git a/examples/README.md b/examples/README.md index 8521c45..223d3d0 100644 --- a/examples/README.md +++ b/examples/README.md @@ -431,6 +431,27 @@ Downloads received files/images. If the file is encrypted, it will decrypt it fo +### Device Verification + +
+Handle device verification (SAS / emoji) + +[View JSON](device-verification-flow.json) + +An end-to-end example of interactive device verification. The `matrix-verification` node emits every verification request and phase change; the flow routes by phase, automatically **accepts** incoming requests and **starts SAS**, then surfaces the SAS emoji so a human can compare it. Inject nodes let you **confirm** or **reject** the match, and there are paths to have the bot **request** verification of a specific user's device, or a user in a room. + +Requires end-to-end encryption to be enabled on the server config node. For the bot's own device to be trusted by others, also set up cross-signing via the **Set up secure backup & cross-signing** button on the server config node. + +**Instructions:** + +1. Import the flow and set the Matrix server config on each matrix node. +2. Replace the `@CHANGE_ME:example.org` / `CHANGE_ME` placeholders in the "Verify a user" inject nodes if you want to use the bot-initiated paths. +3. To verify the bot from another client, start a verification with it, watch the debug sidebar for the `sas` event, compare the emoji, then click the **Confirm SAS match** inject. + +![device-verification-flow.png](device-verification-flow.png) + +
+ ### Deprecated
diff --git a/examples/device-verification-flow.json b/examples/device-verification-flow.json new file mode 100644 index 0000000..e916ed2 --- /dev/null +++ b/examples/device-verification-flow.json @@ -0,0 +1,548 @@ +[ + { + "id": "7158964bd67edc52", + "type": "group", + "z": "vtest", + "name": "Example verification flow", + "style": { + "label": true + }, + "nodes": [ + "40c105c38054d6db", + "83f785d52a61009a", + "d51bab8cbf5f247c", + "2e543533d49b467c" + ], + "x": 88, + "y": 73, + "w": 1044, + "h": 754 + }, + { + "id": "40c105c38054d6db", + "type": "group", + "z": "vtest", + "g": "7158964bd67edc52", + "name": "Verification request handling", + "style": { + "label": true + }, + "nodes": [ + "mv_all", + "dbg_events", + "sw_phase", + "act_accept", + "act_start", + "chg_savevid", + "dbg_sas", + "dbg_done", + "dbg_cancelled", + "dbg_err" + ], + "x": 114, + "y": 99, + "w": 992, + "h": 342 + }, + { + "id": "mv_all", + "type": "matrix-verification", + "z": "vtest", + "g": "40c105c38054d6db", + "name": "All verifications", + "server": null, + "phaseRequested": true, + "phaseReady": true, + "phaseStarted": true, + "phaseSas": true, + "phaseDone": true, + "phaseCancelled": true, + "initiatedBy": "any", + "verificationType": "any", + "selfVerification": "any", + "userFilter": "", + "roomFilter": "", + "x": 220, + "y": 180, + "wires": [ + [ + "dbg_events", + "sw_phase" + ] + ] + }, + { + "id": "dbg_events", + "type": "debug", + "z": "vtest", + "g": "40c105c38054d6db", + "name": "all verification events", + "active": true, + "tosidebar": true, + "complete": "true", + "x": 500, + "y": 140, + "wires": [] + }, + { + "id": "sw_phase", + "type": "switch", + "z": "vtest", + "g": "40c105c38054d6db", + "name": "route by phase", + "property": "phase", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "requested", + "vt": "str" + }, + { + "t": "eq", + "v": "ready", + "vt": "str" + }, + { + "t": "eq", + "v": "sas", + "vt": "str" + }, + { + "t": "eq", + "v": "done", + "vt": "str" + }, + { + "t": "eq", + "v": "cancelled", + "vt": "str" + } + ], + "checkall": "true", + "outputs": 5, + "x": 460, + "y": 220, + "wires": [ + [ + "act_accept" + ], + [ + "act_start" + ], + [ + "chg_savevid" + ], + [ + "dbg_done" + ], + [ + "dbg_cancelled" + ] + ] + }, + { + "id": "act_accept", + "type": "matrix-verification-action", + "z": "vtest", + "g": "40c105c38054d6db", + "name": "Accept", + "server": null, + "mode": "accept", + "x": 700, + "y": 180, + "wires": [ + [], + [ + "dbg_err" + ] + ] + }, + { + "id": "act_start", + "type": "matrix-verification-action", + "z": "vtest", + "g": "40c105c38054d6db", + "name": "Start SAS", + "server": null, + "mode": "start", + "x": 700, + "y": 230, + "wires": [ + [], + [ + "dbg_err" + ] + ] + }, + { + "id": "chg_savevid", + "type": "change", + "z": "vtest", + "g": "40c105c38054d6db", + "name": "save verificationId", + "rules": [ + { + "t": "set", + "p": "verificationId", + "pt": "flow", + "to": "verificationId", + "tot": "msg" + } + ], + "x": 710, + "y": 290, + "wires": [ + [ + "dbg_sas" + ] + ] + }, + { + "id": "dbg_sas", + "type": "debug", + "z": "vtest", + "g": "40c105c38054d6db", + "name": "SAS emoji (msg.sas)", + "active": true, + "tosidebar": true, + "complete": "true", + "x": 960, + "y": 290, + "wires": [] + }, + { + "id": "dbg_done", + "type": "debug", + "z": "vtest", + "g": "40c105c38054d6db", + "name": "verification done", + "active": true, + "tosidebar": true, + "complete": "true", + "x": 710, + "y": 350, + "wires": [] + }, + { + "id": "dbg_cancelled", + "type": "debug", + "z": "vtest", + "g": "40c105c38054d6db", + "name": "verification cancelled", + "active": true, + "tosidebar": true, + "complete": "true", + "x": 730, + "y": 400, + "wires": [] + }, + { + "id": "dbg_err", + "type": "debug", + "z": "vtest", + "g": "40c105c38054d6db", + "name": "action errors", + "active": true, + "tosidebar": true, + "complete": "true", + "x": 940, + "y": 200, + "wires": [] + }, + { + "id": "83f785d52a61009a", + "type": "group", + "z": "vtest", + "g": "7158964bd67edc52", + "name": "Confirm or reject last verification request", + "style": { + "label": true + }, + "nodes": [ + "inj_confirm", + "chg_vid_c", + "act_confirm", + "inj_reject", + "chg_vid_r", + "act_mismatch", + "dbg_result" + ], + "x": 114, + "y": 459, + "w": 982, + "h": 142 + }, + { + "id": "inj_confirm", + "type": "inject", + "z": "vtest", + "g": "83f785d52a61009a", + "name": "Confirm SAS match", + "props": [], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 250, + "y": 500, + "wires": [ + [ + "chg_vid_c" + ] + ] + }, + { + "id": "chg_vid_c", + "type": "change", + "z": "vtest", + "g": "83f785d52a61009a", + "name": "verificationId from flow", + "rules": [ + { + "t": "set", + "p": "verificationId", + "pt": "msg", + "to": "verificationId", + "tot": "flow" + } + ], + "x": 500, + "y": 500, + "wires": [ + [ + "act_confirm" + ] + ] + }, + { + "id": "act_confirm", + "type": "matrix-verification-action", + "z": "vtest", + "g": "83f785d52a61009a", + "name": "Confirm SAS", + "server": null, + "mode": "confirm", + "x": 750, + "y": 500, + "wires": [ + [ + "dbg_result" + ], + [ + "dbg_err" + ] + ] + }, + { + "id": "inj_reject", + "type": "inject", + "z": "vtest", + "g": "83f785d52a61009a", + "name": "Reject SAS (mismatch)", + "props": [], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 260, + "y": 560, + "wires": [ + [ + "chg_vid_r" + ] + ] + }, + { + "id": "chg_vid_r", + "type": "change", + "z": "vtest", + "g": "83f785d52a61009a", + "name": "verificationId from flow", + "rules": [ + { + "t": "set", + "p": "verificationId", + "pt": "msg", + "to": "verificationId", + "tot": "flow" + } + ], + "x": 500, + "y": 560, + "wires": [ + [ + "act_mismatch" + ] + ] + }, + { + "id": "act_mismatch", + "type": "matrix-verification-action", + "z": "vtest", + "g": "83f785d52a61009a", + "name": "Reject (mismatch)", + "server": null, + "mode": "mismatch", + "x": 760, + "y": 560, + "wires": [ + [ + "dbg_result" + ], + [ + "dbg_err" + ] + ] + }, + { + "id": "dbg_result", + "type": "debug", + "z": "vtest", + "g": "83f785d52a61009a", + "name": "action result", + "active": true, + "tosidebar": true, + "complete": "true", + "x": 980, + "y": 530, + "wires": [] + }, + { + "id": "d51bab8cbf5f247c", + "type": "group", + "z": "vtest", + "g": "7158964bd67edc52", + "name": "Request verification with specific user & device", + "style": { + "label": true + }, + "nodes": [ + "inj_request", + "act_request" + ], + "x": 114, + "y": 619, + "w": 512, + "h": 82 + }, + { + "id": "inj_request", + "type": "inject", + "z": "vtest", + "g": "d51bab8cbf5f247c", + "name": "Verify a user & device", + "props": [ + { + "p": "userId", + "v": "@CHANGE_ME:example.org", + "vt": "str" + }, + { + "p": "deviceId", + "v": "CHANGE_ME", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 260, + "y": 660, + "wires": [ + [ + "act_request" + ] + ] + }, + { + "id": "act_request", + "type": "matrix-verification-action", + "z": "vtest", + "g": "d51bab8cbf5f247c", + "name": "Request verification", + "server": null, + "mode": "request", + "x": 510, + "y": 660, + "wires": [ + [ + "dbg_result" + ], + [ + "dbg_err" + ] + ] + }, + { + "id": "2e543533d49b467c", + "type": "group", + "z": "vtest", + "g": "7158964bd67edc52", + "name": "Request verification with specific user & room", + "style": { + "label": true + }, + "nodes": [ + "f7c043d39780b9a4", + "b2807fd5125b56b4" + ], + "x": 114, + "y": 719, + "w": 512, + "h": 82 + }, + { + "id": "f7c043d39780b9a4", + "type": "inject", + "z": "vtest", + "g": "2e543533d49b467c", + "name": "Verify a user & room", + "props": [ + { + "p": "userId", + "v": "@CHANGE_ME:example.org", + "vt": "str" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "CHANGE_ME", + "x": 250, + "y": 760, + "wires": [ + [ + "b2807fd5125b56b4" + ] + ] + }, + { + "id": "b2807fd5125b56b4", + "type": "matrix-verification-action", + "z": "vtest", + "g": "2e543533d49b467c", + "name": "Request verification", + "server": null, + "mode": "request", + "x": 510, + "y": 760, + "wires": [ + [ + "dbg_result" + ], + [ + "dbg_err" + ] + ] + } +] \ No newline at end of file diff --git a/examples/device-verification-flow.png b/examples/device-verification-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..b167c92601637329058602cbf773ce962848e093 GIT binary patch literal 142087 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

LNvfJ=H15${E>&JD z+oCWho$&{f&d3ZjIzBE%?lD~pPA5B3DS>QT$h1LgKhGl)}8= zVe(Fd(6!b(L2G(oBD^~FBc8E*B5Eqm&Uly!bi@+%dftVe9eXFB>Ih7n=omyVi9YnL zj~aOKJyj+;`ou(EcK>uff5)J_Thmw((=7;n3Qq{v1Q+CbK#?}!2Csr}YMDEjjkj77 zsZA>HI$deG1mj@PZ~IoR$0)aedZSeb%^)wk5I8K`z}Gz;jZj32Ht505i6%=An8bKK z^M8Ojqh*^PPS2$W0|f;jEsKRyL}e_>gDVDOJ2t0(Dfv&dvcy8Hc{h;$zP=IL_|T~V zb)H<76|u4X2{^!!QHC6GWA}2>1xkmjMwQHd(`sFfnT1q0DLdeb%>=RJy4#kn{;`upM0bkMay`UrJZsxeT zl9Hg}bic3+-r83{Bd}8C@$JhGktETw+tM~FwEH2o$o+)xanBc^TfU&UT0LxkeAm%9 zA%@_`pHZ$sC}#JG{H6-ty*RuwQ=w0{*P_i=Y~ZV)5Iulw`~fq9wB{N^7SnShKrH8E zsix~g5EL9E887J)Of<*s*zElk%h}-i2p&7)gAVQEMwPhDLYcov5dghrR%U$tvoE`I+*-+|c3! zu;RYLu-$X}yvb@x!7{@}zO%flJGcvw>UpO4YaB^ucPEbeewnv>4D1T__Z66U`$Mcf zmo-h`q5Sc1xw>pb&c3STpN9FqcZ?cJAeSl)(Z)9#v{TJo{1+{OclBX{i60Oa0()F1N`Iiq9!5-N}Ct|){ zQzod%X0R7W{gY1+)>2v?$f~u*0e3N*{Q-YYvW6T5AHno$8`Ye zAcC5EQDEA_%GahC61<-X3Mnxi#Xom1aFTOW8|kD?FW+sPBpxbk$6(%At$ebqvKVD; z6ZaUy1{W7*Z0!bCDS1d$+35)in>mH`eqAK4LHyuknjB!JQh_$$N>Yg9pu$pE5qRp( z0Owcr}To3$cg*apM(11yG&Pv;BP`*F)`*(=unMb`7R%Oh}dt^@G*h$Us zVYR_~$lYpz*Wbb9-jV-%DUM6#B7t#}dm~1o$kj}Js#PP?PvmAUrIKN3d};8jYCPZl znc=AX^GEd}9KB|uw*f5UmBk}9u%F{^Ma##MoLIYngD8hLv}wr_qtSqh@vma zOHux&FYp)>(!1vjOm*MB^SPD)P_6joM=zoYY$guM9go96J-PpM<*ZSDIYxJXDsOfz zWr^0Nqa@*S9IjF*SeHU+t84_>dBugh=wuijD_h8UJ#?LE=e4fuLf!T(4Um`1DBb+T zC8El0Wg!*VL7)_y#F-QRHt^wXC9)RV-JiVap|R;haSZcqz?pGfEXfl>LL1M_PmS$d_Vhg`~O}3!+{0PDdH|OZygg z2?n1nO1bCu($9-KI?1*=w`%aGwa@Y-#M=tq`2e6Ox_qvwP`J(yBN-`-)vfBj1{DF+eZL0Iw^zrR91kgJEHiGT!v$Q2^u@e^^tLRyZz?sUoAG!}=^n zp|yjbkU>&ynj8}}MH9-Qo&5+fv&#f#TX~5p$`8<7+^KCrjNZOTRrhWl^%ljFkAraw z#QVz%Om+`bmqjF4ZLJ~AQ@QBYLoFicnKPUz%Iw5<*5wc-(f$|3&Dx%L{HsMq0x!{s zVP|sFZkhV`lXnRh+O7P2!0{O${ZVY5tC})SKlt&M0kA|rlwc{Y-<04L8nPsh9_26b z7Y8n%#yj@x0I%Hf9nZscFSC?x^+&I59*2yW{Ih`xl3MAdzk^z;^I=K+!yu`N1*2f} z&{JdJSXo8k??O|bKT+U@N-{D-r}sD4#Z}TkaSaWa`Su8V_gyFM)vH;{g@vdEkqd=F z&-$u@pPGigIkq)jz{&~sC`m}eo?RFN|8svk&h^?w#Rm{00)-9HCqraOX-NSJ0p=^O z;t5N=U~U~+yz@Ss2gLMrt;)h-4p}cZobb`5CFVj@?~U2o+4&!8@?&fKq|MT9sm!B> zPyeDzx-GlYt`Xqby!>~m*m$W7ZYe7&S(2=p7j5$T$qpGL{Kcp3C={NMlPDbFn~HVg zvbxOSWNLfPmbS*yolj;yoqDC&bNwSoV01$` z|N1RRBdiHYO8n)ME_79S(#Uo*1>X0?6+7r=UVd5$hM7K=z=F z!)kx>ixui|R`KI}Z+2cj&+~pOcH~tE+Qkj0u~+$U#2*RJos>$x5ZrBNyfpThM4H+; zCI$r}ju8~I21*htg3Fk0gke79Et?rrh)gLre_w^+>ampedI9jBZ7GrxNSf}N-ygin z+)78JX9MR&igYCu%Ml;~Z195$F|21x7wFqf$(oq5jm}&6p%!?PYeo-;Lt z43m&W(28w*gN8@tsOpvs2IZ8_cA=r;5(m~U&6#C(5Bsa@e4zh6sIEfL$hBvhl!VHb zq|=PPE9tUE-xEo*B;tPDrbweKABWy6#TIK!lf^PjbGyC__C4bnh6f7=j zO?pssa8Hq_6?);BSE-=Lwf(ufr!n)3dw2ntyzxN5=1;$*E286^k8o>Z$!wI8iGuA5 zUF)$q@W?XleG=b!HH@xHVOve(jsuZ3HFag77ll~d5Y+WCSY`WF;kEHkS;YkMxn1uY z{z}g+SzkXy>(eSI1c%-&N7*OJiTv9J1pp^wfdh5)Z1Ybj2;(#*U0yIaPr_cqwobpf z4Ur});1;&%LBavbo`ESCa4jRP*{L^oEhHpFt14OxU-t!L?WDMQYRtB#M~NGws{@BK zBMTXy^U*o6rt3l0!1ugMy1GG_r1dBcp?He;=8bWCZW&y3>{P*s0 zS`JbfvQp)%JVs|E0U)%gXmVZ|xI7i2_eMrm-T zsbeYp7G*s@KjC5bv*8ADKfh(*MRmRrNc&8OW4eB7R3-z$LB_9~YV4i(^EriReJbRc z0(5!yI>kY92EE)?&YpPRcYz5YoNnmyi;-umlDGaI;ES-G9eu>i$T~yFJE+A2BnLWP z=8tmS;p4_V^CEGU2>RBQnbf)87;XI*EnM}-Rhmybkz%`ciz}-Qhr+SD%#Ifn8@`%Y z=&b8XxFt)eGP1ghw?dg`(F9j5d7wXYt|qfTNzt4I$tAj=f<#6}9Vn^?t zUZbx0<&t;Oiq)ej7Yzhz^I=ydWTvH!_A-4zzvERNMCow~Ico$i(=i>mB`$NS8LV#{ zwp|>73Zn2?eK7~&n(Q{wxN1B3Af&~Y+*i;1AN{gN;|Zd1bpGT2oMJ%Iu-axzJSa6@ zyKZM$_gEXXX>nK(t=kHMVdKr0m-TuZrn}fj>TT=pq)A$p+pMz80woX+8cYuv&GaEk zLm7=oLxZHV&gCb@0CaS8wCj#`P4g=@u@T9_PIa?c-#4Pywg24qRM~sx)U~(1W$&;2 z!w$Jy;D3qgk7xcswLFhS)=s8jWIP@X<-|hwqJs{98j9^^X%H;KM*W<)FVe4n!^6B8=ZES%UEjr4$-O~}3n}h8V`#T_N}I z$Gi3sMqo=^%?X$l6x?HQj?&53P-2~5)e~q3>&goW{JFzej)pHEz9NKN`S7jQ-6G1$ z^+eR)lS?HVA6CB$BvnKfD}d^j_=jOGL-Y>zf>15U&YQ9Ig@uLL86$~;*<5wWJU~0a zXjbpyFWxEs%EvH%GZA)pZr!@37F9z>LPxcHMgOj(=4P-Z3v}zzf*h_c*A(cW>)h^9 zaZ}erXda~k3DEn4=k^myHkLJlK4l6)N6Lry(X>Gu3SuQWzZ8YQ?sXtvn_v!F`C9Xf zgFOxWI6(crN=t%iDx-jIYnSYEZ_U14x4(y&XSWXpuMpHz~mf!czNgmVA=ZBJkq3WS?6&BLX z7#;FtQ-mqvw*XA4lN}xntnt&D9=1ORnYVU6zJy$x=uNEz|Ca?oms1^sYQ|4+rXGC0 z6CL=9P`|R;m+wJ$+2wkHrJiUGTKOSRd;ol8N_NtEqun4Uld`^JNVL;%BAFOabq z|AXY#WZxdvNVB(j%9RdA9(qV546iqq4 zu+$0!^yJ*!&hhC!_f`pdd?j`-{g44JLM`(Vk}TLju#lkqTHykl{!> zE)5JG-|yWG=O!YG#g<{|ODvDqD?$+if&K8Xyb`i!+TzLPpcH~%vpCCY1E*`YHGB8V ze=dcb^v=ddq{$f#LJi6D1F;bU+^JloEk=P9sgwP51pEa2L|?EX(?Hy?;FFQ*g4?u| zdcf9uw2*`!Cvp%~e~W zi&E3g`&ZQGf8U<}Omh(8hLcOhBqXINYH09kr&-hMu$Nq>8Z1{@y7s67C#8Lym-kHi zF+FBxXHmaVX_w49-!E#;uO1idY0>QOC;dK(J-m29-c}A%X682e?xeiAed6ZM3QDel zd}$DSZljInw2-#%sY&P^asyR*kj0c1`|gxy6ufbfg`E#W;^ymN)X+kIalSa(pXT6|6i<8#jo2ogTNg6Zp-!}NQ4 zknf>Llvj+y6#9_S*!yZBGC3KmGFk)TN1o#d1!Za)yY(E+^mGPS3Obd|Q~8eg@{pt_ zzb7`c8g&x&w{q0ipFdgjvWN`7<>CAn(7+t80%d;wOER37j!1}+wv0?z(%<(tSk^%j zP-K-Yo0c^zvL-`h%93!%N6Tr5u7~X4*}xIIcpIF^zz79?jX5E=Ti2C!+QHNl(wCDW z!788(K5MRboL1@79&dGFZG<&pu@ZgaRjfU{?bi~6-<#I`T?y}83b$R#GZEvD*_iNq z;rUa@*Ab6M%KO0gv%S5dW0B#$dg;%SiA*wscar8FH1i4$^FW}C5i2-r92?aHfalVT zm1@~+N}g5oz--D_fpQtZhCTuh@HSp)0UP4<>PO_f38K)=pjcmP(O^31YOmP(!Y~SC zxV(W_xP)x2$ZkLNmi@Ll-E(o+eQ7?Ckqk3y%!_}*&6reM$d5hWsH>f}GL47?RpEA#lg)p zIjb4mJj;1sJ9us_k7<`N*YziH)jT%JHmF#1IZA_l8Mnqm&kfkLK2j8?ewJ5G=8fb{B&4(}4+frERZ9{0y z)kK5YTU+OKePW2UcgxujlZ#5@Sxk3tZZuhJu6P*JYKZtvsfgDH{Wtos(VI{+B%*ta zdbR-!_ewIL6ntRppT2ONCzv`-il#{Y1kEP%DTp-rAZvzbkOL* zl)9(78L&>0+L;8-Lz1F}LkZ|R1f_8ut-(0|6G3w5IzJgA2sA z`PJ2^JzJk4HDy&!=5G$Sj@99t#mSPL>AmB;l$4TigQ7?%C_7Hv)iOPTvD9SRBL9u; z$nf9NO~rDl7jT6pfeG-+KYRcqY%;7#ne06EcFv-3MnQSS@10F0KaY+)L_ojWl`)`O znyV?J#l`Jt!bQ5t9_tw$Z*ZGzoC4DUX|zzD`Kx%zqT-4Jt9h-RHAhmznDyn+!Acz^ zFK_#>jjd9Wl|iSM;CQkIIh@Uc4R})_rG9-b8cFU0>e42CTDAEbm2cRJi&J{<_a`-v z94FO=Jh=HoT4|!hw|#7oGB%g-R|a@b62_2Kfwr}`b+vHs}ULTsoXS@4*lF=w{_ke|qs;^8<7sZV_WxD!pLA1i(w zfkz9Jgbaqoh5HtlW4f~v2OHgxdD6#?tPt>a%}(IK@PuY&VQ#GMDL+b~-r9_WgfzRc zA?X~CC>_2(roFE|MLT>+JaqU4)QJGpAt8;E1szA&>k4aJACchy_MJ zYN>1e`0Ql6wkUnjvqnlRNcmDl&>EkT1-R$l*as6xpEFID@O#T6xc|YX{PG!ruE{39 z3;FCiYdE;MvonMW~_ zd@&NjjcaMO*nb%}ISydnEJ*R0#Pbf?%UD(u3xzmPn-c@4n1Iwd(Q%SNrCXP>- zq`Y5SAc4nqB5e1STx(^5w|B%2= zbTWt@Ck@9-PeGxs!KeJlQSYi277=|rIu7>Bm-|W<@mEJOD;q-~S9!nnm6O|>){^>m zy+Iq-vkpUxMk+5xd}Z!`!|4_nEKV&sz4U?@VG8!$6g#`%9G!m}ev3wM)$b&;dD6Fe z&Tney{7o|w;U_6Fn!$;XSEw?zJn+1ed1n-M5R09&ET_NeJb{G>(sOF+kpdDS zOVL*qrQ6j36SNXVceGeJBqSd=n4jVWXrP*jUi?2u0woL5)JbCV7j0w={KF3#;*PdF z6Nf(R%DJWn(asAW&av$Ua}T>NgGJ44vYwC#UAND8hlN~hR@SF<8!6+_r8bf0}J_o$0eF~5;6Hc5PY!w^L6 z?HgeUCvtWN5@8aj%_Ib^qTzXA0gW+kcMt%-0J0M#`E-)=VaQZ0^=+3zzy4!yjX9y? zVf?59LL@N*_y)@iRA}iq;R4iYeHB#rzsRSf`pK~pU+OzD4k(>A^<_O^0>MM+vR+ID z`($8)zV@%L$kfbBG%rrvC0PjPeVGNEu+l=|6M%ES6agtK-sO9 ztfFuHjFu$(x7$}!O_d)xaCvJ(d_BCm;%3c2;d^xmVTkQ;8xxai5bp2OYcPm_nplvT z0X5ncQFLIO*K6ZuJ6}2H0VK%q8whFG+TeP&&xXyW4k?I`iCN`Qnn~9OMEk65W)7WIc z(6i7-YAeX5nWUCVv+XH&qU@hzYb*2hx2ii@m6QIe1eFuY#^tC5Ec`CsMvry%-t+lzY1!_Hz*zvE0Tz% z>=-;^RtU>K&m&Onu<(}Lb)`QWUr7q|a?lFugTD)tO$JoMa_u~j;#QhPMcj%**nc3| z5YZ2^uG+X2ME50eBBaQ*3YD3E#(3C0rotmtu&hW;rNHeRN9X1w$8Zw$?#jTMvhaIZ z(5wAsGWZlyGm+X}%lm%^gK22mq=<-!V^g_&d^{ykCXGqecoQ%EgU+f|~z-xO&IvN}_HHH0jvt*tR;hZQHhO+qP}nwylnx zbnN7v@8aDz-Wm1xjH+FG*P07+&Gl8$t1fc%uf2i7n^OM&b#+huNjP&{r=w^jkm2BH zl}{l|umJ-v4qu=KPhwn!l`K*W^OmJmQ$|@OH>(*& z5vusCpcYza(?A8ff-qFRqIFrfQF@ZAm9?DTfU zyhavU2Y%Rw7N^2Rp2?0ZW!Llh=ff))ndtd~gC;jAfDEx8!>;(bMaW(aem7n`R0Y8T zoa%<=xCs~H?JolX3_Cht22uxn1rNWxNxr#qwCGThmpuwHUR&^PN~mXX#a|l3n??$; zy}gl$Vl3l??aYnz$6pcB2?H;#PKIrGJ(5JF4T`Ot_)46=XP6wEKk!-!3@Muua|sP5 z{zh^XXv*V||UC^Do;k|x~Y*FlNRDSmua}|S}(r81wMy>7nfp^ zJY$pw+aunMW?p@K6DN+=VKR*sdC61WzQ4jmV;^GNMZlJ^SR@k5K_Sb1lyr$Trs)k9 zh)GdeJ5FXe9&Wc%4?3@_gQhuX-XFX)m^V)%m@s=stkGBWTpZwQGpc8e5p!NfjhApCcovnIZ;4!pGI-e-P`)E zI$g#7LgQj#11e&Xwm60!^v4T67RnS1`mD*Fj*77JKow5W z;M0L7>G7ha2&hI3jkYdEB-##KxJzHh{{>|;F_I&Xi-rK@ULbal#+=yL8%k=lYQ+i) zI^M$-&IM0PK_8^QMeY~RTLXG^j&l?Q8? za@#{kQIs&~tBL5@vbwje364eZ9KFQJl8hv}fA?A!4>xQW_LegM%2KPXuCCXd{r3HF zltaUtEmgA?`0!%uPj|i-7g%&#R@MJT4zaDiuL^Cx_ucrqt-DG}N?<+0<{vHoRdtVR zuNPU%o5ZIwJ5DWTLT#C7bnvk>m>&hgC6aLHVQa|JGA5unAVa_m8xRIY9g<}VYHa)`@^8K;{zG!FIi%^ z*Bj(NJqmGKl270!xTVh^?InhO+w+6usNH#G@|ng=N6P%yX{<$hvc-Mh5F9-6{QO!V z(GcC*s%evwii*?hV}2e{NGP}Ac7xidSM=76BY^-ZPOlY@yKYCSg(-1GCiPh2=_Cx=HMin|qXJg>bJx+%8<|tp zz=UO1J!s4-lbPa~++ec!0so5uplDD@Mg7nHOIbCwsorxo_!%&msI&89-jc=pd4ust z7)QBsN-xJA>QHiAB2h^RN@kyPAI`mYGc;c@&Z41dVtSf{iRmHI#_!#N*6U>@x8tSu z9|vyF9#{)ji>T9v=Ra@l${fZTD#)aZ4VZInla7}ENkPa&%~%#V7Hi8={00QmrIgZ@ zC58PdQBEZ1idI_|p+N%47z^~AC}~P+fS}TNlD%vN)PNhAFI##NGB7h57Nwk>T&rb| zZ+f9q6!67R&{5@gl-Lya-nA6f+4&pGgGug|(4(FQLRGDBmaqyHAbLGt?f*;Ql+9-6 zC~4)h`~Ns$!laPOsx$md2aM%acE;}R7m{1$$_zcl_E%1q=7V%0^aN=qEWP%t7N~*o znj7`*3)vEJR<;}yLD;JaxwfExgJ!tmkFC7qTwI0Dq}^-@hFxhPwXnifAVQ|4~; zW9PTJq)d!Ne}fq<@rrr5;S$%;0qN(^C{v*VZW@`^v9imFPX>_JoQ|3fuXa8tsg$j~ z-M`ZLecw{t#3!L@$0mc@_O=I!<9xw#RAQ3RM0>?CiU&_o2q_6f7v&*bx;xKzqI#L7 zbw~eLNJPq=OO4NQMT+MV2`dcUA5Q@W%?nUh)=h8#LL4dG=`!cgjg{*nc2h{(uDwNlv61K$%JBxvDmgor?r5r=eN$>@WRlM zsWir(!er(e_l0LmzyTpZ1VhG3h|%*im;5MFgbK%EQQ%L?on`C$8cyC+YiFM@rUIU8 zdA$LQr6N)Oh18d4!J%<_h2NLWnUYTtkad&~!RoHWTogqevJ~*VG%F1gIr2d#nYHyEL_u zUdYj+78V^7ckpk;66tc=FC2v%2Zei^i8icf`H6KuA>Iv1OfZu+hIicdBr<8yb2cI* z+ga0AZXfigW|he9pp_*50N|Cfu&^+i@AI4VlWjvwxM^c}4ZkNWanPXj*p4J7<`Zj<=jTM`#al;i+7w0aEh+4k#0twUGy8Q#Z<8{7 z&^=M&>=OK8c~mtvF(F(D#oj2&a{}Ydcdx6?Re0D{FCQlfT0q|5PuAPTs290=9`ORj z&W;ZT95^_{cDXXZ!Z`(o`}!@lTJ=u9Ta!@EKHGS$_u9;He3VMkP`dK!x&E%rrr)7o zrfs7!oHzh8Z_!-7plpvwA|OT99~YKxlM_Hd{as5>)e^p&8>drcHWYN}@!f%L=bT3N z?{DZ<2G5dE?2=niS7E;WhIxy_WF!P5)(ohS)$7cU&zGp%d8or!sMo8J00%^LEA|d* z2tCq}C0y3D#v??_HkW-S`uBqy@u2&G?e48`u&1J+*hcJCCVI2d+v0x;?4Z$T1ij{%lCOnr+nTB6`B2aa<)@>y zUp#z9f)pt$Mu3J^3P|7YH*|_mOA}I4n~S-D2lDXXSidIXnC-1*aH8+Sd~QJ!0SBB7^;m@riXZ5$+3$hFPqCXV6Mi%%+;k`q=jlr~D8;6Jnvhjrs~ zlN!P}W+zhum-G7ofEMV!eK3y~UrELJa67RIq$P`r3Tm}ZlV#vc>@1$H?=kMlbS{VL zO+~>SsM~f&4T;wrU{48x=mTr*`45DGvbUdzaJ$9_9rRE zV|M>*CDhbOWDhB8SIV!B4ci8$aa&=h$cA*}L%2!oHwX<1s0NyU`adHTojW7mXbKl7 zWX;HKd)iVu-$N6QbkS}!n<4`Pw;NmmFqy@>E-Pk+KtgDyZQZ0>S7uqvy(?2MnBy zLc#rtWYgF$6`VIOC4n`ll&@L|&#cI>mZtu~E7S6=g)|Al4y7Qot318*EL4RVPR1|} zkrGZc!3q^p^X^uQJWM(G4a%jVtpX6V+vKou=EQ|TS4DvY=Wy+g2n^)L+imw1%|H7es0gI!79vH>U}RriUMw^jvd3>HEUsh6ooQ#9Z|Vwh2H zV{aeN$qRQvUTaFf`14MP+4nBJT{XTf>L_RPwr_{A(kj<+bt2HqZ0M z<#(`rJ|>WmCnnlQB9smAbSo+BbFZ5ZSLJ_lTlPYT2aCnOJd&H6nHL{9S8F?BCXwP8 zijkdISX=g~#!WUBlY>g??Cb@j@8ZY8$`|V0^Z>}ZN;DN=#R?Aw7IHb4CQ2eV+WQ3| zzXiQeoI!UDHf?0-YynTOpfFNpPY)k`v^Nf zyBaus0F1wj8*(7qwgoiGfPx4lN{o0_=l>HqmZDAsXf8Bry4{7E5t=VSdIqMsNOv0C zKY$7cCNkp_Z{cPYjbvbRb#z2`#u%xu@&2g`Vq@|!(>u=fV3U@wy zR9CcjqeB|$oNY|H`QJ(hONQKRF|)-fjS zPbB+|obL!WoQn19yUlvtc@py2j&ppz6ENK+nCx$B{Zl(#635v+GTEOSy@n1W;1?d{ zpz}W;SPxGG5a1G%P~v1AE-SRv>b<`;WdCy_01-xKp@I(Oe-i5(i$TnvosnykA~GrE~9M!Zkj^3~8~~d^`6K4PT1WK1uEgNzT6O{c`Y{ zZwo);ggqrur@B|hd;H*g-jl!ck+TixA>AfGG+SWUXJZ5nZ71&yBx!Y-LvZ_d~(5uns$vFp8pQsZp7`p9e zZpd6R8e53n^XG{;gyp24NwQ#*y+WT+((hI9i;U-OE|S}Cy6X}2H`+c;qMmfQV8|I7 zQBFOKo~yCv&AEw2zns#oDDr5fvn{CwuBX>qpTK~tEWgSPzP~3uFT8@kSsouB3$P|w zk}C6P^c&JUjGpcro}b1tXO*-7MX3F#!I__#C$JN7B`&8aooQM(86AmoaRi@zoD-i1 zKSwQ#3o+&ki?ZV)vyHpjjBdMK9s&E@poYM$-iIV=K0ojHRGZrc-8UVoS=anAhzh^! zpZd5KfUs%UM5B2){IbHwDoi)J#9;y)3hi)7EhU*%$H}M~#RW+_zUdo%z)7L}b1T8!rr-)ye{6RhwZtji~BU-vF zLwO1x3*_whn*DEUIQ&G*LVnu&!JkQ_F4fJKMaXtSOAKfID)n2O-LbvVfndW9i%3ddpoD z>?uY}S}XSNK6H6j>mcsvYLaf?=LBe_4)Bgj%9A99hiXe$kIc5rT4dE zc5E!O~^jKpy7P$pi}MBh>p-L;^2@gxjkz zjJOxbPerqzCMn>AKi$uqedG=bs!w^mF`7WS_R&OxEJ6I=$6j9~O_{KrqHI3anr?~Y zA1l@K_0xAFnO7g)Sv8~oI%@%d3@HOXzQum6Ns5vxGS$W9s2E*IEE5V)&WeaaN0d() z1mq|XxW&d3Rm)gc#Av^|BQ^EfcIXo1_vX*v;~_fM&+{F1B9~>~`j8V_dZZ10-KVc= zZQTF`aSx{#pMP|4Gc}-aq{`dx6jdkK=3bZR*0q-d?5{XnEpbhb-D6DKLTY`f)hkl= zCMB(x_vlUccgSc)5>Unjn>|^?J#;6#OhLJ=MeRI<3~`XYdR$+3+*GHt0~`k~EpzYC zmk((uY`L`YgpB{7yj-9EYQE@5)?j@?_zp~cG#_mUwtIDj8`Bu9;4Yo*_QX67AdY6d zG`+`yr5CHZe(#+|Y=8Txn&p+cez0zS2niqPJJXs@T(>1ifpF!0;C?+LFXy_x^Tc=E zBUP@;Tqn2&21ZSK;gxKf__yah^Vrq@%oBEQV_h9jX*AyGO18jVeeN_ZyID2#J^d8y z{UdMh-dgW)!M4fKW$u6}*L|T_2>~bix&~#@d~f?q{^4GQ+kh$WC2xs~R@9DdV_)PH zFPsl~N4`bpU|_G{2cQ3MdAYv5v4E+Z;VNZq-&r^H)@IK|i6?Wv(lk}a*ZLovge&ZK zP)b(j6B)c6)=B<3FC&Sb+VlY6@3*|p@eftfxYV+no^RZkPxGeKvhUH5YkP8f>z8?S zX#!Ku_MZiB)g!BTS+~0+zlV4gviXgoXsL2(=Z++Q22ejR{S)grlQb@}%^#>*)P)ocpm#-ljisR*}tF8h6hj(2Ys z{^$~N4ih=8t2S)ZhRHZ;==?OUo_015jc9#&_?Fhiv6 z-!wLdtF|uPx+Z7s%DAVAVKSxV5owvh$OcJn$26P3H)u@mp~>&En2`k8=Z5(m&LSH&Qu)||pL1^ANvQju zT#Y+^<-wK{v$##p=MJBX!Pltvcc*CB^3K@Y&y38mo^n0ii{yEk5f=lTK^BjPL^6(8ZfjKs#u8Y#fE$$0`8J1j{_Zbl)ITVs( z%JFPu?GCJx2k7g&pR1h%G-C+;|ME)T;Omh$&4>+ZjQqWTu(xtmYOldFDhyi$uy%Hv zqR-klQvdP_pI9*6)TPD~#C|8~DLHNr*AK{udO;M{X7_4EF0A4c6s?IZx=>$VuKk&2 zmvgMe9-Ea}mwiJ-jNqciRBwUKt3_SA)fJnb5?xsmefJkOe%5JsnOV=;S{R*nxBKDD zacW91;X8@bBQASp7&V|xC=_|D@$nb1!F{vq6BDgVElfLbws8mQbG~8sni56?lEVd? z`^M4|3m4uc>b&*xAFu{lh)j8f?~Xk@DwKLjm3uBzg7NNRN9TZgG2J?k8ZB=vZ5q!M zup=4eS0<7wGpXgsp~)GqjN`-Ra_v7kQn$iZDOn?fkEEznWG}>6a zXt2Sz-VLCk{-psxQb6Ugj*-U}KkIo_uCvZ&4jFSlsmjRE`H zka1KWw@c4GAt&{=$@U%SW7j?8`y6|t-WRL6X8@niJt;NOPKmP)+Dr_JMUGokVN@fB z>E!QZY{T0SdU;8xwio=fHUwwUVRRWKulQ6h`1Dlnut^LGiv;uIhxl(Sc@WM^=lkZY zjh}D)1^Uc@jRY~(QmCAb0S@{~6TWMFyHffeJX?A;V8ZS%fO`w?i1+HWy5Y6{HckI( z9yXo{(KjdgMsWM-3IpeKDqnLwfZHN(98?>(SFG`Yi2b7l#(#Bg3C{lY*leT8 z4!qWjsyv+~#`M9Fm0L5Y!4BK5PbutS_V%B9f%s3Y*+#buTPdz z<}pF!fyhUac(#=rGw()%&&&GhT}iID(8nytNh9Og_*D4PEp7%5+=u(m5D=-Ga)M8F z41OP)(T-c?>lQM5#}nw!y*EAB-qhG8eyJV1>|Hms?`JZ+=f^rKQbwMY@v$d)*QR&% zYZbJrmSFcY(|B%$kXL6|9^Y-jSwY69V`%28Th`lgmCy8d70oZZmir4zY`LHMp0|u@ip!A3jL$P&kO8@@JB+1qHj?x0)j@i`#rrco)dnIxnbTG>-}^0B zXkT?{0l#ovHw+^IM!r`j;>7`!&%Z0Yn`mfCq#@=H5x;+%BZc{N|J#D+j+%N+!%xp| ziOT!xD}$UJA7S7a)0*AD>YJ}3`>0%4|OR%E&P7-1)l`#^(5 znn!&|s>|AMV%0^1+crD-H2S%;UVNIzmk7i;J;Or)u0pdS2@pmHCx^#T4f!P1b2wS4h=Dk7I{tH{MM+vvVgm;9{cwwR|T zvCoNb&r<2QdL`#oDdS&U6z_F>@rSQ;IUcrueqK4xIS}TX3qY~-3f-5sgz-M8beiJ+ zCgSIt_>iI;tKnQv7@f&9oi$tfvqKU*e?aj)Ghe6THR^tM6 zOkvRBv8A5W7`t?^zpZNucCvRC*(I&a%j(l1Iu)U>E`1)M%)dfR>b()4aP>(|wK>iE z+)I_-?#=0s_=uCUCFo(B{-gGjiy{tw3erpJX~NhGf;rb_ zy!LOxFY+#MpHB%JkYh}6ELHj=v@T2qdBT|R_0~_rP3+6Fy(V;KB11({sYG|~SQ3fm z=4|7Klj~ESvJJxwc{I!0Ud_bD=mQZ|`PYM@2oYj*>v)ohx6N@!s+!*H@Fa2V*D*SY zMB&fHsKKd-WO|wm=~!>Vm`&i?2ZYNNL`V^ODsKcfxBBh;ykB;Xkal)#-%1c~LqDEQ z)>WKMksDEYNBbcvXFOZwH9?UvFY?CV_VH?oQ#V7npoST+PhkwH?BO}?QAIXYxaaJ9v*aq2&nA^c0%_R==Ock3+;1&Ygrm zO4I*Uo=ZRm{@EEW9MO7LZmNQt3RoJDqhxf^>Ljj5) zffRw|0#K($+`Tv<#te8rziw+tk|jU<{taBg-0XmO__NQJ1J|vAj0n6aaHQ=AYuqqw zx-dOl58>6y-rrOa(lf{e6D9)ixoC|dqy$5QQqGugfNbuKOPgH7%0u3z<@j_51iM?s zQ*wMM@lf z7$hh}AZ!FU^fXoAi~+YN$QvdVWXKuDs(d!By|WcPuE!g9Prtv2KbT&|z+53#wkImL z`ox?u+$=1rFwO6ck2`37lEDc97O<+8aFRP`QNkrj*FVEvUP8aCX$?etEB!P~FpYbz z3uefZb}>U1x$(MI!WDq%{{Tod3jX8%E|E7*T1{{*_-0<~B|>}lDI68*vyt%P32;YKB8RVyVCFb0(3Hm>#m)nZnxYoB_9N4h3%tp}K&^imgaxTn^%U0qCQ1nj2AlV0+%rEiXZ(l9=Rrk)ndtSLUGKDCo&0MWFa_3Y!0OT zZ8%s{j->Fkw*Oik-pg?;Y{tjW#a|8;%q%!UM* z^NwL`bayTtlWv;ug9+a<1FDZWu*~A#A@fijF=AR>W-rLt2x}FcYLGTVF#z{5~&gaG&8$;-sX)!xTAcWl+aTHOA)l?96m7S1ti1jWsQ&@~zTPtl`0l&Y2GQw|;)+(aI}bH6 z-U3uAlu-T(=9YlpKdQ0KfMTJxkN>x7vQ*;O)MZeWA$icM}WLS7ik^L4TAZq5*XH zfCvM>g2FR6it}#eYMlORHX^Mh#voo7m|Qkl`(DaO%kfBrv<7U_ZkgW(nIZ7`onWlN zdMmD|Yd3Cc3m)T=D(~K5K*`|(9Jtt-Th98N6n*N=J>fovAgz0`R1Xoavlp!Knh6P7 zG5DvyNlp~5B^PF5K0B|>9Lgm^`7L4LCX8q~z)QG$COL09mV2LGuES*tEw02YDjA^f zp8XyE-Oe}y!v^W zP+G2v`uM_2219%w(bh5;&i(7-eB;D*pPG}$yoY-mDBOx|qQx0NvF;MnH2oGZo`^u! z$OO{z$@sKH^a(`mDMm}<3a5Mfk}+ZLG~0xYrjS^BxodkiW_2prad922N64y)_>tw@ z!t;rfXnc_6%+k|$B52_PcL?9XaHXV?$-77zPq62zA?KsmpZtFx|80fe-90a@^*S08 zV7^ON3S!17lv`xvQ_6`XwT(;CHSfoOw0Be=i%xXV?NLXTs2QLlJ3EU^;+Yam^=y_v);-f-r>1q+CR_eD2}2erte5WyvoH$_&z|O zAc?5@-qKea;^u?MP+{x~=oKqTIA8Z@!ikzl9@dq_j;_!27AI@w3pZq!{g=sh4#Z|N zg?86!nDro33BjySuoHo3%tUukvR~?xy2T)4VRcC^#ZX^oI1TyPO$s5iOtKpY;p3R# ziNroBW8XvwuC4XN&1i+{S(w%uLB*~DNi7r=nWCeU@ z#~FvZKQHhl$mosWIaMB1{zGeUvNm&0Nyt|%S~aKBLdcB+PzXv@zzh_5ya;ouXxe(= zjk%_iY!QNq0Qv7ybavocUNVl&pfPI0#w;a435Na3%$V0zTUpN&d5blEZ9%#kjVL(F_tErGAumc)pak&aQ%9gr?%N4 zs)a`HO!%_2LTx%tTdcQ=*H4Zh+Bg;ZlM+n}CIbp?KgLv1A3e;5&~k|4#Bi>Z^kVLZ z0HuWL${5t5}$5!0Xn}`7ZRZebFRa?NEWq zG#OZX=#LzhwT5SL5Mk`iqy2Ki48lp2xNX1n@q}MGi7U(5X$bJR9ybxmF{lqN^5`~m z^aXp3jze^Zg*w>3#}KIWKNRz%7D-@>H5ZI`m^g_9LPBF*Gu%$o!%KYH!*MoLfn#!a zMW=^HTFS$+A?GMhzm3ZYa7v9J@6#*ikalE_|i|apDl3mH&(? za+&GKEL>Js=<`msN!+u}`jz>K;ELp3e})`~VDR}$+SmhQ@lkb)ycksbtd@+&+h($; zpX;5yI&sR1B476IN$f$z-M?*4(6X&bT2!eqxLC3>?wE)|E_=Val_jcKi3o}cXdvBo zoGN4tfUQ3dx~_mm1hfU|Vf{|mn#?LXr~Q=kV~k)De+Mg8!&Qu;(H|1BqLWKz?_y4BbL^X0=8+Y zMQOn@Ea6*2|EX3%ExX)T?Rivux>9PC6=IZWP_$O)vyac#1U+IqjsGFJdlU`FoKb)B z=bE0#AOFOGGYkzd6he0y8?2sUBY=m!dxnqLc;T9egOj<^W0xN57gsf>6csBAwh09; z$|u{&Cz?kqWrG#X%Tm+B&S#=cGJMeRJ8c?eSw;%Ds!q6C$7+OL47Yijzi4UfM>{>L z#j}*j_?(eO6}m0nEUm(P2-S*uqEx_%SYQE~4KeeoC0xfQ(w8nH0KtGS8BO*{02JG@ zrPUaloA4{4J;o~nmwpAD0P{{v9zm3-{XCSf+AHzstu4)#a8KP`wo^&B(ifI_A}sNJ zBR)Ka-IbUfX4vNcn+Jv+se(Ee!mwo9k4Iln@gXDAXiYtAx*BjThnlN7CNXAfy~Za> z-4@Z)<-ogyw|=^IPwkpt?vtHZW$=;vgi-*GCjrf9GIS6*upqjKcSe<1I>6xlMt$o| z$<|hjD0V}fbc%<(kkEEq9c8cp;YHS-Ai? zSI^YPNTGXJ{9qaR?bgaTWSw8>{9OFe)R-MSWYB886S84=QVVF0LmUI5@4JV;zRoe4 z5(Tsh_2N(vy@50J^h*%@&PkkJO&GB4*5pDPYUi`sWR^5DU9^8hya{nTobHI(QuW$+ zyNKhUi6X$J=y+hfvT*1I;b9=cl5xT}EICzi{XX4`N912jk@xvQb`AvJ35;2W?HTv_ zdEEOq26wdT9n^(hlFFb(%iG%XSa*j1Mn^q0)|11gT7MMa^?J6VcX#!iU{ctL6M3>T znF(NE?0!4FH{Kekd~29+JZ7vf2|7Q8>Q7+ZXkQvlY4IKC{ZO}P+0YgkP84mo6<^TY zCW0$ei6kEutIm!wS1MdJoj0oJO!6q(GkNgC6E<>G z;B1T3-qnBk=yKS}h~fAq%kul6;l3j=v0^*$Jw?s^(&HfkIMBO4Brq^7udOcx8EIwj zytBPK`M`pYxI1!f2E3Q#RWd%n!OjpdGqlqb)H%)4Q~N_=iV>1@Qy1c}lc>_Y%ORiU z|H}nMX|lY)l#M#~AHgzlMmiJz!+{`s(;HCKXVv!AEFMTl|@C9O$K!wfMA>5Cc9N0l1M3 ztaw9b=dp>(<${%P8lU#~*uA`i3UWd5!hn^yGzY&Bq+py2UCxQPZ&ex3)|8bd8jPR$ z2_}5?SNst0X4hyiwjKZ0fultO{sPhHBnF81=YwHSF=*aCo>3<{ZmL3+Qet&r)5EP(kKknF(CrGL% z2&(n>zeC8|dFEjLkO^=HPc}p^Ny+3hG1}jPg!_HcHnnvkfpQ65=9!LL@=w$Ae@!w? zV`)oGt=$r{d-6en6M4V~hq?~CGe%HnhFCz(bivHU;aKk+n3um>FfUo||TR`AKXoZ zbO`!G=eFoy+x|;yTyR>=UPPm5XF%m)7h8>#eam(+O_Y;eZv#TSGObXvPL8 zuxb*Z<=wlq379vPoSLezH@|KRZeB zl2ZG0%(=A0c=nFO`RntGA>#sYX>Z~7;Gq0W>3w2Xsrt-4Ik~#BVuGoheU3hs#}3&J zX?kWJPu&0!P*X=8Zj7A47IAj_sPRmU{dv&o8Q+1L7~QlC91HF|&iavBkIDWz^XPr@ z4Zo$$m@nELADGO3pu4@PcN`je(c@ftRvA`(qs{xig>P>AO602TamK8kYFJLyudA|$ zCv{@4*UW0zxLUtvMNW!+wuD>T<&_)g=)LF;-YBmGj}aXaswympXU2js`V~d5@eQSa zuxxfW52mQw7_?a#R2%P2W&<*lD!Ddy+NgKIOrfU2f{b@^8aqu8EM@D92zENA-xRo0 zFu!e$KYyy`x7zXAaW(I$uk^dRa?(tf_`~k4EVr>u@7LsxQb^^oSNLY4=ygw=qVHXB zl3G%z^x*d$@P*9fc-+kajCL4^WYoazp!+ifvFL;AcS+q7&y(i02HH&4+Of^^?Oxo> zsxO6$;i3X^Qeqd6gqm3B7)+PO0 zk9N{Fu$-KnvesOhR-+cy-SfHg?j*bmb5~1TTJG7u1=~xJUc(rXBXps^gSl{PxYLhD zrAUobYP;<%Yn zdmYUe{8qDXw|#EmcK2gXFi=U`o)tnDw;xwr>)RjP90$BbZs&mLn9qXCy}wruH%Ssb z!DqtC!e9zWs6g4YkVkcXbC~~miIy+g0H#jL-WOgM*PEb+yoD|Q9+0cgo_4Y=Lbim5 zF%cmphca!((K9L(IE>FKu(#OszBYmwJ6!Q{vot^?BXGeydDn9acFRm-SvQE z70{12chl$3B~2&=HfxNYoS-AWYO7^++ZdPDyTp?GkyPW1e*0|$v6_C{V0v0`>$!A8VFCmFEluf3RPL-@A^0oz*7(6g!&AI_n8=EniUHfEqVhk*;^F(Y=|0uA^n||f5XedP z?qL36YdkUVS*Rrykq-L?!C_%tCO@atmK=Htm#La+f=r%_FFY;o`B@7P&7iYiP<-iJ z^Ves@l>v7Ewe_1>6VDclp6_;*9?Gq!+sw(JYun3XltZjo+F-;SEF{^x&YzRT4211z z^4P(!gLhAQkWpSwJPxZ)yc2G9bsJXM97#J3(aDATykKL(&@Ka%#7k_f@`{Rqy`Mp5 z6hLP?jb3YVcQ?1CWsJ_QUfm0!@wBjLtn;2Q@kgAQ-nXb=I6R9M6(#SBY*?RF`BY&6 z-PF^=aK+rBbw#qSF<~%<=#?e{7B=?XW4F75OWEkC=(K4RYz>;Aw&8m45eNi43KHHd zZCkTmmx784fWqvlqo$>lQm=H#L3xPt$}DJaNZz#Pl)=IJC#GX~Vp=^A!7Q_E!ty zS4oEkrt)0fw{g?Pg3bVmxS=~{fr7^Gqq&TCQD|(hZw<3W`;PO2C;#bOVSK$%u8Ms-^C3+(z0Yi3qf z2ZB4uj105wZBN4C75(*KWB-|~wuC%AaR&z|&K&rdQ8=YSuI#lYd9{czIsLX9CX8Ak zv;)JLQfSLcDhetp%(K$=->50_8XM9Q8Tf=noiaStXkK^Jc;d6AKl z;^mdbvi7K9Js(a!#*DvFSu?#>g0?H$Cj=dm!2F4N=qEh&Ojp#Llmx)Hc6SeIO`!>E z4Tq>mf}GZ$0zkqTUp&#{d?3F^$AKHZ)kBpGafI+;G{vL^$7VcHuJc@>B5J1b^>2k!}lGn z6#K-qX{lc$hb$7^r54$HXwk?pqarV;3Hw2J9|e($2u zW(b=|8ZB6Pp7A7t4+~1l!1C~gYO}OuO-juj+Vu&8IDV+&^LTSNkVi$so@=V_1ikX< z608?35?FHV&8lI=iQV_$c|C6Ck+a#ja? zQ+1wf1C#n(bBbo}tC+LDRZKLuje(ruA-Z^f5+9>!%$;T`w!U@Yxrn)YJYEZK_m3}y z92_WrHoM`%ddvtt0GdwQ&+N|rl`DVf4bUEtP214izBn1rDF9>H*sq@H`qa;=4(|~l z|2}Wol5o=ux7b{EO6^1=2xrLeHzKM%4@~Ui-Yix;qh2V){2WLMz9hE$7PqI<9?k?9v-EXmh~? ztqU$Jr-&339Hc27kr~zhXY=xL6MEQNNI~WM?fdyE_R`gg8AHbVbAWyS5M@l_k65TD zA&w>pNhrLh@Eh*QyOPr-JDd)jU36hfT=mfIv`Ug zrnk2dAZBLPLJG^dhuWb9OAMK}Si7OL4{Y9B(R$6aj}F zHUeW39IdirL06T}U3y~Dil{7D=HwApt75}<746jD;GI8zF}sJvkQY+V7bqv^9XBz;)*>q|(&SBk#z4MI?LvaqKg4iWgB4_oqZpU=UwB?x{!F4y+cGW^- zV%)aPE#0hZvO_b+S2(SO_JoYHD`xW#iR6m8!1?>|Mzt#Q=8cj2MM?#|5|8ikV}`4OT$|dzjU;s3tf)Y{n&X`}{bpG6-vWL<8eUGp$uP!$m+q1=;-yngh6dA0% z+*q&8(!1Ueval4*)lQ)ahTkN{(*y$+-ll|_VpW$Bn3ndiSe;aXX^Z*nW&n{Joy8l3 zxTLVVrvZz^q>hLx;j_bD3q@nJ7nYZ}g(3Wz=x!>8nq<6G_!62p$9LSc2~^u1W&R{!zETnYI%QwxoetuvKmi@7w0=J@g@PVs~z0NXTXgqkDgagZR; zsn@`+0Nu$Bs_gp}{fcufTy2>9ru_zM5*sGG+muEnjBkGkhx*V~eR9TVyTRD_Ww4>4 z6|HM0V8|BxpGN){$R}6!04HBx{nk)b2x1}qf7_1Wt+%*Gv(d*2pyX5)`wL@Ec(-R= z%Wll`x`A{BfuNaC$7hPYWOo#fAI|AN=I3L0 z;nzv*=tLI&J>BCaXv-VW{MCqPXdnMSyQGMka=r!-=!N9@o4v4kbMsmB3(NdNL3GP8 zCUeNvt^-fQX}=ZvuQ^l{x|_LM`;Mr}2_Y}1ynlyFS0(Y)yA}OjYpG%E&GyU$Rn}#u z@yvKda7$ML>Y0qYisoP`zJrnxUA4=$Aw#HB>Of@-JbfNUpp^`=Y96R_cPmlS~WAh`(a$0<@bazLwC9T1TgE1r|FaU5Hv{4oT z%%-lW)hLzAo$TKnX&IL>LH9%wvW;<)SzJjNeX#MwxsEx);m45v5;nlnyQ`mU%DPPmf?Oq_C?VRe^CC4r_6 z-!~n1kMr``^|=Nt6k;obn{CqV>MKC>6Lo1ids@qms~Z_nJrq_^!{T^hObKJUJz9y0 zhIN5e(H3yh_*P$X%HE%5u|(#E5v#QHM?#uAa>xMRStwOSg_Mpibt{#|(Z&hvD3vs~ zlP9PJQScGPfhg1G{-W?)_1`5zn*3_0|KwqyCQ zJ|)x<7J>8`)3S<+^T|5jn+arj+iD{#);7?cDTJzC#8})N-OKhLJib||k&~IOTAeMe zG`@3S7 zSV74(JVaz+;s3+dR|eJ5EnPzhB)Ch^KnU*cE+IjKySwYb-QC>@Zo%E*;1=B7-5tKk z^W68={qa@7ABvhYGiUGa)xB2lRYV0~pe9=UJ9=Y*_LKUwMJWohA3U(cfpD;c0Lt*U z2@AH}kjy}X5|E0Bktc6y#QJ2zfv-|NrnQ?o;%-(0@75|Ks;KHRXDK4)|L**by?#R0 z`cU+U-fRitx|1R_v}6)#!{&H;5_dp-?Zw~Mmjre|D419fs2YLGSEwA-4U|jgXBJE@ zUoe=1aCxcbF{8(~x8+pj4vrHEgfRLzDd$>UYC_VuVEuAlwhcoh#5rx&KXzs#^+UWu zLTb`nurw~w`JJuOH8}RkFwlj9E57{$xOy?= zFalDT=_FensaXA*1HqmPXT7jvI;->Zy(!GbK?4?pW9CjhJ;L0G{l%uLK$<}cS)Ci3 z6?k2Gv$CVRAoWKP4hQN;AhTSj<`(rzWk?zr;HEr(}#Uv2P7mC=w zv>q+K)O&Ccn)&?UG~glDb!yrAF3I&>ZxdB@Q{v-B?%a_#e3KQ$WBy<9^M-qg!bu_L!hc0qbFHDe@F8_IBj)KPA$>hEW?3{nK@`= zN|nP^wTApeP5}`;mqe34YjCVjx4~CGdtAnxgAE0ny|~B5{qqGyad9DlB6(9UTx_0K zFcl)YEq_>%(pgoTAMI(o3!9n2IH`~Ge?*Sliv0_O8*3uP^1AxxK4eIX>*sw6TUh>C zXzcR)>eitq65K@My<&C7Fz%FKU&r+lOlPh>BIV$OS7%81PB9NU{|McZPr#( zSve|5$uBC3osVyox4H7~UV04d+2|ae3R|eDaI_pA8Z(p}*yqbqUEHu)-^j72Wmi;F z6O7AGnV6{ZXkHtx8#Jn6*6aPKm)qaaB8HcqWn5it$LYj_Sgs`eZJ-F!%=C;CA8BOQ zD3Pu$C7Ka?@$H~cTTV$y`rE)hq(ugb9z!*odU0tf$gp#DyDRd4VI#LmkUG@71sfHW z`T51INa0*g5gZ5pdL+Bf->~+dg5dhFLa8)DmASI7r{{r@gq1b!%5cYoHI>ggYSicp zl^MQTxxKc_9-Phm2}{E*)%DhcEk~zS&DPG2goOo0jj`s`>0LpyD-*N*H$Er0h}_)C zl6eBB<5{iiV}$j!6|06V&hEH;5RD0TH2*?XTJ;||7PXu1hqIvDZ+}BM42U3T#i!3A zr=mTE*hP@ZkB5|-6F6$XJ28dn=?U-S|J-S94uyYUr0!6>3B=(Mo&;CCZ`G``=o~bt zfj6`SW!A`P|2o{Fq}1F^WAFP?UbfCTJA>nQ1R$!`J!MRqkg(E*{qh@}Pc1K^6@GoJ z=JinRA76`tzS`2{T?c@~INw`fcQ;zaHPpE`e`fyQxAUT8Oz}xeoEiue!PIQ_V(CX~ zgOudO<&3w(o|rlcHYhQuIo|dQg(X9Wo(p#YY!um4q$3$Kh8*D2PMkl&t31h=2{wSMNq?J~G1N%M}G_d8sjFd+2APjdB@OkZgJ@KRq zIqudDC9Slg$U2&HHSZYkkv-rxQ1w+E$L;14f%~j)Zvb8#-+&Q1w$R7Be}PZ%sT_t; z?TM%~U&zd2xa-$Bk~^C>bn-~3?5e7ebmqNB&m>KMeed31+od%4+)2EiWIotVk*`_wh5YvF7R$_9q zK-y?~@haqrH*nL;GV_nkOXsUhNe z0U7h4xJ>=X;xa$RXD(Ns)m1Hs^ua-UbNY(h690ezo2OXO9y)wK-`wulzOdjluE9FT zy^6M91sRFJJHko)Pi(gj?LJ? zc?m~BN!tSS76Z0`FPLwwDJb<27ejRC5TSn^VF0f5R8?9R%e330tUqj}ig+e1hckIDq>EOOV}VhC!9 z%rZQ7FKg7OL#Z|z(ScS>S+jA0-|GjFI4Rt)j-bGh9s((Zaoo- zSAqIZjQ=OX1UmmW!l2#-@CH=ri#NH(3=dNSj*O&{ukBc$&TlVCxm@uf@(nS!Gidj( z8PwL#U`uSx{54h zBF@OBVpKPB-4gb*vz6jJ06CFIimz2)D<~<^v6bG$-lSyk_QSzmaUu0XL-)Dg0)3JQ zv12yw6~ulIM~xRvaXAbUlAb2eOh$zz@DCOXkBmqy<#T>eFprJ@#`(Q!7Agp*mbMK=Dc*;o;aqb`v<1EhMgDGQK1xzV&%d^oZ zH_#zP=yyrU_@lk_bo(M)Oj&ic!(&eAWbUxWV#cHsP9+TJ2&Ji`+v{0@SvlE{VK~Dj zZiy}RfI8vo^od&*16p+I$w?D&59nVJ6x%z_*%~S;p8S)ZKb+W@M~L+VqXNxZ__{i< zk5wd)gBkaMvM@DXLWc;y^ z!UE|KUBE#1$7yVGyrh~d)=a`o5iVj6E54sS;&x+koNBdmj=8#-8X4emVyXJ(GU54* zE`%{@efob|fM7x0^Y-sUN$J3ci;Q1iw#ftE{+FJ{ihS&**49|zpeSZpLHRtbo^Kg#rozxv_qFFu#M~rCnxL64;XN(|^dr#ZXKpbJ*UKPET8-6g~ zZ1|O5|8UleeeAk5x6ICLlbr^F6}}cyq6q;{ybNc%DtWz*FT5@gsi5YF^E(mav7M06 z-oU?J_PE_&8NY3(sJnVeFV?r6cs_=C9+zWB3KD&L1|^~Lt06z4C_T@)S-g$XAiT_` z4o}*jG%GLMfZKXqeRRigSMLw0{~&dYEGl*!-`UUM0s1Rc z&*-$>B?N$4d-tZpnvC*-qK*}un4c2}`lz4~;NMiU1ihWna{C>?;lYHst-OPprZl=s zXLFa+NeSMB!XhkJXR7^i;}t!Px14;V?8pxA>ieHMOrYnyBUi?Vgs^@euc}jytc>QQzFLak zC39X3sbN87lix0KkfpD6zku4tV866BJ9?*qR9$lumxs$Oj9=Qax<@;9cRpa%Je@BY zuw37?FJ*EQi@eDvbA!lqbBH0*IZ_0jW*3Gl#Fv#VlkaTVqc<|U%r@wt1q3ekWgu#Dj~#|#wyoH-x& z*jhN>Xi3snfM9GYO$a@1qI%EXX!%`u@wkc}M);#-ivxeJ2l4Obh{oVt>lE|#uYy+f z8s^|K)p)~HPB^q?pf9Fth@y4hPoO*Aow?iyqL07rb2=rxdqhd(yMsHj^wPhnq4Y1; z=tsZn)t&HdVu(?GyXJQX4)m>98MwMC{Wdp3x>_HR)y^4PNQAseG)|y9t3Mo!c%yRy zjtF;gL)Q0fMH>z~aycC45=tuDNVx%_5-mzjh5&BwS(U#Z3=UoJDba3%9*oW zUglCoeiBYb!}1lB?{06hxiHNK^g zQoS>(+DORS>Uhz&Kz9EytgTIEd?BNXitT%WjK}yqZoUoio^~Ci?m1p=%a<%ov^^R@ ze>uA`bc-C7&2g%J_j!2h-6SNqRfO%W!|Qhoi~p0BbLP`v?gWaEip}T85)3v=_kf0< zR9*b3nEX%B=6jrOo770w8!2zk-r=~N{E<&t&z$B-lHGIheeHK^yAN~7XtleBnOP6C zUiTEq;zY@!vYn)vUVT9w9=|rah@dq%K&3>+J$Kk>`K8;v>_q36Vd|U;i@RY2m9{+B zL&G%O7uR zQbzPvpGO|2)@PE+6H39X-;wN|cv53c0$9BX=Seeo^X~O8NfKQ21S+Wl=-*V1Ij(1l zak!gm<$x8Mc^^e&Z(j6C-vySFO#8Z=`Qk~u#_ok)#m!^Mz5(irmVjsX?H+q}X;nvm z66Acj$Xlk}5P+bGfA?}|J4+YYH5We!Jod)J+}%%u)8~7b;W<4a1Wq`A|8OI#)*9@l zf`Y_fzYJ`y?-rZjLUT9wtaXGGNP&VB(!|6>q;zItetsY(&$6e=za2gu(q)DhgocH+ zx!8Quc7m~sJ=}yQ6s(0~*Hg~@3)$c?H^pD=`vB(}!mBOMBhitH>?2!ghZ{1;dOmF9 ziQm{#=yQ1H;9`GLSN+;=_lGZKZxbW_CPWTNX?wqFQm@`k%BYPjwun`qrDWOMtsvLj z-bi*QlCHbm6KFkgMdem%>7H1tK{0u!Zipx1gsl%q|c8cfaq&t>e&Zd~ZOJ zIqr8$CySXohf>q1j!#^fP4Ilq?yOy&9= zty*6-?1ic0Oxi_@B*)fI#*vD~z83?d&dLUGl0?c{GlWOzhk{^(#%u9~_36@Hn={b+ zid%15$X#BK%qAYuF!@75yLR zuRHPH?nuT9FjxOM-}p#)>d6)&2P)S;$z`N{s{MJp^LD;RTa1x0Kk`^dUsc+A(=}sv zdOBUTwWeHizoGmsOl=Sy8SLoA(e8y$Jx3ELjxYnL}bJkN$K4<%5n8Dg(owPe_b$8>8&sIVJhS^xgK=|2A>vgWwA~|)iwDPQ= zDDS!$S@)ossHU?qpl-NheC5p(C0A&|6jExqDU8=~vMCHaf1uAtFh^;|*-s6fv@zstr6zoS-b7)B6Q(t{ zI+fY@QlqDYz^TQ*-3bFjG3IX}X~o7OTy6Z3`t@iFevXvfYlL}j_u`WNt+&3$2tf{( z<|D0ce~SbLtM^4O9pATE^bU&1i!Ecu749gHry9n^5e&c*cWr@-MnL(>w;vRDNL*1w*xrO~3KbiROSNryU8!fCxWl`F~Qb)m|P zsD8?StAxP4T(~o&DqxKJ2p^%_l`imyf4M8?C-d$fAgYg(><7LF2lb=y%gccK&#ra` z(d;^0g8DtL-_xJ*uSM{S4mWYPMj*Ej7Em~{d$}AJmc6O(@zxU%G%Oqs$LCY4a*@CP z9(O-%t+nZA@iC*un-VO-b~XaGDE*?p$99{d-{`pe&B1#iwtrRTj?=A&J~v8bAPc+wRx)vaDf!nTl<-w=kA&7TIMr<#A4H}Z9GsrTxnRDB(5YEixY^dgPYAqW)=nA!u@ydU3#{GIGq~u zLRLRynR;ors&4M89mY76t&Jw1G~=iCKO#-&(pB{Q!95;vOt$1gfZ^4FsX2(f)x_5e zZz}$khMtVdjMQVmE7}tK?JleDdT@(D8b0T_*C-TMct3u|x2YLU{%CD-b`q9RzV=0? z%u-|Uux0LiH+5|y-YedczN1&25&|oHro3MltYQ4FoF4M^^*!|a>h)r;*h_^pyL`uR zWGeCjyl}EG`{2q^$-V_10T~nQ!t$4u{%@w^ErC ziqRJ{SeYIN=I&7Lu&J%F?CqKhr}zfpu>L5c8$60|=9K+Z5on}v&RDmlJq2$4qPJT87j2D!AVXsCt z7${(K+H*oa(#t?p*Np#^*9_CJU#<#+s-862yvlH+7@!v$O9AS-l$=koOC{enMjPT( z&ci;aHeF#<=1=P~D*cp8A1QddBDFSbIJ06MQ4LZ>_swCgJ3zqEU_R8EW-Xo3pE$sQ z%$RkUKcOgrBL`h20l(NDpD#+iM!~G+Dz?v@pESqm)YyCoIB7I-l(I??{G8(S*82 z567w_!M~%cn32&n{kKo6XJT5eEHf7$*dDVvl%5|=3DrA%{f?ZVxDWzowJG{&nDElKvgVtB}P`XF7K?^orCcnzUQ=umW*K6+c!FL8-?Cl>B&2NyyI{6r=VxpC9^$$R>4|hopXsOAx_2Ya7pdAg7xW^v>Vw{ zX{EsR13tc0T@__x3qR7VEO(%YoW-3Krjvow=acSOw%%dx?!3C^SOLg$%y*K5{3n-f zq@Daqz^WO+KKDIWw?@iPyFzdW=u;IUW4l5Eh7Lr*ea~6!qL56yzt~5U#)sCX6jX6@irujCQxyD4 zb3I3ABv`xJ#4?o;mZBrMA?xXJmu6T&@}=qh+Cx~1-td>U+SuG$9tUP?Lmy?r{+h$Z zwN;}6SOV8_@w>a63u4ce;nug~a{t3FjhNA@DbZwHHCMYj;Ld9CgOuOHTZIUF(WRw7 z)7%oUG471@^?s0OKq^7HjVQe z{v7Uij$}EHFPkp%?-ous@a!(l zIAyL;{=lkFUwcv;tIQ15BFsYBBncH-o|fnZi$MWaPCbKh2Uf_x%7sf_o)Kx9EKz~< zR5@js4zwxB%*@P!GdYmFhlb3mYg#ULwiu>e1n7kR7Tt~g6K4`I3`R9u5i2xquUdqf z@dIc4KX&R0-vVD7%$@C@xF+`3m#s42lrh17NZCB9!9h-V{_#te(fotBFV-L zMvh;q+=j6)xM*YbqsM(4XF|=NKp-SFKV_^wUEa59Tw6OzXYOES?zJFQcp3GyZjFXc5N_MT`u- z07?_$$6pGehg}YI2||?#BLnk7u80#xA;$JDY@fhHSNmyiB!_)_-wwyH}8Z z>V1u0o6&@AK6gd(u<^F|g~MnUs7qnwr>UGTb=0s;V-LwY+iywR;T;aV9A!D*7G|~pV~_Pug_E+K#hwLOF5xbmA1 zJ0UVDjfzZPW=EB-_SfA40-hFIQoLE-@!Kp2Ev|4ktvx>y=J$@XWZ5Jk%IuMbFXNd; zXK}>)qhIkKpDkDajBXB=XI1fCe4TwY`_NQwx{%&~THd1i&^^A;z88eCQ&9bM&MD~w zk#PKUl=V^V4L)m= zzfkb;2cGeEAtt5`$PQ$y@hXL#ZgKPe5?PLe(PsMwpUC?J9UL%LSI2TR7mP2rQy0T1 zzV|~u0If8z?%CPGKmf|-VS!{OQ{ga(IIIa4kmdz-TroT6O}WS_DTTZ+(ND0IVn)FfNyJQPrnZpTWfsy5};OmER6I73iG+E zGM+7sz>>l|(1#OSiNAbdpdo>_Hj%rBM^Qy_-}3a}FHAU!qB0Fz?k#Wt)cH-O2mk9&)R|;Ff~=h{~?io z2f46dUS~nn}J!+ z2RxOl0gnpWfPk+6E7)@Vms246mjTn*!4TEG+=d-#vf_dOOd^k0lKgun)1qf?Xl873 z<~V41;q@afs)Y;5rCS+}^Z|~*&eD=P;5@hr;6|Fv!~V%yZfLQ1)h>! zoKd6vO+lkjD@Z^}vxoZv1(nRtzglOw_9_gp@UpC_bf|f8I7ekOO~Q?i!!pC1tBv3$CCrn=lsZA3h%47 zniaS5T$`SoIYwO}Qiy|33;0reAtiu^=t)K^y|yO@tHwSnpUpZ$N$#eJX;7}*{wZ8s zXfA`xpZ9a)3&vyht#5a)Jcu)}MrxELLMkdxgxkOz3E$hZIfiJ{CL7|-)qlu&-W1?Y z-UxP7QyPE#rI4I3^m6l(@%Yhgw1Qf#*Se%#O_z`hk&yA0b`WO%GNBP~eeQZ!@mEc5;&sx*fBXxm1A4WhAx_UUTW^6w^YJIkHpQC@m2kpeohKY+Zx{bRf!j zqQo`^5b$*ENKF+vClTqBjfNbK#y{8v$<6Uvh)2X^@S~KD@HrDRJe*Xt!1GqV0}Z-8BG?^z_B1g?~x5UWn-EV=_WQHDJGqOOCvE zAY93ytu@9+`hZ(L>v)oaa;j6MGB_}x_x376V>B$lUMS<@Y!Z^i9~DwH>3qlo)6A)d zV#ARh=XE`EWcIJj9sh8AFDdu8!@_7d0XMEm6Vk%~tSn}C3(j{$0_4KeS%ev0t1tfI z;f{3|BPlh2KXr2j5gs1~1}5-8(^mC1f0>00DoUQeAO~$yJ-~`Aq zTWG(Es3U!d$MVw_$cn!MCJNY@{N40(@9(wG?jEj&H@ugu)O-CY_Cz#AMfV1|)jOrw z*E+Aw3mu%ZW_2`VRU~zFL)3%hL1Kc$WX+?C0}I9(J~k?bfC=nBEOfH=GP}0)U$sG^ z?j7aDG)fsNgvwFm+vllp~DY8>P&0rpzi;MaT;9voC0v23gN)Tixn4Xev6 z6Hn==k`wPEK`MQaKV*C3NK^I-s}ePi`)6Nyxb1yh8z46tp61ZY-eX zIGzEp@s0KWkTG4lDgoP1r|MnDs#7W4AUdv$FSj)&e` z%M{?Ya#)&mHdKe_kCL^XC?V*JU(CyR`Hvdq0eT6j5b=Q#o~fQgxa@3-F?ttF#Xg;L z!%cr%|DdR^uA3YX7Hr(2qF=*tG56sHufcUjr8E*2~% zhNA8a@9;nC`7^79&?W1fAfZNS*deZ{x6P;YF`P7B3 z1I4ckJ3`Ddg_v{UA&iX|JeHsR$WKb$`74=XZd_HB6CumAoE~xDuR;cNn)A6Md1EFG zkooz|{e~T!)8-FasG-j&>a}W!OG*-#Dz6%TI=bN!0*C8v58U#~Ikgao(^sHoGE(|7 zLmU1{qk+LxEOi_<{OA_}h_6WIK8IPE)h`V)uxh*pW)zv$|D3Y;+S(p{Z))r8>Z)aj z*QPb@I5%2Q(VjV7JH?Tg$NbUHw`-h}+EL_Q1p$Ug`E$F9hX>RHbf08ci;dLL#+QD; zb0w#&745j<+w#$tEje_+@AIk(@8pDpABKiE5Dg`7-~Q5kIlA-iX4kiu-D_(dEA^qF za7d(NIj+#&LCWNJ4tDd01Ebs{jpqynQepN#(h6ik*O&Actct$^QO-uLe3SrYTPZU$ z)7d3*9oR?^D1?NE&t4yN8^1U@A_Cb6aG@-&u+;W=p(T$j)CX0}vD{OfxWzEn1dYp@ z^Lz1IX;&Bdd26tA(xMA}w5VGR@u2=cfQfq>qx#Zf(PQp8h`v93-Gkd0I=TJYTawP<$myKi%tnx^=%fA0B; z?qq1F^&(u3nZM=z{J6%x^&5G$BLTREaSt0QHWpUyzI!uKv^qsQvBBQ96E~@NP?X-| zuV85}elJ%RU~2A%EP!@4G}3Se=qa3DTC%f~ec$cb07IiT9KrZRT~MI4o#wSyYvj0; z6u(@g)vxa`MVkz|D>gdE`MnkIXnJ(cp@9)z;$B$>3t>Bb{Vz@tub1qt!6jF}+MCa@Z|B+X^ zuw6-kwTK^fmH}Cpr@QV2Z~NrEw?-~A^?Ob~=Y(ILQuFKIyJRr^E!oRM*ficcjg8&-gOD9=E!)dQ0WS(?a7lb0k-#(ge`jl5}|$Ql7aLLQtvwjICVd?_3JW$cVBJ zz#I|8X9x)qK?S3SYIV&ezDH;BfPlXHCp;4iv*W_VJKtOl7F}h!$kJwP)&HM*rS~)Y zw*Hu`6AyFNK^#dkA7!`H4$!Xz#BFlV@9i68N|HxL0x;*1I|Vo6 z9B-CfqK7{CVxFKccYRs}SwAfU)-G>!;irgPQ%zzYEUxCZ!P1{+&o1xE>G^~+?nK_X zrCBd!ERpz7N}>rfs#0OVNhjAYg5J5mPg$SNQml$z4DqN5Y@vPTHYl@x$jNyx z?8!W{_Qt$NhUfiB;kw!GslH)JhX3kMkwthKguqWlTK)`_&{4?BEZPlcOM;GYB;%5< zu(D>c*Dqh9uAR_5Dd$g|6zaCDR{^VYwzLfUJ-CChUs9pDOy;w!%MM#N6_#i19e%){ zFD_1>7x%gZiD_tlt@vwVD&P2P{#TfX^$$95`1XxZ2iQUOPpupcw6Ht8W(Z$!uZqpQ zt7x-QEWWPU?UVMddO+nBeRjKZ!KdOp^JlGZvg| zFTFes(v}Hw8(}dgmXYv1=0;mqe%1#L*LNT3xB$)_pU}xaPTPBEyeCsCPZgWGAi(EQ z6!7ribq+*fy`wXdK@`%j`0S>A7r7JJVoh?Mi}X@(XwL$z(PN?wZzwKLA>Yr?*0WPn zu8=toUgf1!P}AuxN(rA!6j(=eb`$VeopXO0ZHQN3_2!EWVP6&QR`B``dX148w$ht{ zV%IUT&m1bK+!I?7Z}qZxN3BE;?zqK}&_4eBg*wQ!61I2s9wKa4B>Qahez{Lec~TcR zD*V7{&bHiUC!5|h(ph1YbX+7|vkfg$z`kA&dT z>0sOg=$Yd1hi7!LL^i*}0Ll$5IO8CFc|rTrA7GDaJelX;Fo9DlSs|X6dFd%~g4=8S zg)R@{6ycY4w`y-kU!V2nVz`YZ_gEP>J+AG+zjn7K5uDaOmQ;3Rr0!bd+&NOM)}!T? zgKV4@ct&U|?+J^o84mEQ$9SvZ;;?ntH{ULP_)}4HdKlxIbtU#YiO5p?+4@~?a3sKL}f{=y=o|ja3?V-EeAcX9K)?i#?5?J^wJ*Qt`&vn{) zQLQ2C6LON@mV_(Fd|I%^i7B2CFB`JX%IgW%B#nAZtniX1a+B0(SHRd=mgMWR{VLDi zA-R>WBY{#&pW+#Uigsa3Yf|kKTxs-$JjU-hDEN;)b5Nyv4{5qr)g|j9^GbaGg;eLj ze(+>pjBC+(YbYL-e-qVo==1nIYVq)7WQLfR(r#1#1{MkfEQ4xX^ywzoUNNuGrQ**# zfY(w?vKfhUou8)q)FtM?&OZnu7ugznOCMKi>)$9MiowBzEi5UvhNzj0I2jOQ;@wL2 z@PmF*&TqF@Px|qA=w)X$07~}SgWzaNWqo^0P8VGJY64R4Pg>3)-XPexDJmz6l|q1l za&5~*{SwE8J$EG96rMlXOckkB*|BYV(0o2z(yqgA)Dn_v!%VoR`smS&7IvdmeJa&u zZw2qC^qgUGBtx<9mALPyE=%?ruOIi6H>%nfoiO7puBDgnxTi|86s(F-owa|>R6_Vn zX^6^IYYakbVTXDeNx_KZ`{rv5$q6dRW`m!LW=K>UH}=Qm{Ef^gC`Fg({(FrP$9jjI zv~1O9Z#N$wPhYU(Of#_T0b_6TOm7NDuEZV|=R&jk^m~`bsXPfJ!V6_u3&d2D;(0ky z41Z(fxxADmzwmad;(KxW&*q*a_}YW(H(5W|&a~2>l+iq_NDGPHAdzISCW$IYxxR$N z#i`Y2FlS}*&-E)ant-7CkYC9$QRyU?w2cQ+Z>GJhA*B)b$20RcTKGwrR|PsVJD4o` zt*C-lL@hfL+WK*!PnQ$Aat?7<)1pUSF61nzOLx24QV@_CV_cR*tipe1&rz_j(6u-) zSus5HN7-2QOHUk3y75vVA{Le4MfDGdpcl^nw*ApkSxNyn$@r4OM?0#0(_AtHUUby{ zso`*&yD`rj^9}%W^c3@s9%JHGw1Ry01;JjPwOEyVu7qj;wx7e!7*<_7Mbc`mt&8g4 z8m8bW!Bez+6FTV&J9KJjkqMhGp(>^Vsmp?!38c=gZ_+8fI@0Z7Y{3Gnm3Zt*dH*33L#GmxkFPb;wU9XKM2i8odwr3l+d%v zE84r-82vua4=Q4B)^-z}$#QU+%@hMwu(Rmwx6S8$&6fMx@~tZe&DdpBwemzx(6Z}^ z)^B)|I)Y4v2^Yc-g4(Zz)lNJdshxe& zbg+_VlDefEmTtmi{3u>n4}zp~bXiVzV9NROBQ+AcM=ejt{Nqtbg+ulPI36h~BD*Y( zVPC7{i-R{(f3O6{?P;gN2^hBVi(FqA?p|B06ehAJtIW@?I;4*R)PjTwl&qK{R%_rczfaRMGh6Mwta5TJ;RNiVrt>l5L3_rN9B*kY^_iS-`9p#x?Rf;QeyO*CFJ3iQLQQ#s92O(Ty9}f);i7KvO-;`*T z-fEO{;Fc#g-Y3G^iGgirUyi6-OBTnNE2Z5?BYR^w&=YJBZT(;SCOG^aDBT=1xj zWJnpbx$U!p;Mtz{QjhGhUmT~6cjdJ*WM1)Pem5-0*9r&affDR0hD16jBkU71`V|b)ohxA zB-QmdE155i?9!^+%&Z%IMZ~UsEH4|4xwfu==(_|zcZvL2xrlbAF#op)#?hn2(k~YK zN9L1g!7T?;)2TLi^e>TAC%x1DQp9ID0%hDzOy}d&lQ3X;$Eh}qaO*-RZ?NRad6!AO zkxog7Sehf+`1;u7H5v3WkN=;`CtncTWD$t^F!M-oGRoF^U97o4LlaDU>(kbg#Cw zaCEr?o}|Ey4J3ag0_GntJ-iy%?<*DED#Y+b^P-uULKbQYo*E4CNA-FN%AFPK<_MGi zu0rw6>s^jl4aGcD)+|f<@q~@pIRP*8ql9cT{F`ZxbcB$ocl)uNgE}O&UZjXESCm|} zK|QCoBbgH%2#YHMot-Ty9rwN)W8P}jDRvD3+u&7=x`Jdo5b0nJnN8-dMgpPz8~7LV z#CQKDpWLaS1-D3ck<7jWg2kP_SyIPe?lq zdB%5s^eG>1Rlf5e`QASJ32Il6xUUP>vIpi9>(a0u8mmZ)AKiTYz+y$)O(hCLf9%Flb#u9etTh+kXu+*_Q} z?oCI*!oR{ZVBe{w@TSr4+T;lTwO4scHV6wINq(v4sAUTI@cXB&vJ1opB_0a)e8#Vq zxn7Mof)YJomv#sKb=0jc(Ogl%>0wfm&-7C3}ULL z9PYlC%?32}9`0?IP!`29wsYC_QS>G7Ve?eDv~)?OqYo`~NnwG3Fp&hjtHq5g8KmQ> zoF}pUJEys*l$pU27&7x+(I?tEpQ?ioe{U!Roql%r!b)IA7Qcx{f1SbJN4G8{rHbSl zRrcRa&mkcpw0tEL8s~Js3b?`2fJE{gvM_;3%Vr9TzT{f_ee9%{c!3$Rlw-4@Ih`sw z-A}%bMAKb9_lUL_2*W}sOV6V*rBj0Tna=|rr(X_jfkVVa2hNeZ0VRPq@fj6WXZW+L zI-Mi?SeMwIOLw6Br}}{NaCfRgvg~0M(ai#`?^sgAofUw>p$Ezeq+z*Ea}~C3`54W(4P6KAoWX!Ar*)Ffy9$?<;w&R%&B7 z0_bD8F`?5N;-}bGMyjtN!NFG`q0q4LD$6IU%PV--zUQqlifEQ&cX~Ueu|cHii&I(* z48)O4R=@a|o)`76f8yB#d{lhw|bmz;Pl$A3@@xSy4a8MQ~iD3oI~GV zD)hwG7@KA?rS+E;$ih9N+TQN@@fp;D37Av5UOLD;R?J}fdAeo2_L?InfK%nUA2`il z_o!<(@^G+B`}D%e=274hnPyv?!X4x(TpOS)M-&QVzW3^Un&{GR@&*sBAJh!EoYB*) zK`%y|poxtcJx;qbtv-HA`9fs0(a@izt*>TmI3Ev-?|P<~c=rBkfD<9CT1b)wl8af` zcva@6S;qM-L1tpgn{5}%s;%ZC-CWW?sH=&Fw|I?cF4>!%zk$ma>=l$owYqn;M&Gr| zhH5|60~9?Gg4he-3(beUyuO2v^>#emLBDi`)itROfJ1?fCD)b?= zr|$IF)E710nQ1E_9Z7B|FY^%*L|!z=JKodkIm!Imo$ZQWZGt#?R3v?ofode6n59gY zW)`+V%P)pSP<`UInnx3S>8zRtt9^uE5R>O*^BEX_4Vyght0IrcCmRbNgu>Zc3NMCP zmKqI9xmtk_RW_BJCMECAth61+m+;sc z=RuVp=$H~*?$FZTPRgEUU6)oB5DiAc$0mCD`e}8Ian!ZbO1DIE#)!nuN&AP?|f`odE(az2+ttu`q zj*^lx2N3?tQA#9_(i#^Mak+<=tAjSnKpQ6s#rZ$3VjeE+WOz2zo5o+OHbnFYpuFa(rt4%YE4 zF7Wf_bY?gv@p_l_h8KqA(f`IlP98MEjEO_@!j0K~Bxjq1Z@jw94KXk_wyPtJ3S+NO z`qVHN=l+>`Z7pm$hIBzmQ^MeFp6(#0g~@8D<5hb$gK%6k%&NKZPVt(m;!ux=)zF!j z%DJs^1t=9iM2Q<3Kdug|eR;6+l4)U*Hap}f_$4KfH<>$Qc7AwW1hm=mbTcSh%@e(v ze?|=R1R!b-g-|ASNX9h2)1r^Z@Y!)cJL!RaaVGEB4i$c@d&FQasO26^Jy~kVW1~LY zzHlDk@$}X2uO1xco-#YLHSfSCChqe0e=o197*Vr2lXI(i(xeOU;>-0=c+wSJQNhCpf{K zAPErM9TEr@ED7%J?(XjH4gnHe2X_x1g1a-gJ2Ut2Jn#E`_ujQwi$61Sy8Cojb=BUr z>)W?COfV}Sn9`#}ICHbFzu$g+ zQh(W*+sXe}$-muiPG@w|M68VY-QFml!+4fuO~o%)zIh`nNAw-?U)+gfBf6U(<*0CW zMF#eLyl=LsgRr!Fw$9J`Qj3+zpC`~yhzW_ z&$TZeOh-!jG*wpK)l`P(8QI+5L$^|7PU(a8Uuz>2VTvf2yEckBku;Xp`a<7SHX_J; z!%YYQ>YRjq1WV==#6dwCi&Al@t3oLe+A<+8G}Ua*t>^SdD|oaxxU zK3JITo$P!7Yg+ZnO8JT!i9dc#pjx+Z)OOy8H#b=A2JjRJz#P&SF=U=nVX=FlC5_75 z(kU}SLANvG!5K47x>S45r;jQ=Wsb?g`|{+CB+BiOj;mj#5uco0T{%9pyvdtk?Ir#l zzQ~Vqzz=oh7oHqC!@@yyCU&71FH%n`IJcEnN@_3;FTmvVACh zP~mE`c?bnq`B?8$VQ5M~fPwIxT{I2w^6kkEfiZ3L4ifUQBL{w(*d9(?(h_N?>*?v4 zbgvb-CiTp|EO7rVdWF!C{4Oa;%Qw(>B8SP?z?nWbwBN~9@iWWW{Q}A+d~_2FM~z~; z4yjFY&*FsdHZG2WRK&qxpMub#>a7rt1|J-l5Nb?A8{C0E&>2 z$<4T-5-$2VG8>}???k5@_6u{B2nrN6aD z77Q4tp+r+2afGmTfD$q$)$mvk`RhTxUczr%T49vU2517(CG~usZaW4 zXL>8;eo5%&Ir12c5lRyam;_&|l1?0+>w(e$$rgNnborAYH?+%RPCI+f)799@jKq7v(mU1!c)}^J1~i8PpXeF5Po*EcIDYSuml|` z1}=@AZ}DA$nuj7JD48pbF&ze`7`d&MKOnypIKJ_|(KK*wAQPWP6dESSOjhPzb=%S| zme1~~)(Pd+$pi`yo*iY1A=1EUquv+WDNu8c~*GVAs&AB1^R>wE= z?=MBS5AJmSAq<_;?>$7S=77vt;e6$|$F(!Mjl6NL(T+3Zai@^gY5#3OK{*Vmh)+Oi z>1xAKV!<~lqVzt1(D1zEg?xeEmb03C1PDDfd3ke7pID%y+i=^i4(yICF(2OR#FmUC zxP(*Ne?BB8UiNBtC(#`_{RKomymJEL-D{C(c290LEFb2J^32{^Umz6!h`zO5skUbT zgau^&S@I~FVbL&Qxw4u2jurUgps>gUO2nT6s?i5#8l@!+5)N0RygIRq412K=Ir+X$ zxIm5sZr#a;^8OxiWw{{c;@wc4>DXK2Z9C}fHFUA3W|UDn4hS1C6KK?5Pnnw6Yv5MI ztbdB{Tj3)YOZzqE9m!+-^}#tsB)@}7$2K;q(5&8#_`q`s_xlt)T|`gVwmxKVFg7>O z)rpFMjyPBxb?*%U!SUJy;4FE)57080>CWf@8Kv8)=B(@Ell6WX%||yu{|?_7nW~C| zexB2^Lcvm7ovOJ*>u+?uhn^c4gXXP5K!7Zj)jFV{Dj!xu3i6G zMb#Kfhb6BcmnUrh!OLMpi_9a#d@_2JfH%+|_mD_fkN;*EfdkQglR)ObI{AaWP=G>z!i z_Cd#0wM}>Krs?0zk>n<)McWkk+)ZQ5bZNxV8OiWtYmN4}$Ks;oG%w?~!WsWRkl@eG zty2g5tM(OB4+%!%-!w3Mk4l^MU#3GEUPzaQT@FELcJL*?F1ks1G{Y@0Er!vPk1Duv zT1ajzDf3L=k|+qJiFdmpZQemA^Q;s#Ov!`UTPif;OwH}O1?IyaHLWxWJnG-vgG1)n zH|6i&6IhASVMs?tVa35tN0k-|l3Qw`Ov}QaAtIkZ3rFQ^)xm&KI{WB73 zdm8zT06|Pboat2C2j<41G}wq!LgGnOhtjb zdlx)YWAfHcVc8nuf$H#IQDj(A7Yuj#L6*uu{cvhPv1c!$GGz zh3Mh{sE~;?FfBrpQZtJUs)S_0DWD~qmayyFy#beE7-Hd8L9?8d^JUwfB@(tA=69?? z?fg+d!a~$xS=yq(kdVwFM2>>&WhQIffx}{DfSNYjhqj;l=$F~-Ig6Z430e6=B7}0!?JG0=dB!$oA*$x{tCmjx zK%%n%1LpMxtu$Z} zwJ@x)Hu5#B(v5kQ@Z^t;x6I+~d18VhaFS->bJ$TntPh-FIThJtDI4P|wR9c!EWpGM zz|^CUfu@^;-iB3IzoyzMYqiO+H^n-*iq8@^ z)D8!vNb<1t5~BkjsF*N%uDP<9UmRM0@XlWem+5AlIdsI}c>0crNx%TckdK_S+}~)+ zexhWi3<1o-GZ^{u$|7U@=Xgte1!+c$|`+x`iOrK#+fw*i8Sk>ZaI7g!Ismn+tME{vJK zBT9jlI>e0@)yjHt=#=~tv)q8cupUD-GslgZjl+l1KbVLmWV*(2{r&!}(%^(3LK?pK zu4XiYAHiB2FX)z93zM)n3?uNjqswwOSnINMeq8yJkq8yKA9oNejv~>DLS)5}msf@H zTb|XFF9qU(DEm$ywTX&i^fn8|%wP@w+Sz0!g^4%lJH9fAX7wBRXw>P0U~b&h(By>@ z(OJNC7oy?msmD_LkNfyb^=X_p)S^3+j9 z$<=Q7&+_DBWT@ZSh_ts)o+1Kz3^0P^-#U+H?|-$=5*n>cbou)!+dMhqc>5E^XIZ_T z<<@kY$xXq`;g|z{D?CYFgY2&8%aZ|Iv%HHt7YwBZBI0Ew_uEo$#wAE~a?E7@ z@qfFl^p4moZBOr!>=zlmimwS0vKF@=rgY4$BKckex2a~w9XDos9#Gx8&S+Sq9#N+G zV=mwia=gl{Au9G_FRAV8HOL}VB)6k`7v*ALtp-ttHEYT&Q*93X?f)X}8Q&t^{A1#A zu(4$Uv!1Lx!+R(IQn^WH?9ZnoK55#*Pe=@bsXo}c-p=Ot7N3r9buq^hia;`%$oTYo zXakaKJ>QfZRX8Y+^!9f4ox*>u{GAymaX|rIlkydewe~qVxN58tde&K0-UX|lb6$4Facn0O)DKbjR#q9rH&|zYn=pFbS|c( zQ#^DLo4N!^0+r*<;XPSUr(T(A0!q zFsZv7SW}Tgv;;ny{r*26?bDll4AlTc(Qm_yb2Vh*jL~0yNmVrA<)5flMggnCwjE;% zR@YqQkwf$Txc;b|<=crIu9eQ?(R3=v{glT4GhBOVlsiEECx)Lr#$?(XuT?`Ao^Q(x z|8UM$0vu5^^`hLTF2p09@<0rX^YflogJp^6moM1Q58`kgI@`pL{g(1NBpvT^fH$~$ zKs-dQe4OCS3#`MHw$ROoO_wql!f2$U8ylM_V4hQfTzG#hT3lSX#QlLdABMAOg>;w~ zWA%h+e0i662vOWk$RSi45PC_T{{Liukp4fB1T)7ccD+iCnJxUZ{{9l9yI2-S zTt7<>TBTPJFQfjuU8J&B$tBBXut~9_y7lrZ$HPa!N1wA|v{UiK_4|fs3=AG%?Y4*P zf6I)VbAs~g=59xs7!`Pc_wX+| zFhdjtdpzL6cW7=&5j}9*LtKH8bi?vgy0ik`PykeV_MJ5Vq3qWf7zckHfyD^x=vi4W zE-nTs&Mm~~0K@1-FHC%njU^kAn6jAS_IcHmb?9mOqNIkwbvb`~a%pR+0pFaLM;NUF zuhn|<&%pym0lv3?YA%(Swh&&@$yqW4B;B|V7=?rzy~%+62M>|&f0sdr zeDLsm)1l?(uZpb$St%7lZY!+wb2h&!R+ZuAT1$;;nG`o{z{FQv@6#*{%E(o~TabM` zZBL~9cg`=E$PZq!d^!L%8HV2NPaQE6v9E5-*&2t_{=12++u#0Es?pa_Oc$5N8}})2 z6Lx4^So+hab1n_9g5!5N|BVR3%D()&L+eGwzWly_ml8ud)-%!|C7-OqpvJ>=PJ;&| z%0;!T6aEon-GoDdN9tpzv40Cwaxos4n(B);r!PE#ZLxc^z`?AuN#FVUlNaD0i>*)Eix=X+BX>%2(FV43-a*4K~lJ zxcIm-{>;D`UjY)rFGoF(4rOcL#~lR)uN$r{fh#owad1++G?3_B=>&2qFJAl?W7S9C zVhTQdG54ur|9q_=R`Dw#yAxJ#2s+YR+C5xa`K*FNk9ZdCys|PJ2jFv?4*$;V%9-Gr z%G3!=cR0IC@$Zv`lK)x7&$+t{vv?E~@LlmyTUmUHf8PnU%3a11Fl9w9kZ!I)UUG7D zbO~_Uz>0hP(21jAKzM~{z)Jj;Gs2b#MZEc<`rpg<%axZUcI(>Bo=^Ml-&y-+?M{#O z6njJOc0hjoAT!dde`Tm+W2RzELZJ97Hzp^`4o%!#pOPu};G6DY$GFMnEBY$I=&F)m`>^Awd_{EsET zE{PqOvVb{r6Rg;+JKkyL4`j<*nP)Z?vOX@Hy3w#0x)_f!$(VFAqJZ&kjJl6buna;!7S!BlkIP|L3sQ_vZqt=KqdL$fc=4rR?3? zF29kTf|kYEm9aN3uY*)s`Bx<0`}&OO9_XFX+@`<8 z-Ue*UzU#ul_rv_zwiTaCrf0^O{`o$4$#ihH-p7(dw}j;?-5w_J*JKR}@-?J)sMDcV!B@}w< z7!MctcJA(-I1oNYn+}guT}}-`*9%>JM{qa_zza-(pll^9{HB!j`Sk-nF0SL*I$PW7 zI88muOHza~WY8CoxO@ruyv{a=|G1tcBLFpBpQ~mZJrkA`RYwoj7&p>ce#27l2Ib3Y zHzk$i>PA=(Q``xzEvztE85vDR-7kyiaV>QWHGinmTY{imf*v7jS=FMk6FZl_IUbSm-nkyng~hrfdO33teO* zpc|{tp#nm>^Vk+9+hO{$(j=#AEG|bjxMp=y z34XEOtbd&z?T9bbRCvOlB&Dq@QjvkTm`t*?C4-_9Z@f;V?&4O&tM`s}H-WUwbQqOo z5}M6)1?`5@Vi6hU(EW=g@W__qd13S2kc3XKKDZHbYFW;!6rBRY@t`If@s-s{X-zV6A(>LMmSUpPT)9e(!{zV8US_@i~Jp3pfI18OMN`ZZbF zMwdK)Yx)S5(=Rt(>F*YO)`PKf7f?U%^fgtK=6re7ZK9k%Uq;i=>nOZOcW5v$KwrKf z3}c9b&v*iTN6uZX5QF;(w34b0<+a=fq{R1k!a^@{jcMI+p{v_@w6+?BXEMQ{_n56n z`1bj$0Yh|>@yIH~%LiHwa){6Ty<_8@DWj(ye<1n}>4ODcRmT%-t4zkqJRACU{G5*Q zNxtsoei_mH>-X1Sk#nt43w-F{`zQ|b&2Vw^zI0eqzVt(ti#6y|$ORI04F|cwLSmg1 z8l1(Y+T+2{7SJnvj!|-e>CVH0E z7KYQ0d!t=k@1x^be8%8e_Trb1I5sMV46ZMrJI74@ywPvS?%L4wK3~9;$DpbBrhPiH zo+e`JinyI8_Ib>O##)OD?F^)1wPzO4{rEu>pT-O0H#$n{ps?-CTB7{buPKPoCRYf) zj;{}jL!<-T9X37w9ENB%{wC~BBiagc#!K`B!HO!nJ$0tAS8S8v`+8E|GA>2jcjwm9 zFu^@U8xBwG_8jQ4+pgZaT^Q1aiv_Z2m5xhmvXy++BNdK(=Nejf+)UvxArxD?9nH9D zQu3yDPaD$07u&<4YdFl3iERW!8~pf(uUJlhkx&=gg!vmpv#H*@VEVw><;~k3K*IwlOj%5?yafoTdes z7IThIOJ=o ztgKy_v-?SxRfF7zzLvj#O%x}ZRS0J)ic<4QFGgDb^H(-cc}{Y%73o#z2r&kp@!3sP z?r#5BavrL_-GSZj)km&6n+Ng{)DU~9&{mi=oACV5x+aCm`J@%(?pd;VFNfAmUjd50#R;G73+`^RKJU?}78XKj}1~pIMJXJGX~_pyfbM0!JTDMo396uqFi40@O*m zM=78Ik`JeQ!TqSsd-qNSCpy0qh@MGwr^lLEAfptjCSTpm5EYo+gQJV&C@KdWT}%8M zpv`IOu^j_b-KcH{8%Z&kgP3yq-G+7Rb-I^bLa`TJie>b2=>t~?)B``786RgDvlx8) z+m;9}w21~aFJC^Ql@s{9chLX}S}9W@ZD?V;+awBh6G94g1?<6(_yg7E3%6;vE2+GM zJduyZzpHI2TO#O(`hJ8P!sr?wc|XQ-?hdAoC*VbkE+!$rpdPtzm#kb=e9nl#twwG@ zgMqL^$y3<`_`-DPT5e8mV%w==PAg@sD)jlbTzIEXkT=@Aroa5zls_1wnrv~X)LY`a z=?_-3Nw)!nof>OT4DK1}jd3|E0p&ncv#J;usZ2J>H~IX(c+ibz+a0CJaHFgzzT~Kb z62y+nwRRX62a9E?4U3EQW{AT{eL1MJ9_L*~`_qW%t4*28VrKoQV~;6pitIiT|3XHB z(R%l&)=C*zi{V^JL?}of{RU6d9h&H!C79fOuqM>KX z&SxJ|{piFq)RfMo%-a4%&wFcYq$rR6bBA=fI#_lKMxqbweax^p0ixdFogqeXyr?Fs zP?H;*CZ!FQN=>ezu^)A=wXTugsYq7S zyuNHEZg)-WTy1M#c%6~RY1Qz6haz#{W@tSBxYTY5#d-gRfxOQ(vw|0tUT4Jz1aB+8 z)x1=fp{%>}HLWIGMCjZw$E!V>e2F_|35=%SmxT!M8_?f@pTMbA)gN9N(8?JGS<^Cb zZ?7v^4lP$_SOfV3Qi2#*yOxXjhkI^a+dIT4dSIe z88tgeg&3>?p@^P7WF9Ph6Ii{|)!w9}B<C?6Q0vq70b3D98-cvKfIv-lR^g0+`T6M}S+W>M z=m_agc6J`dta<5)TIbcev7E1?HBv&Y_5p=(nxU!#e+*xv!lt6C8jgd@KkOPA78uE= zkKib*j$}#0Kewj`Q#9bnTfSu<^{e}dq^fAJSKj@Lm^Cxn4=3v;a?w>^A@A4-X)wz? zii{h}F(JMu8cd2xHj(zje_ldd(ZdWe*m~7&;h||0qxebBpFaigoS>&e659?2Q>S3K+L<1CNgIU1+mRSQUT z#~I+@?iSw-5j?6-q;XBs=p9BsJsS;iWz|qjgGQRVZw}udX|y1->T&asg}yPgd(C^S zq@w&yMSZk5HD{gHlDTP0Uyiy6znl^&%1_af#>EIlBkn_UA8a%P)* zu%dRJ6zQ0yyuh2IWnKcUK_GrF^p!T^3xh53`$0gG(@Xy zc}pt-L^I>(@9akX_~@f+>^na9ugZ_6E&2R@JcT5gKcC@!=Xc8Z?m2e9Y5`BE*-W;< zIH9MB^kY>Z)o9R%n~eVUmX*Vt*)bsymU{MA?D;ZB1-j?)YXaKaABiNw>7IWI*fI*5 zt-*C|)lNw8)CJ)UqBaub#`4vxtdY%zJ!}VPgc&(L`b*k)Mm7@zZoQ1f=v)seCM}Nm z;h^WE@v39Dx-2}S6NdLj2aFOw-ANhfOU}&5L+m-yO@43bxyX$?E7M*RvL0NK`J!91 z{*uw%Dcm!~wnQ*h$9NS(VBpGmvwaYyM$1JWO7CypSfnSIEoS4xy>v5AWWUGwy&~G4 z`~h)7h?~5ODRbp9o5t4?NjhI*lxKOwvFa({QBia}w+__wVQ4vLL%=mKn((pc{O)q1 z_NdZ7h?C;|Nxh=lejh1L^bCSpXuL~C z(aY7~We)GR5?#Dz?-hhPlla7za;FX%1;(T`5q_DAv9*pYfE*B8r8bZp*;?5dnA@EH ze(`e=#%V^>zI1;hss$TNVk2cT+2xjJKU^XvxwH?f@|BbLPqFNJ&FPBk)Z(DrOxX)f z@wL6s;j_>%42X2LqdB~pWp!kW!{s8=nds?yVU7)Qz_zMUhg*-t+a6A^R@SAGF#ec3 zW_%y3YdYdOLNAywUXzH|2d`vYzdI)I-VP6J!s(w{E zXS01LH0nLk`8%Qdb-BLX`WSRGQV~xr7T3^gSADh*V~f z$31(x3vjI_(cJ>NF$rGxXfh&2y{CP(`UGEcq~#%H%aAoFvYVb-E_k~Q*x-aWzi-ax zbSq6xktc2FHiL~v7Vgr>G&h8~D~8|NPkqnfdmu^Sv3#t8tm2OK4_~uf%5G1%9qjm? z{oo@ldmq~9TNe>%%-Xb7QE+zm=4cj7IL$#hLy<)#%xq3DRQ_1^E+@N)&uG!7 zYs=eX0U|n*SmTlhoKq6xx zmggj5+g|OT7(*-%*|}we4o|qBJU6d%>(ON1=8hDODvuXK@8M^G5HA-5AuO969#+uV zxM!KbzSu7!-rn4Pm~$(JPN?0?rV0|EfTCjW#snU{9e+F@Gw*eUR3x=uq0U-)rHOgE z)*eopXQ@zEa*()r?tCQIebQWgQb(|lm-r288?UwA;XRzYctYJ^)O?__^m(0zHV2}Zg*2eTfr-j)WX z-o#G%pdOmpwJE+M7E)l2AO?hqK9V zXC6zW8rk-ok?16iUv?}2x9y!XzEf`N`iezoadB`9`Rci><=f27DX-^wLCebV$ut${ zfZ_J=+Cy`B?B+qIsn>qdCidT&3}CX3-hJjDdUn1kD=EL@exE^7A`8=HQ?+wg)!DuE zEj%`ICuYgE!_4fu0Zjh+kTMo`wJK)7$&L;Ub=C3pn!&@vLyZXg&Q z{Vx|_b^HoI3P0R3J-9@c&UEBvvy2we{Wiu^0#_hv{}+kTTKg|>!w}s10#*oj9R?34 z4zgF<%WaQt(f%xRJwFpeeT^mv70&}Wc@uNi#REFjVnF{w(J<71;RCH#f~Bcx^AK5S z0g+MS^?`bO2m*i**7@|}qo73CwJw`EFWOkXv_+ay{7(koL5GExggA}QJ-~P0*;tv& z9~SnrgajbpD!tZ_8&#-2cHkw2`2s*r5lbhuhUrup9{i<8BS{JSe;SS&z}uN zjoV-Pf5=%YV_-a>zXs6Mla&VGWg&UH@`1m^(0d}x7BUM#$h_n z2-j=Ks!{NjQ+2O?v6KP%l!8IJ7N)C=8;e*dY3Jo2{)lU3Pq&93u$!rrHa%nw4!UvbeR3IH-35f3+F z8P{^>vZ8fGU|SoU6^MAYVvSLT{{<|`{=3)l4q&X&yf29vd?EAcxF>(BA|O`6la1dG zq;AOz7Ilg$n8Cq%BN;^LcYnJ)yky(Hwjy^hWip;Sl5w|jgp6`}QXD4SS2Onnio)X-3xFX*$C0JvFqB}vaAJM3Z1AvkrPvh+q-jdVVI`Yf4Z`Mz+%YX zP%$tp1lp&Zoa|mn)znOJ21x>ZRemG4>hKLpf6`x-J4uDt*Emawgt$Xeb>UksuPAr+ z(x&xj0rn#)PCt$aM7Xr{yQ=C$O!it_vhybP6fX38nBt!SL+;Qxc84Zt39Z5o(4+f+ zWwZetSp|r|0Ccd6>$HDR&e92*;X=t+y{n(`vYFf!GFvJtDx0$ie-`|g#5N0g)0~TC zmZ%;o^QwKW4hiU`9Y`K-V`)aP7golw+WVBsg2{Gfl(?H%4)1p=kzy7|y(E8``Zb2w z4~wx8M>E~n^Jjk+7r(gQ%j8XnUpS(Tmac)L%NY3Kmey(D?0A1Kz7Oc(h>gK`)!nl4 zHJ6-GA;0Wx`*#30(`zd_F^yIKNqk9&9Mj$1T}gRq(Ys^H>}`v$Z%?o0&1ZK(^0rhq zNw=dj1o&2zra$eT3zY4#< zMnh{eqbQsj>I=vDSyAy0z|JG!Od=5@`O@z02sgJ30Sjfr<)c`1aCXKDv?oFDI^Uw@ zb=s#xk+Ob4^EOeg(`}PZA3dbYDBw0qnue$v0}uN zTp{Pek^F^yc+_am0z>Nq6PDYgz0b__jXXTjV(uu5YNp%X4_I??e9I^x3mK71v>#XtKc z2|_z$L0`L3bodOH0ok^UQWzwDEjfmH|YsAKyOaZ z!aLVk0JyRBit`W5Pl4iXkT2)jx)8C7E7D7~x z{F$*qh~WsXYYqOfwi=<N8<3Ivfu*Z!O$T$zUJew|-mF>eu0Z&$Hb&dO1;} z=gCu{`%LVe4(p)vM7v8WtYEcrI<*Q#L+tOI_|-a637)5(SYf;1O?=e3Od*KuzkfXS z!X*3>>FNu+6@5oc%Y=z@ zzgO(G|0R#9ePV!S-?xp8D9kv3N)tY;jS1J4MLuW_+o9E(8DHa#q>C4RtF24K+;m|| znk!fDrUfm_IA33U%cam2qrPg?)y%hYLtcHXi0og}gE7c8Ai& zv~D;@w4C&boDP(4*dz;yJ~t|g1ZfQ0L`*#fL&uuM7d~=n&t-Xmn{^$sFw+4Is-<_y zFr@qEmy*dGHP~l?x0-yr6zdbasWrSyBftDveU+iGr#kE#sqjl6wy3F2_d08V>$N-a z%HEB)v^=R*&=b>8TTjOc%9!>`O}ruC%<%X{Y-d_c)JO<7`wS;BAt!%R64hu5&%qj% z!W&jO)WIMNEnQoXdZW#Ga`(>p6oeHnDzUS&+`K~`!T#3nPIq2s?+0gLB*q@Gu+Bo4 zCG7|Is~WFW_Xk>1>)3?$%h10$uN~fl(kHyRXEex!dsiu*{bu{u)~5tUByl(F;X1vO z+dXgWK+Tw@!(U1ptqjMWMk1_QBOgj2n1f$Bx=l}*25IC6D?7KEQ#|KE*7SjcI@{~v zmk5EMq*#g!j!S;2Yxxm@3(m*Fg;HMm?2%uvSWZ$L2@UFzS!omTDmda|G#jJwQ zE8Uq!-qseQBUw13zwM&K!|%1&s_TA(SI(p#3|i9^JrmH%lJC!7xk0d_|3dR(!-@`M%mIPSQnU%bV(>gk3&a6gxReId+Vz-E^^-MoXm$WY5RE;1Nkx`d49 z8t>TQmr~J>+p#EjxJS{Y{>bTWZOyB5PaXoLggb?1^(e`(uSlk!_i*6mTpQvs6o7tm>Si_TFfOXr_Q8iDf7BBw1+gq(~OI(;hZ%_+8n-vnf_Yc{A>}!py z^rXmd?~z&TKK*uY!~MK!!_MEzdJOk);uDZ!Jh$4DYeMW*XMp4aNB&{BL8M3yo5$-U5IZf@>!X|03jvp|qxHG17P|fOw!!&66e^-DBbwg4vrQtozmNQ> zgJ4rTtF+>s+C48<-Buek^R%|k{EcSEMd+y9VF6^pcYQ{?$ZOYs0nMqA73Ac>qabP$y-?sT@bNs+7aV~dF zY_VN;uPpcWPwioATZ&O{?ef(?y8Mr8R{9|^p5?{Hc!veT>|)74!Q zAde?oJ++2OOR-aTXV?-ciZvG&0XLW1prHNSyM4ZbYfw`Lz@_Ec29fDoz&a31grZgX7-~y zimqG8=$}C|w|eYV&x{Dw9K-56WGcy=;(9~2Js;@p>jm^A$c6bA1?J781DT2J%L#3- z$~DiB=1_xrpA8o9aO~#FrT1?88A@R1BjFo(Eowv`5OUNTvI5s`YJ@6B zd4^tCObWqa0SIhP`L9h!WAV0yojp|NYsS%w@15>=Bp2RIXs_4=7OWqR%26^_1wIXS zJg+-#e3|DpU}I#6v8Ltqe(=`uK8|LK1>5nefsQah%f5SO){mHfYkZ-az1nrajmGGJ zuQ0ybo<}N9uiV;7K>gdi3h4+j6y7udK}AsbggezpbJ&3)UMi08T%S8$@GQvD2IT*B zyEOQ8N^x&tTP`zWcKeDbX}6Dep*5DY;Q4W}aVq zGRM5T_(RH*z z%&dI3l}7qbt847Kvn{LpK;oD0uX}dp#S)Kc!S`27 zd~EOw3*VrW0%~{thuK-Bc>RxON1w*i80K3*5ook=Y?&9>1gDKoZ_c8;b-lhNo!C*j z3N{53sq3M`m~*Jt+q_#Ke+@^-oTbS5evwlSBy4vIXk2g<^KE=WOa!`E&)@SXos!xs z4u4oAhu|Mywh>xCo;wpvUJr}+q2-w&W;?oaY_(rD37#hMsePGyG8!ocnyGgb^hULx zDI`pJI*iIXMrVF*3vX*F44PBMVN_R`4uqi`U~K1L05gUDQQTwOW9l;#mM->0mg8t| zI*3g;(H?({OxQ}Jjyx^6J)0P^#=tuJMAe?OY8)BnCgIK!>v4jumCDY z*0rSd)K-acNGc@Vp)=LkNgKwT0a?dtOvI?}@vr2Zck0ASAQ4$+=G&`;fDs1og3It? z4f$zT?W}(YZ}XF4kWvpiF5DD}_#&VM0chB=<)19!3sX1uk_v!o7M9o=n^^NeqJ+m! zRBl_awDx%#6GH7w?ZURl(ua5aqi{mz{NZofjub;XEX#ec@Tgl@P7D^2BZ@Rf*C-?H zgj=SKuU}XC9Pb$MiLQo6|Epc8!==CNkPp`-nXijGXor46#QrcpZj*z}sn$nnML4b> z2(5}}vwEZ8SWF-iAg}&@&4#L)=hIvo>u%bnTYq3@$ZghWQGK4yRQOJy(M|1Jn{t

QD_q>sCk1;1-O)qX|hj#j+wf6lRI( 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 C10-w! literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json index 198204c..4a18ee0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,22 +10,22 @@ "license": "SEE LICENSE FILE", "dependencies": { "abort-controller": "^3.0.0", + "fake-indexeddb": "^6.2.5", "fluent-ffmpeg": "^2.1.2", "fs-extra": "^11.1.0", "got": "^12.0.2", "image-size": "^1.0.2", "isomorphic-webcrypto": "^2.3.8", - "matrix-js-sdk": "^34.13.0", + "matrix-js-sdk": "^41.5.0", "mime": "^3.0.0", "node-fetch": "^3.3.0", "node-localstorage": "^2.2.1", - "olm": "https://gitlab.matrix.org/matrix-org/olm/-/package_files/2572/download", "sharp": "^0.33.4", "tmp": "^0.2.1", "utf8": "^3.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=22.0.0" } }, "node_modules/@ampproject/remapping": { @@ -4188,20 +4188,14 @@ } }, "node_modules/@matrix-org/matrix-sdk-crypto-wasm": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-9.1.0.tgz", - "integrity": "sha512-CtPoNcoRW6ehwxpRQAksG3tR+NJ7k4DV02nMFYTDwQtie1V4R8OTY77BjEIs97NOblhtS26jU8m1lWsOBEz0Og==", + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-18.3.0.tgz", + "integrity": "sha512-9a4feyt8QLysARu7PHKaRWT+wcCd+IYH074LXp9QK5WqfN4zUXueRhiSSMNT18Bm+8q3sBR/4zxDxOSDR0M8Kg==", "license": "Apache-2.0", "engines": { - "node": ">= 10" + "node": ">= 18" } }, - "node_modules/@matrix-org/olm": { - "version": "3.2.15", - "resolved": "https://registry.npmjs.org/@matrix-org/olm/-/olm-3.2.15.tgz", - "integrity": "sha512-S7lOrndAK9/8qOtaTq/WhttJC/o4GAzdfK0MUPpo8ApzsJEC0QjtwrkC3KBXdFP1cD1MXi/mlKR7aaoVMKgs6Q==", - "license": "Apache-2.0" - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -6037,11 +6031,6 @@ "optional": true, "peer": true }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" - }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -8288,6 +8277,15 @@ } } }, + "node_modules/fake-indexeddb": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-6.2.5.tgz", + "integrity": "sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -9337,6 +9335,18 @@ "node": ">=0.10.0" } }, + "node_modules/is-network-error": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz", + "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -10717,42 +10727,27 @@ "integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==" }, "node_modules/matrix-js-sdk": { - "version": "34.13.0", - "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-34.13.0.tgz", - "integrity": "sha512-AAU8ZdCawca+7ucQfdcC3LA85OtCTV7QeqcjvKt/ZZhU3xL9VoawuoRQ+4R6H8KZnqyJmT4j7bdeC0jG4qcqLg==", + "version": "41.5.0", + "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-41.5.0.tgz", + "integrity": "sha512-CK3h+qQJ4wkVEUgEWc5MdLjccXyiFqncCC53P+auqOhnX2U6tAFsRfnbML1QQiKIsFMzqTrAnF/4a5LUUOIeXg==", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.12.5", - "@matrix-org/matrix-sdk-crypto-wasm": "^9.0.0", - "@matrix-org/olm": "3.2.15", + "@matrix-org/matrix-sdk-crypto-wasm": "^18.2.0", "another-json": "^0.2.0", "bs58": "^6.0.0", "content-type": "^1.0.4", "jwt-decode": "^4.0.0", - "loglevel": "^1.7.1", + "loglevel": "^1.9.2", "matrix-events-sdk": "0.0.1", - "matrix-widget-api": "^1.10.0", + "matrix-widget-api": "^1.16.1", "oidc-client-ts": "^3.0.1", - "p-retry": "4", - "sdp-transform": "^2.14.1", - "unhomoglyph": "^1.0.6", - "uuid": "11" + "p-retry": "8", + "sdp-transform": "^3.0.0", + "unhomoglyph": "^1.0.6" }, "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/matrix-js-sdk/node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" + "node": ">=22.0.0" } }, "node_modules/matrix-widget-api": { @@ -12298,13 +12293,6 @@ "node": ">=18" } }, - "node_modules/olm": { - "name": "@matrix-org/olm", - "version": "3.2.15", - "resolved": "https://gitlab.matrix.org/matrix-org/olm/-/package_files/2572/download", - "integrity": "sha512-5j5CLplrEodeqGMHEzwDZz6UlqWHyR8c1+tCpCj/yrIJ3V4kYbQVySUhrPNIz2k6j7Ief8GiNxxArjXfuGLfgg==", - "license": "Apache-2.0" - }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -12559,15 +12547,18 @@ } }, "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-8.0.0.tgz", + "integrity": "sha512-kFVqH1HxOHp8LupNsOys7bSV09VYTRLxarH/mokO4Rqhk6wGi70E0jh4VzvVGXfEVNggHoHLAMWsQqHyU1Ey9A==", + "license": "MIT", "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" + "is-network-error": "^1.3.0" }, "engines": { - "node": ">=8" + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-try": { @@ -13680,14 +13671,6 @@ "node": ">=4" } }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "engines": { - "node": ">= 4" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -13773,9 +13756,10 @@ } }, "node_modules/sdp-transform": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.15.0.tgz", - "integrity": "sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-3.0.0.tgz", + "integrity": "sha512-gfYVRGxjHkGF2NPeUWHw5u6T/KGFtS5/drPms73gaSuMaVHKCY3lpLnGDfswVQO0kddeePoti09AwhYP4zA8dQ==", + "license": "MIT", "bin": { "sdp-verify": "checker.js" } @@ -18417,14 +18401,9 @@ } }, "@matrix-org/matrix-sdk-crypto-wasm": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-9.1.0.tgz", - "integrity": "sha512-CtPoNcoRW6ehwxpRQAksG3tR+NJ7k4DV02nMFYTDwQtie1V4R8OTY77BjEIs97NOblhtS26jU8m1lWsOBEz0Og==" - }, - "@matrix-org/olm": { - "version": "3.2.15", - "resolved": "https://registry.npmjs.org/@matrix-org/olm/-/olm-3.2.15.tgz", - "integrity": "sha512-S7lOrndAK9/8qOtaTq/WhttJC/o4GAzdfK0MUPpo8ApzsJEC0QjtwrkC3KBXdFP1cD1MXi/mlKR7aaoVMKgs6Q==" + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-18.3.0.tgz", + "integrity": "sha512-9a4feyt8QLysARu7PHKaRWT+wcCd+IYH074LXp9QK5WqfN4zUXueRhiSSMNT18Bm+8q3sBR/4zxDxOSDR0M8Kg==" }, "@nodelib/fs.scandir": { "version": "2.1.5", @@ -19841,11 +19820,6 @@ "optional": true, "peer": true }, - "@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" - }, "@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -21631,6 +21605,11 @@ "base64-js": "^1.3.0" } }, + "fake-indexeddb": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-6.2.5.tgz", + "integrity": "sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==" + }, "fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -22427,6 +22406,11 @@ } } }, + "is-network-error": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz", + "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==" + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -23483,32 +23467,23 @@ "integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==" }, "matrix-js-sdk": { - "version": "34.13.0", - "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-34.13.0.tgz", - "integrity": "sha512-AAU8ZdCawca+7ucQfdcC3LA85OtCTV7QeqcjvKt/ZZhU3xL9VoawuoRQ+4R6H8KZnqyJmT4j7bdeC0jG4qcqLg==", + "version": "41.5.0", + "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-41.5.0.tgz", + "integrity": "sha512-CK3h+qQJ4wkVEUgEWc5MdLjccXyiFqncCC53P+auqOhnX2U6tAFsRfnbML1QQiKIsFMzqTrAnF/4a5LUUOIeXg==", "requires": { "@babel/runtime": "^7.12.5", - "@matrix-org/matrix-sdk-crypto-wasm": "^9.0.0", - "@matrix-org/olm": "3.2.15", + "@matrix-org/matrix-sdk-crypto-wasm": "^18.2.0", "another-json": "^0.2.0", "bs58": "^6.0.0", "content-type": "^1.0.4", "jwt-decode": "^4.0.0", - "loglevel": "^1.7.1", + "loglevel": "^1.9.2", "matrix-events-sdk": "0.0.1", - "matrix-widget-api": "^1.10.0", + "matrix-widget-api": "^1.16.1", "oidc-client-ts": "^3.0.1", - "p-retry": "4", - "sdp-transform": "^2.14.1", - "unhomoglyph": "^1.0.6", - "uuid": "11" - }, - "dependencies": { - "uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==" - } + "p-retry": "8", + "sdp-transform": "^3.0.0", + "unhomoglyph": "^1.0.6" } }, "matrix-widget-api": { @@ -24730,10 +24705,6 @@ "jwt-decode": "^4.0.0" } }, - "olm": { - "version": "https://gitlab.matrix.org/matrix-org/olm/-/package_files/2572/download", - "integrity": "sha512-5j5CLplrEodeqGMHEzwDZz6UlqWHyR8c1+tCpCj/yrIJ3V4kYbQVySUhrPNIz2k6j7Ief8GiNxxArjXfuGLfgg==" - }, "on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -24927,12 +24898,11 @@ } }, "p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-8.0.0.tgz", + "integrity": "sha512-kFVqH1HxOHp8LupNsOys7bSV09VYTRLxarH/mokO4Rqhk6wGi70E0jh4VzvVGXfEVNggHoHLAMWsQqHyU1Ey9A==", "requires": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" + "is-network-error": "^1.3.0" } }, "p-try": { @@ -25804,11 +25774,6 @@ "signal-exit": "^3.0.2" } }, - "retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" - }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -25873,9 +25838,9 @@ } }, "sdp-transform": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.15.0.tgz", - "integrity": "sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-3.0.0.tgz", + "integrity": "sha512-gfYVRGxjHkGF2NPeUWHw5u6T/KGFtS5/drPms73gaSuMaVHKCY3lpLnGDfswVQO0kddeePoti09AwhYP4zA8dQ==" }, "semver": { "version": "7.6.2", diff --git a/package.json b/package.json index 527815e..8bb2505 100644 --- a/package.json +++ b/package.json @@ -4,16 +4,16 @@ "description": "Matrix chat server client for Node-RED", "dependencies": { "abort-controller": "^3.0.0", + "fake-indexeddb": "^6.2.5", "fluent-ffmpeg": "^2.1.2", "fs-extra": "^11.1.0", "got": "^12.0.2", "image-size": "^1.0.2", "isomorphic-webcrypto": "^2.3.8", - "matrix-js-sdk": "^34.13.0", + "matrix-js-sdk": "^41.5.0", "mime": "^3.0.0", "node-fetch": "^3.3.0", "node-localstorage": "^2.2.1", - "olm": "https://gitlab.matrix.org/matrix-org/olm/-/package_files/2572/download", "sharp": "^0.33.4", "tmp": "^0.2.1", "utf8": "^3.0.0" @@ -51,11 +51,13 @@ "matrix-whois-user": "src/matrix-whois-user.js", "matrix-paginate-room": "src/matrix-paginate-room.js", "matrix-get-event": "src/matrix-get-event.js", - "matrix-event-relations": "src/matrix-event-relations.js" + "matrix-event-relations": "src/matrix-event-relations.js", + "matrix-verification": "src/matrix-verification.js", + "matrix-verification-action": "src/matrix-verification-action.js" } }, "engines": { - "node": ">=18.0.0" + "node": ">=22.0.0" }, "keywords": [ "node-red", @@ -72,8 +74,5 @@ "name": "Skylar Sadlier", "url": "https://skylar.tech" }, - "license": "SEE LICENSE FILE", - "engines": { - "node": ">=18.0.0" - } + "license": "SEE LICENSE FILE" } diff --git a/src/matrix-crypt-file.html b/src/matrix-crypt-file.html index ae73715..65a7a3b 100644 --- a/src/matrix-crypt-file.html +++ b/src/matrix-crypt-file.html @@ -43,6 +43,16 @@ string | null

the decoded mxc url.
+ +
msg.headers + object +
+
optional HTTP headers. If provided, they are used when downloading media (for example authenticated media bearer tokens).
+ +
msg.access_token + string +
+
optional Matrix access token. Used as a bearer token if msg.headers.Authorization is not present.

Outputs

@@ -74,4 +84,4 @@
- \ No newline at end of file + diff --git a/src/matrix-crypt-file.js b/src/matrix-crypt-file.js index a6236cf..c15f6db 100644 --- a/src/matrix-crypt-file.js +++ b/src/matrix-crypt-file.js @@ -32,7 +32,9 @@ module.exports = function(RED) { } try{ - let buffer = await got(msg.url).buffer(); + const requestOptions = getRequestOptions(msg); + + let buffer = await downloadBufferWithFallback(got, msg.url, requestOptions); msg.payload = Buffer.from(await decryptAttachment(buffer, msg.content.file)); // handle thumbnail decryption if necessary @@ -41,13 +43,14 @@ module.exports = function(RED) { && msg.thumbnail_url && msg.content.info.thumbnail_file ) { - let thumb_buffer = await got(msg.thumbnail_url).buffer(); + let thumb_buffer = await downloadBufferWithFallback(got, msg.thumbnail_url, requestOptions); msg.thumbnail_payload = Buffer.from(await decryptAttachment(thumb_buffer, msg.content.info.thumbnail_file)); } } catch(error){ node.error(error); msg.error = error; node.send([null, msg]); + return; } msg.filename = msg.content.filename || msg.content.body; @@ -57,6 +60,52 @@ module.exports = function(RED) { } RED.nodes.registerType("matrix-decrypt-file", MatrixDecryptFile); + function getRequestOptions(msg) { + const headers = { ...(msg.headers || {}) }; + if (!headers.Authorization && msg.access_token) { + headers.Authorization = `Bearer ${msg.access_token}`; + } + + return Object.keys(headers).length ? { headers } : {}; + } + + function getMediaEndpointFallbackUrl(url) { + if (typeof url !== "string") { + return null; + } + + if (url.includes("/_matrix/media/v3/download/")) { + return url.replace("/_matrix/media/v3/download/", "/_matrix/client/v1/media/download/"); + } + + if (url.includes("/_matrix/client/v1/media/download/")) { + return url.replace("/_matrix/client/v1/media/download/", "/_matrix/media/v3/download/"); + } + + if (url.includes("/_matrix/media/v3/thumbnail/")) { + return url.replace("/_matrix/media/v3/thumbnail/", "/_matrix/client/v1/media/thumbnail/"); + } + + if (url.includes("/_matrix/client/v1/media/thumbnail/")) { + return url.replace("/_matrix/client/v1/media/thumbnail/", "/_matrix/media/v3/thumbnail/"); + } + + return null; + } + + async function downloadBufferWithFallback(got, url, requestOptions) { + try { + return await got(url, requestOptions).buffer(); + } catch (error) { + const fallbackUrl = getMediaEndpointFallbackUrl(url); + if (error?.response?.statusCode === 404 && fallbackUrl && fallbackUrl !== url) { + return await got(fallbackUrl, requestOptions).buffer(); + } + + throw error; + } + } + function atob(a) { return Buffer.from(a, 'base64').toString('binary'); } @@ -200,4 +249,4 @@ module.exports = function(RED) { } return uint8Array; } -} \ No newline at end of file +} diff --git a/src/matrix-crypto-store.js b/src/matrix-crypto-store.js new file mode 100644 index 0000000..eb74cf3 --- /dev/null +++ b/src/matrix-crypto-store.js @@ -0,0 +1,175 @@ +/** + * Persistence helpers for the matrix-js-sdk Rust crypto store in Node.js. + * + * matrix-js-sdk v37+ removed the legacy (libolm) crypto stack. The Rust crypto + * replacement persists its state (device identity, Olm/megolm sessions, etc.) + * to IndexedDB, which does not exist in Node.js. We provide an in-memory + * IndexedDB via `fake-indexeddb` and snapshot the databases to/from disk so the + * crypto state survives Node-RED restarts. + * + * The `indexeddbshim` package (which can persist to disk directly) is not used + * because it is incompatible with the Rust crypto store migrations + * (see matrix-org/matrix-sdk-crypto-wasm#195). `fake-indexeddb` is spec + * compliant, so snapshotting it through the public IndexedDB API is reliable. + */ +const fs = require('fs-extra'); +const v8 = require('v8'); + +let shimInstalled = false; + +/** + * Install the in-memory IndexedDB shim onto globalThis. Idempotent. Must be + * called before MatrixClient.initRustCrypto(). + */ +function ensureIndexedDBShim() { + if (shimInstalled || globalThis.indexedDB) { + shimInstalled = true; + return; + } + // `fake-indexeddb/auto` assigns indexedDB / IDBKeyRange / etc. onto globalThis. + require('fake-indexeddb/auto'); + shimInstalled = true; +} + +function reqAsync(req) { + return new Promise((resolve, reject) => { + req.onsuccess = () => resolve(req.result); + req.onerror = () => reject(req.error); + }); +} + +function txDone(tx) { + return new Promise((resolve, reject) => { + tx.oncomplete = () => resolve(); + tx.onerror = () => reject(tx.error); + tx.onabort = () => reject(tx.error || new Error('IndexedDB transaction aborted')); + }); +} + +/** + * Restore previously snapshotted IndexedDB databases from `filePath` into the + * in-memory store. No-op if the snapshot does not exist. Databases that are + * already present in memory (e.g. after a Node-RED redeploy that kept the + * process alive) are left untouched so the live state is not clobbered. + * + * Must be called before MatrixClient.initRustCrypto(). + * + * @returns {Promise} true if at least one database was restored. + */ +async function restoreCryptoStore(filePath) { + ensureIndexedDBShim(); + + if (!filePath || !fs.pathExistsSync(filePath)) { + return false; + } + + let databases; + try { + databases = v8.deserialize(fs.readFileSync(filePath)); + } catch (e) { + // Corrupt/unreadable snapshot - start fresh rather than crash. + return false; + } + if (!Array.isArray(databases) || !databases.length) { + return false; + } + + const existing = new Set((await indexedDB.databases()).map((d) => d.name)); + let restored = 0; + + for (const dbSpec of databases) { + if (existing.has(dbSpec.name)) { + continue; // already live in memory - don't overwrite + } + + const openReq = indexedDB.open(dbSpec.name, dbSpec.version); + openReq.onupgradeneeded = () => { + const db = openReq.result; + for (const store of dbSpec.stores) { + if (db.objectStoreNames.contains(store.name)) { + continue; + } + const os = db.createObjectStore(store.name, { + keyPath: store.keyPath || undefined, + autoIncrement: store.autoIncrement, + }); + for (const ix of store.indexes) { + os.createIndex(ix.name, ix.keyPath, { unique: ix.unique, multiEntry: ix.multiEntry }); + } + } + }; + const db = await reqAsync(openReq); + + for (const store of dbSpec.stores) { + if (!store.values.length) { + continue; + } + const tx = db.transaction(store.name, 'readwrite'); + const os = tx.objectStore(store.name); + for (let i = 0; i < store.values.length; i++) { + if (store.keyPath) { + os.put(store.values[i]); + } else { + os.put(store.values[i], store.keys[i]); + } + } + await txDone(tx); + } + db.close(); + restored++; + } + + return restored > 0; +} + +/** + * Snapshot IndexedDB databases to `filePath`. If `dbNamePrefix` is given only + * databases whose name starts with it are written, so multiple Matrix accounts + * sharing one process do not snapshot each other's data. + * + * The write is atomic (temp file + rename). Values are serialized with the V8 + * serializer so typed arrays / Maps inside the crypto store survive intact. + * + * @returns {Promise} true if a snapshot file was written. + */ +async function snapshotCryptoStore(filePath, dbNamePrefix) { + if (!filePath || !globalThis.indexedDB || typeof indexedDB.databases !== 'function') { + return false; + } + + let dbList = await indexedDB.databases(); + if (dbNamePrefix) { + dbList = dbList.filter((d) => typeof d.name === 'string' && d.name.startsWith(dbNamePrefix)); + } + + const out = []; + for (const { name, version } of dbList) { + const db = await reqAsync(indexedDB.open(name, version)); + const stores = []; + for (const storeName of Array.from(db.objectStoreNames)) { + const tx = db.transaction(storeName, 'readonly'); + const os = tx.objectStore(storeName); + const indexes = Array.from(os.indexNames).map((n) => { + const ix = os.index(n); + return { name: n, keyPath: ix.keyPath, unique: ix.unique, multiEntry: ix.multiEntry }; + }); + stores.push({ + name: storeName, + keyPath: os.keyPath, + autoIncrement: os.autoIncrement, + indexes, + values: await reqAsync(os.getAll()), + keys: await reqAsync(os.getAllKeys()), + }); + } + db.close(); + out.push({ name, version, stores }); + } + + const tmp = `${filePath}.tmp`; + fs.writeFileSync(tmp, v8.serialize(out)); + fs.renameSync(tmp, filePath); + return true; +} + +module.exports = { ensureIndexedDBShim, restoreCryptoStore, snapshotCryptoStore }; diff --git a/src/matrix-get-user.js b/src/matrix-get-user.js index 9afa8fc..0f8b595 100644 --- a/src/matrix-get-user.js +++ b/src/matrix-get-user.js @@ -106,14 +106,14 @@ module.exports = function(RED) { let user2 = {}; try { - let profileInfo = node.server.matrixClient.getProfileInfo(userId); - if(Object.keys(profileInfo).length > 0) { + let profileInfo = await node.server.matrixClient.getProfileInfo(userId); + if(profileInfo && Object.keys(profileInfo).length > 0) { user2.displayName = profileInfo.displayname; user2.avatarUrl = profileInfo.avatar_url; } - let presence = node.server.matrixClient.getPresence(userId); - if(Object.keys(presence).length > 0) { + let presence = await node.server.matrixClient.getPresence(userId); + if(presence && Object.keys(presence).length > 0) { user2.currentlyActive = presence.currently_active; user2.lastActiveAgo = presence.last_active_ago; user2.presenceStatusMsg = presence.presence_status_msg; diff --git a/src/matrix-invite-room.js b/src/matrix-invite-room.js index 61c7589..22a7f92 100644 --- a/src/matrix-invite-room.js +++ b/src/matrix-invite-room.js @@ -54,9 +54,10 @@ module.exports = function(RED) { return; } - // we need the status code, so set onlydata to false for this request + // invite(roomId, userId, opts|reason) - the SDK no longer accepts a + // callback argument, so the reason is passed as the 3rd parameter. node.server.matrixClient - .invite(msg.topic, msg.userId, undefined, msg.reason || undefined) + .invite(msg.topic, msg.userId, msg.reason || undefined) .then(function(e){ msg.payload = e; node.send([msg, null]); diff --git a/src/matrix-receive.html b/src/matrix-receive.html index 3274c4a..bfe4a50 100644 --- a/src/matrix-receive.html +++ b/src/matrix-receive.html @@ -227,6 +227,11 @@
msg.content object
the message's content object
+ +
+
msg.headers object | null
+
for media events, includes auth headers (for example Authorization: Bearer ...) used by authed media endpoints.
+
  • msg.type == 'm.text' @@ -358,4 +363,4 @@
  • - \ No newline at end of file + diff --git a/src/matrix-receive.js b/src/matrix-receive.js index 77059c8..45f1e76 100644 --- a/src/matrix-receive.js +++ b/src/matrix-receive.js @@ -47,11 +47,31 @@ module.exports = function(RED) { return; } + const setAuthHeaders = () => { + const accessToken = node.server.matrixClient.getAccessToken?.(); + if (accessToken) { + msg.headers = { + ...(msg.headers || {}), + Authorization: `Bearer ${accessToken}`, + }; + } + }; + const setUrls = (urlKey, encryptedKey) => { const url = msg.encrypted ? msg.content[encryptedKey]?.url : msg.content[urlKey]; if (url) { - msg.url = node.server.matrixClient.mxcUrlToHttp(url); + const authenticatedUrl = node.server.matrixClient.mxcUrlToHttp( + url, + undefined, + undefined, + undefined, + false, + true, + true, + ); + msg.url = authenticatedUrl || node.server.matrixClient.mxcUrlToHttp(url); msg.mxc_url = url; + setAuthHeaders(); } }; @@ -59,8 +79,18 @@ module.exports = function(RED) { const thumbnailFile = msg.content.info?.[infoKey]; const thumbnailUrl = thumbnailFile?.url; if (thumbnailUrl) { - msg.thumbnail_url = node.server.matrixClient.mxcUrlToHttp(thumbnailUrl); + const authenticatedThumbnailUrl = node.server.matrixClient.mxcUrlToHttp( + thumbnailUrl, + undefined, + undefined, + undefined, + false, + true, + true, + ); + msg.thumbnail_url = authenticatedThumbnailUrl || node.server.matrixClient.mxcUrlToHttp(thumbnailUrl); msg.thumbnail_mxc_url = thumbnailUrl; + setAuthHeaders(); } }; @@ -141,4 +171,4 @@ module.exports = function(RED) { }); } RED.nodes.registerType("matrix-receive", MatrixReceiveMessage); -} \ No newline at end of file +} diff --git a/src/matrix-server-config.html b/src/matrix-server-config.html index 60db2f7..7d9a29b 100644 --- a/src/matrix-server-config.html +++ b/src/matrix-server-config.html @@ -31,17 +31,232 @@ accessToken: { type: "password", required: true }, deviceId: { type: "text", required: false }, url: { type: "text", required: true }, + password: { type: "password", required: false }, }, defaults: { name: { value: null }, autoAcceptRoomInvites: { value: true }, enableE2ee: { type: "checkbox", value: true }, global: { type: "checkbox", value: true }, - allowUnknownDevices: { type: "checkbox", value: false } + allowUnknownDevices: { type: "checkbox", value: true } }, icon: "matrix.png", label: function() { return this.name || undefined; + }, + oneditprepare: function() { + const nodeId = this.id; + + // --- Secure backup / cross-signing setup (modal dialog) --- + // The modal is built once and reused across editor sessions; the + // current node id is stored on the overlay so its handlers target + // whichever server config node is being edited. + if (!document.getElementById("matrix-sb-overlay")) { + $('').appendTo("head"); + + $('
    ' + + '
    Secure Backup & Cross-signing' + + '×
    ' + + '
    ' + + '
    ' + + '' + + '' + + '' + + '
    ' + + '
    ' + + '' + + '' + + '
    ').appendTo(document.body); + + var sbEsc = function(s) { return $("
    ").text(s == null ? "" : String(s)).html(); }; + var sbClose = function() { $("#matrix-sb-overlay").fadeOut(120); }; + var sbId = function() { return $("#matrix-sb-overlay").data("matrixNodeId"); }; + var sbBtns = function(disabled) { $("#matrix-sb-unlock-btn,#matrix-sb-reset-btn").prop("disabled", disabled); }; + var sbCall = function(body) { + return $.ajax({ + url: "matrix-chat/secure-backup", type: "POST", + contentType: "application/json", data: JSON.stringify(body), + }); + }; + var sbState = function(icon, color, html) { + $("#matrix-sb-state").html('' + html + ''); + }; + var sbResult = function(ok, text, key) { + var h = sbEsc(text); + if (key) { h += '
    ' + sbEsc(key) + '
    '; } + $("#matrix-sb-result").removeClass("ok err").addClass(ok ? "ok" : "err").html(h).show(); + }; + var sbStatus = function() { + $("#matrix-sb-unlock,#matrix-sb-reset,#matrix-sb-result,#matrix-sb-reset-toggle").hide(); + sbState("fa-spinner fa-spin", "#888", "Checking the account…"); + sbCall({ id: sbId(), action: "status" }).done(function(data) { + if (data.result !== "ok") { + sbState("fa-exclamation-triangle", "#c9302c", "Could not check the account."); + sbResult(false, data.message || "Unknown error"); + return; + } + if (data.crossSigningReady) { + sbState("fa-check-circle", "#3a9a4e", "Cross-signing is set up. The bot's device is cross-signed."); + $("#matrix-sb-reset-toggle").show(); + } else if (data.secretStorageExists) { + sbState("fa-lock", "#d18a1b", "This account has an existing secure backup. Enter its recovery key to set up cross-signing for the bot."); + $("#matrix-sb-recoverykey").val(""); + $("#matrix-sb-unlock,#matrix-sb-reset-toggle").show(); + } else { + sbState("fa-shield", "#888", "No secure backup exists yet. Set one up to enable cross-signing."); + $("#matrix-sb-password").val(""); + $("#matrix-sb-reset").show(); + } + sbBtns(false); + }).fail(function() { + sbState("fa-exclamation-triangle", "#c9302c", "Request failed — is Node-RED still running?"); + }); + }; + + $("#matrix-sb-x,#matrix-sb-close").on("click", sbClose); + $("#matrix-sb-overlay").on("mousedown", function(e) { if (e.target === this) { sbClose(); } }); + $(document).on("keydown.matrixsb", function(e) { + if (e.key === "Escape" && $("#matrix-sb-overlay").is(":visible")) { sbClose(); } + }); + $("#matrix-sb-reset-toggle").on("click", function() { + $(this).hide(); + $("#matrix-sb-password").val(""); + $("#matrix-sb-reset").show(); + }); + $("#matrix-sb-unlock-btn").on("click", function() { + sbBtns(true); + sbState("fa-spinner fa-spin", "#888", "Unlocking secure backup…"); + sbCall({ id: sbId(), action: "unlock", recoveryKey: $("#matrix-sb-recoverykey").val() }) + .done(function(data) { + if (data.result !== "ok") { + sbState("fa-lock", "#d18a1b", "Enter the recovery key to set up cross-signing."); + sbResult(false, data.message); sbBtns(false); return; + } + $("#matrix-sb-unlock,#matrix-sb-reset,#matrix-sb-reset-toggle").hide(); + sbState("fa-check-circle", "#3a9a4e", "Done."); + sbResult(true, data.message); + }) + .fail(function() { sbResult(false, "Request failed — is Node-RED still running?"); sbBtns(false); }); + }); + $("#matrix-sb-reset-btn").on("click", function() { + sbBtns(true); + sbState("fa-spinner fa-spin", "#888", "Resetting cross-signing & secure backup…"); + sbCall({ id: sbId(), action: "reset", password: $("#matrix-sb-password").val() }) + .done(function(data) { + if (data.result !== "ok") { + sbState("fa-shield", "#d18a1b", "Enter the account password to reset."); + sbResult(false, data.message); sbBtns(false); return; + } + $("#matrix-sb-unlock,#matrix-sb-reset,#matrix-sb-reset-toggle").hide(); + sbState("fa-check-circle", "#3a9a4e", "Reset complete."); + sbResult(true, data.message, data.recoveryKey); + }) + .fail(function() { sbResult(false, "Request failed — is Node-RED still running?"); sbBtns(false); }); + }); + + // expose the status loader so per-session click handlers can call it + $("#matrix-sb-overlay").data("sbStatusFn", sbStatus); + } + + $("#matrix-secure-backup-btn").on("click", function() { + $("#matrix-sb-overlay").data("matrixNodeId", nodeId).fadeIn(120); + $("#matrix-sb-overlay").data("sbStatusFn")(); + }); + + // --- Login: fetch a fresh access token & device id --- + $("#matrix-login-btn").on("click", function() { + function prettyPrintJson(json) { + try { return typeof json === 'object' ? JSON.stringify(json, null, 2) : json; } + catch (error) { return json; } + } + let userId = $("#node-config-input-userId").val(), + userPassword = $("#node-config-input-password").val(), + serverUrl = $("#node-config-input-url").val(); + if (!userId) { alert("User ID is required to fetch access token."); return; } + if (!userPassword) { alert("Password is required to fetch access token."); return; } + if (!serverUrl) { alert("Server URL is required to fetch access token."); return; } + + $("#matrix-login-btn, #matrix-chat-login-error, #matrix-chat-login-success").hide(); + $("#matrix-access-token-loader").show(); + $.ajax({ + type: 'POST', + url: 'matrix-chat/login', + dataType: 'json', + data: { + 'userId': userId, + 'password': userPassword, + 'baseUrl': serverUrl, + 'displayName': $("#node-config-input-deviceLabel").val(), + } + }).then( + function(data) { + if (data.result && data.result === 'ok') { + $("#matrix-chat-login-error").hide(); + $("#matrix-chat-login-success") + .html("Login Successful! Auth Token and Device ID have been set below.") + .show(); + $("#node-config-input-accessToken").val(data.token); + $("#node-config-input-deviceId").val(data.device_id); + } else if (data.result && data.result === 'error') { + $("#matrix-chat-login-success").hide(); + $("#matrix-chat-login-error") + .html(data.message ? ('Failed to login:
    ' + prettyPrintJson(data.message)) : 'Failed to login') + .show(); + } + $("#matrix-login-btn").show(); + $("#matrix-access-token-loader").hide(); + }, + function() { + $("#matrix-chat-login-success").hide(); + $("#matrix-chat-login-error") + .html("Failed to login due to server error communicating with Node-RED") + .show(); + $("#matrix-login-btn").show(); + $("#matrix-access-token-loader").hide(); + } + ); + }); } }); @@ -72,7 +287,10 @@
    - Password is never saved and is only used to fetch an access token using the button below. + Optional. Used to fetch an access token with the button below, and — if you + enable cross-signing — as a fallback when the homeserver requires the account + password to upload signing keys. If set, it is stored (encrypted) with the node's + credentials. Leave blank if you only want to use an access token.
    @@ -145,88 +363,55 @@ Allow sending messages to a room with unknown devices which have not been verified. - +
    + + +
    +
    + Sets up cross-signing so the bot's own device shows as verified. The server + configuration must be deployed and connected first. +
    diff --git a/src/matrix-server-config.js b/src/matrix-server-config.js index d69cd49..234cbcd 100644 --- a/src/matrix-server-config.js +++ b/src/matrix-server-config.js @@ -1,12 +1,14 @@ -const {RelationType, TimelineWindow} = require("matrix-js-sdk"); +// matrix-js-sdk is an ES module; load it via dynamic import so this CommonJS +// node keeps working. All SDK-dependent setup awaits this promise. +const sdkPromise = import("matrix-js-sdk"); +// The crypto-api enums (CryptoEvent, VerificationPhase, ...) are not re-exported +// from the package root, so they are imported from the crypto-api subpath. +const cryptoApiPromise = import("matrix-js-sdk/lib/crypto-api/index.js"); -global.Olm = require('olm'); const fs = require("fs-extra"); -const sdk = require("matrix-js-sdk"); const { resolve } = require('path'); const { LocalStorage } = require('node-localstorage'); -const { LocalStorageCryptoStore } = require('matrix-js-sdk/lib/crypto/store/localStorage-crypto-store'); -const {RoomEvent, RoomMemberEvent, HttpApiEvent, ClientEvent, MemoryStore} = require("matrix-js-sdk"); +const { ensureIndexedDBShim, restoreCryptoStore, snapshotCryptoStore } = require('./matrix-crypto-store'); require("abort-controller/polyfill"); // polyfill abort-controller if we don't have it if (!globalThis.fetch) { // polyfill fetch if we don't have it @@ -17,6 +19,41 @@ if (!globalThis.fetch) { } } +/** + * Resolve the real homeserver base URL for a configured server name / URL. + * + * Uses matrix-js-sdk's built-in .well-known auto-discovery: given e.g. + * "https://example.org" it looks up https://example.org/.well-known/matrix/client + * and returns the homeserver it delegates to (e.g. https://matrix.example.org). + * If there is no .well-known delegation (or discovery fails), the original URL + * is returned unchanged, so explicitly-configured homeserver URLs still work. + */ +async function resolveHomeserverUrl(sdk, configuredUrl) { + if(!configuredUrl) { + return configuredUrl; + } + let domain; + try { + domain = new URL(configuredUrl).host; + } catch(e) { + // not a full URL - treat the value itself as a domain + domain = String(configuredUrl).replace(/^https?:\/\//i, '').replace(/\/.*$/, ''); + } + if(!domain) { + return configuredUrl; + } + try { + const discovery = await sdk.AutoDiscovery.findClientConfig(domain); + const homeserver = discovery['m.homeserver']; + if(homeserver && homeserver.state === sdk.AutoDiscovery.SUCCESS && homeserver.base_url) { + return homeserver.base_url; + } + } catch(e) { + // discovery failed unexpectedly - fall back to the configured URL + } + return configuredUrl; +} + module.exports = function(RED) { // disable logging if set to "off" let loggingSettings = RED.settings.get('logging'); @@ -25,8 +62,9 @@ module.exports = function(RED) { typeof loggingSettings.console.level !== 'undefined' && ['info','debug','trace'].indexOf(loggingSettings.console.level.toLowerCase()) >= 0 ) { - const { logger } = require('matrix-js-sdk/lib/logger'); - logger.disableAll(); + import('matrix-js-sdk/lib/logger.js') + .then(({ logger }) => logger.disableAll()) + .catch(() => { /* logger module path changed - ignore */ }); } function MatrixFolderNameFromUserId(name) { @@ -54,10 +92,25 @@ module.exports = function(RED) { this.url = this.credentials.url; this.autoAcceptRoomInvites = n.autoAcceptRoomInvites; this.e2ee = n.enableE2ee || false; + // Whether to send encrypted messages to devices that have not been + // verified. Undefined (config saved before this option existed) keeps + // the long-standing behaviour of allowing unverified devices. + this.allowUnknownDevices = n.allowUnknownDevices; + // Optional account password (used by the login helper, and as fallback + // user-interactive auth when resetting secure backup / cross-signing). + this.botPassword = this.credentials.password || null; this.globalAccess = n.global; this.initializedAt = new Date(); node.initialSyncLimit = 25; + // Live device-verification state, shared with the matrix-verification + // and matrix-verification-action nodes. Keyed by verification id. + node.verificationRequests = new Map(); // id -> VerificationRequest + node.verificationSas = new Map(); // id -> ShowSasCallbacks + // Cached Secure Secret Storage (4S) key as [keyId, Uint8Array], set by + // the /matrix-chat/secure-backup admin endpoint once unlocked. + node._secretStorageKeyCache = null; + // Keep track of all consumers of this node to be able to catch errors node.register = function(consumerNode) { node.users[consumerNode.id] = consumerNode; @@ -65,7 +118,7 @@ module.exports = function(RED) { node.deregister = function(consumerNode) { delete node.users[consumerNode.id]; }; - + if(!this.userId) { node.log("Matrix connection failed: missing user ID in configuration."); return; @@ -77,10 +130,17 @@ module.exports = function(RED) { let retryStartTimeout = null; + // Rust crypto persistence (see ./matrix-crypto-store.js). Each Matrix + // account gets its own IndexedDB name prefix and on-disk snapshot so + // multiple server-config nodes never collide. + let cryptoDbPrefix = 'mxjssdk-' + MatrixFolderNameFromUserId(this.userId), + cryptoSnapshotPath = null, + cryptoSnapshotInterval = null; + if(!this.credentials.accessToken) { - node.error("Matrix connection failed: missing access token in configuration.", {}); + node.error("Matrix connection failed: missing access token in configuration."); } else if(!this.url) { - node.error("Matrix connection failed: missing server URL in configuration.", {}); + node.error("Matrix connection failed: missing server URL in configuration."); } else { node.setConnected = async function(connected, cb) { if (node.connected !== connected) { @@ -98,7 +158,7 @@ module.exports = function(RED) { device_id = this.matrixClient.getDeviceId(); if(!device_id && node.enableE2ee) { - node.error("Failed to auto detect deviceId for this auth token. You will need to manually specify one. You may need to login to create a new deviceId.", {}) + node.error("Failed to auto detect deviceId for this auth token. You will need to manually specify one. You may need to login to create a new deviceId.") } else { if(!stored_device_id || stored_device_id !== device_id) { node.log(`Saving Device ID (old:${stored_device_id} new:${device_id})`); @@ -117,13 +177,13 @@ module.exports = function(RED) { }).then( function(response) {}, function(error) { - node.error("Failed to set device label: " + error, {}); + node.error("Failed to set device label: " + error); } ); } }, function(error) { - node.error("Failed to fetch device: " + error, {}); + node.error("Failed to fetch device: " + error); } ); } @@ -142,25 +202,62 @@ module.exports = function(RED) { }; node.setConnected(false); - fs.ensureDirSync(storageDir); // create storage directory if it doesn't exist - upgradeDirectoryIfNecessary(node, storageDir); - node.matrixClient = sdk.createClient({ - baseUrl: this.url, - accessToken: this.credentials.accessToken, - cryptoStore: new LocalStorageCryptoStore(localStorage), - store: new MemoryStore({ - localStorage: localStorage, - }), - userId: this.userId, - deviceId: (this.deviceId || getStoredDeviceId(localStorage)) || undefined - // verificationMethods: ["m.sas.v1"] - }); + node.isConnected = function() { + return node.connected; + }; - node.debug(`hasLazyLoadMembersEnabled=${node.matrixClient.hasLazyLoadMembersEnabled()}`); + // Snapshot the Rust crypto store to disk so E2EE state survives + // restarts. No-op when E2EE is disabled. + async function persistCrypto() { + if(!cryptoSnapshotPath) { + return; + } + try { + await snapshotCryptoStore(cryptoSnapshotPath, cryptoDbPrefix); + } catch(e) { + node.error("Failed to persist Matrix crypto store: " + e); + } + } - // set globally if configured to do so - if(this.globalAccess) { - this.context().global.set('matrixClient["'+this.userId+'"]', node.matrixClient); + // Discard all persisted crypto state for this account. Used when the + // device ID changes - the old crypto store belongs to a device that + // no longer exists and the Rust crypto stack refuses to load it. + async function discardCryptoStore() { + // remove the persisted Rust crypto snapshot + try { + if(cryptoSnapshotPath) { + fs.removeSync(cryptoSnapshotPath); + } + } catch(e) { + node.warn("Could not remove crypto snapshot: " + e); + } + // remove legacy (libolm) crypto data from local storage + try { + for(let i = localStorage.length - 1; i >= 0; i--) { + let key = localStorage.key(i); + if(key && key.indexOf('crypto') === 0) { + localStorage.removeItem(key); + } + } + } catch(e) { + node.warn("Could not clear legacy crypto store: " + e); + } + // drop any in-memory IndexedDB database for this account's crypto store + try { + if(globalThis.indexedDB && typeof indexedDB.databases === 'function') { + let dbs = await indexedDB.databases(); + for(let db of dbs) { + if(db.name && db.name.indexOf(cryptoDbPrefix) === 0) { + await new Promise(function(resolve) { + let req = indexedDB.deleteDatabase(db.name); + req.onsuccess = req.onerror = req.onblocked = function(){ resolve(); }; + }); + } + } + } + } catch(e) { + node.warn("Could not clear in-memory crypto database: " + e); + } } function stopClient() { @@ -172,284 +269,464 @@ module.exports = function(RED) { if(retryStartTimeout) { clearTimeout(retryStartTimeout); } + if(cryptoSnapshotInterval) { + clearInterval(cryptoSnapshotInterval); + cryptoSnapshotInterval = null; + } } node.on('close', function(done) { stopClient(); - if(node.globalAccess) { - try { - node.context().global.set('matrixClient["'+node.userId+'"]', undefined); - } catch(e){ - node.error(e.message, {}); - } - } - done(); - }); - - node.isConnected = function() { - return node.connected; - }; - - node.matrixClient.on(RoomEvent.Timeline, async function(event, room, toStartOfTimeline, removed, data) { - if (toStartOfTimeline) { - node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: paginated result"); - return; // ignore paginated results - } - if (!data || !data.liveEvent) { - node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: old message"); - return; // ignore old message (we only want live events) - } - if(node.initializedAt > event.getDate()) { - node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: old message before init"); - return; // skip events that occurred before our client initialized - } - - try { - await node.matrixClient.decryptEventIfNeeded(event); - } catch (error) { - node.error(error, {}); - return; - } - - const isDmRoom = (room) => { - // Find out if this is a direct message room. - let isDM = !!room.getDMInviter(); - const allMembers = room.currentState.getMembers(); - if (!isDM && allMembers.length <= 2) { - // if not a DM, but there are 2 users only - // double check DM (needed because getDMInviter works only if you were invited, not if you invite) - // hence why we check for each member - if (allMembers.some((m) => m.getDMInviter())) { - return true; + persistCrypto().finally(function() { + if(node.globalAccess) { + try { + node.context().global.set('matrixClient["'+node.userId+'"]', undefined); + } catch(e){ + node.error(e.message); } } - return allMembers.length <= 2 && isDM; + done(); + }); + }); + + fs.ensureDirSync(storageDir); // create storage directory if it doesn't exist + upgradeDirectoryIfNecessary(node, storageDir); + + if(node.e2ee) { + cryptoSnapshotPath = localStorageDir + '/rust-crypto-store.v8'; + } + + setupClient().catch(function(error) { + node.error(error); + }); + + async function setupClient() { + const sdk = await sdkPromise; + const { + RelationType, RoomEvent, RoomMemberEvent, HttpApiEvent, ClientEvent, + MemoryStore, LocalStorageCryptoStore, + } = sdk; + const { + CryptoEvent, VerificationRequestEvent, VerifierEvent, VerificationPhase, + } = await cryptoApiPromise; + + // ---- Device verification ---------------------------------- + // Surface a verification request (and every subsequent phase + // change) to the matrix-verification node as a "Verification.update" + // event. Live request objects are kept in node.verificationRequests + // so the matrix-verification-action node can act on them by id. + function buildVerificationMsg(request, sasShown) { + let phase = sasShown + ? 'sas' + : String(VerificationPhase[request.phase] || 'unknown').toLowerCase(); + let msg = { + verificationId : request.transactionId, + phase : phase, + payload : phase, + userId : request.otherUserId, + deviceId : request.otherDeviceId || null, + topic : request.roomId || null, + isSelfVerification: request.isSelfVerification, + initiatedByMe : request.initiatedByMe, + }; + // chosenMethod is null until a verification method is picked. + // (request.methods is intentionally not used - it is not + // implemented in the Rust crypto stack and always throws.) + try { + msg.chosenMethod = request.chosenMethod || null; + } catch(e) { + msg.chosenMethod = null; + } + let sas = node.verificationSas.get(request.transactionId); + if(sas && sas.sas) { + msg.sas = { + emoji : sas.sas.emoji || null, + decimal: sas.sas.decimal || null, + }; + } + if(request.phase === VerificationPhase.Cancelled) { + msg.cancellationCode = request.cancellationCode || null; + } + return msg; + } + + // Emit a verification update. Never lets an exception escape - + // this runs inside the SDK's synchronous event emission, where an + // uncaught throw would crash Node-RED. + function emitVerificationUpdate(request, sasShown) { + try { + node.emit("Verification.update", buildVerificationMsg(request, sasShown)); + } catch(e) { + node.error("Failed to process verification update: " + e); + } + } + + node.trackVerificationRequest = function(request) { + let id; + try { id = request.transactionId; } catch(e) { id = undefined; } + if(!id) { + // transactionId is only assigned once the first event is + // sent - wait for it before tracking. + const waitForId = function() { + let tid; + try { tid = request.transactionId; } catch(e) { tid = undefined; } + if(tid) { + request.off(VerificationRequestEvent.Change, waitForId); + node.trackVerificationRequest(request); + } + }; + request.on(VerificationRequestEvent.Change, waitForId); + return; + } + if(node.verificationRequests.has(id)) { + return; // already tracked + } + node.verificationRequests.set(id, request); + + let verifierHooked = false; + const onChange = function() { + try { + // Once a verifier exists, hook its SAS event so the + // emoji/decimal can be surfaced to the flow. + const verifier = request.verifier; + if(verifier && !verifierHooked) { + verifierHooked = true; + verifier.on(VerifierEvent.ShowSas, function(sasCallbacks) { + node.verificationSas.set(id, sasCallbacks); + emitVerificationUpdate(request, true); + }); + } + emitVerificationUpdate(request, false); + if(request.phase === VerificationPhase.Done || request.phase === VerificationPhase.Cancelled) { + request.off(VerificationRequestEvent.Change, onChange); + node.verificationRequests.delete(id); + node.verificationSas.delete(id); + } + } catch(e) { + node.error("Verification request handler error: " + e); + } + }; + request.on(VerificationRequestEvent.Change, onChange); + emitVerificationUpdate(request, false); }; - let msg = { - encrypted : event.isEncrypted(), - redacted : event.isRedacted(), - content : event.getContent(), - type : (event.getContent()['msgtype'] || event.getType()) || null, - payload : (event.getContent()['body'] || event.getContent()) || null, - isDM : isDmRoom(room), - isThread : event.getContent()?.['m.relates_to']?.rel_type === RelationType.Thread, - mentions : event.getContent()["m.mentions"] || null, - userId : event.getSender(), - user : node.matrixClient.getUser(event.getSender()), - topic : event.getRoomId(), - eventId : event.getId(), - event : event, - }; + // Resolve the real homeserver via .well-known discovery so a + // delegating domain (e.g. "example.org") works as the server URL. + const baseUrl = await resolveHomeserverUrl(sdk, node.url); + if(baseUrl !== node.url) { + node.log(`Discovered homeserver ${baseUrl} for ${node.url} via .well-known`); + } - // remove keys from user property that start with an underscore - Object.keys(msg.user).forEach(function (key) { - if (/^_/.test(key)) { - delete msg.user[key]; + let clientOpts = { + baseUrl: baseUrl, + accessToken: node.credentials.accessToken, + store: new MemoryStore({ + localStorage: localStorage, + }), + userId: node.userId, + deviceId: (node.deviceId || getStoredDeviceId(localStorage)) || undefined, + cryptoCallbacks: { + // Supplies the Secure Secret Storage (4S) key to the crypto + // stack once it has been unlocked via the secure-backup + // admin endpoint. Returns null when no key is available. + getSecretStorageKey: async function({ keys }) { + if(node._secretStorageKeyCache) { + const [cachedId, cachedKey] = node._secretStorageKeyCache; + if(keys[cachedId]) { + return [cachedId, cachedKey]; + } + } + return null; + }, + // Caches a newly created 4S key (e.g. after a reset). + cacheSecretStorageKey: function(keyId, keyInfo, key) { + node._secretStorageKeyCache = [keyId, key]; + }, + }, + }; + if(node.e2ee) { + // Provide the legacy (pre-v37 libolm) crypto store so that + // initRustCrypto() can perform a one-time migration of any + // existing crypto state into the Rust crypto store. + clientOpts.cryptoStore = new LocalStorageCryptoStore(localStorage); + } + node.matrixClient = sdk.createClient(clientOpts); + + node.debug(`hasLazyLoadMembersEnabled=${node.matrixClient.hasLazyLoadMembersEnabled()}`); + + // set globally if configured to do so + if(node.globalAccess) { + node.context().global.set('matrixClient["'+node.userId+'"]', node.matrixClient); + } + + node.matrixClient.on(RoomEvent.Timeline, async function(event, room, toStartOfTimeline, removed, data) { + if (toStartOfTimeline) { + node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: paginated result"); + return; // ignore paginated results + } + if (!data || !data.liveEvent) { + node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: old message"); + return; // ignore old message (we only want live events) + } + if(node.initializedAt > event.getDate()) { + node.log("Ignoring" + (event.isEncrypted() ? ' encrypted' : '') +" timeline event [" + (event.getContent()['msgtype'] || event.getType()) + "]: (" + room.name + ") " + event.getId() + " for reason: old message before init"); + return; // skip events that occurred before our client initialized + } + + try { + await node.matrixClient.decryptEventIfNeeded(event); + } catch (error) { + node.error(error); + return; + } + + const isDmRoom = (room) => { + // Find out if this is a direct message room. + let isDM = !!room.getDMInviter(); + const allMembers = room.currentState.getMembers(); + if (!isDM && allMembers.length <= 2) { + // if not a DM, but there are 2 users only + // double check DM (needed because getDMInviter works only if you were invited, not if you invite) + // hence why we check for each member + if (allMembers.some((m) => m.getDMInviter())) { + return true; + } + } + return allMembers.length <= 2 && isDM; + }; + + let msg = { + encrypted : event.isEncrypted(), + redacted : event.isRedacted(), + content : event.getContent(), + type : (event.getContent()['msgtype'] || event.getType()) || null, + payload : (event.getContent()['body'] || event.getContent()) || null, + isDM : isDmRoom(room), + isThread : event.getContent()?.['m.relates_to']?.rel_type === RelationType.Thread, + mentions : event.getContent()["m.mentions"] || null, + userId : event.getSender(), + user : node.matrixClient.getUser(event.getSender()), + topic : event.getRoomId(), + eventId : event.getId(), + event : event, + }; + + // remove keys from user property that start with an underscore + Object.keys(msg.user).forEach(function (key) { + if (/^_/.test(key)) { + delete msg.user[key]; + } + }); + + node.log(`Received ${msg.encrypted ? 'encrypted ' : ''}timeline event [${msg.type}]: (${room.name}) ${event.getSender()} :: ${msg.content.body} ${toStartOfTimeline ? ' [PAGINATED]' : ''}`); + node.emit("Room.timeline", event, room, toStartOfTimeline, removed, data, msg); + }); + + // handle auto-joining rooms + node.matrixClient.on(RoomMemberEvent.Membership, async function(event, member) { + if(node.initializedAt > event.getDate()) { + return; // skip events that occurred before our client initialized + } + + if (member.membership === "invite" && member.userId === node.userId) { + node.log("Got invite to join room " + member.roomId); + if(node.autoAcceptRoomInvites) { + node.matrixClient.joinRoom(member.roomId).then(function() { + node.log("Automatically accepted invitation to join room " + member.roomId); + }).catch(function(e) { + node.warn("Cannot join room (could be from being kicked/banned) " + member.roomId + ": " + e); + }); + } + + let room = node.matrixClient.getRoom(event.getRoomId()); + node.emit("Room.invite", { + type : 'm.room.member', + userId : event.getSender(), + topic : event.getRoomId(), + topicName : (room ? room.name : null) || null, + event : event, + eventId : event.getId(), + }); } }); - node.log(`Received ${msg.encrypted ? 'encrypted ' : ''}timeline event [${msg.type}]: (${room.name}) ${event.getSender()} :: ${msg.content.body} ${toStartOfTimeline ? ' [PAGINATED]' : ''}`); - node.emit("Room.timeline", event, room, toStartOfTimeline, removed, data, msg); - }); - - /** - * Fires when we want to suggest to the user that they restore their megolm keys - * from backup or by cross-signing the device. - * - * @event module:client~MatrixClient#"crypto.suggestKeyRestore" - */ - // node.matrixClient.on("crypto.suggestKeyRestore", function(){ - // - // }); - - // node.matrixClient.on("RoomMember.typing", async function(event, member) { - // let isTyping = member.typing; - // let roomId = member.roomId; - // }); - - // node.matrixClient.on("RoomMember.powerLevel", async function(event, member) { - // let newPowerLevel = member.powerLevel; - // let newNormPowerLevel = member.powerLevelNorm; - // let roomId = member.roomId; - // }); - - // node.matrixClient.on("RoomMember.name", async function(event, member) { - // let newName = member.name; - // let roomId = member.roomId; - // }); - - // handle auto-joining rooms - - node.matrixClient.on(RoomMemberEvent.Membership, async function(event, member) { - if(node.initializedAt > event.getDate()) { - return; // skip events that occurred before our client initialized - } - - if (member.membership === "invite" && member.userId === node.userId) { - node.log("Got invite to join room " + member.roomId); - if(node.autoAcceptRoomInvites) { - node.matrixClient.joinRoom(member.roomId).then(function() { - node.log("Automatically accepted invitation to join room " + member.roomId); - }).catch(function(e) { - node.warn("Cannot join room (could be from being kicked/banned) " + member.roomId + ": " + e); + node.matrixClient.on(ClientEvent.Sync, async function(state, prevState, data) { + node.debug("SYNC [STATE=" + state + "] [PREVSTATE=" + prevState + "]"); + if(prevState === null && state === "PREPARED" ) { + // Occurs when the initial sync is completed first time. + // This involves setting up filters and obtaining push rules. + node.setConnected(true, function(){ + node.log("Matrix client connected"); + }); + } else if(prevState === null && state === "ERROR") { + // Occurs when the initial sync failed first time. + node.setConnected(false, function(){ + node.error("Failed to connect to Matrix server"); + }); + } else if(prevState === "ERROR" && state === "PREPARED") { + // Occurs when the initial sync succeeds + // after previously failing. + node.setConnected(true, function(){ + node.log("Matrix client connected"); + }); + } else if(prevState === "PREPARED" && state === "SYNCING") { + // Occurs immediately after transitioning to PREPARED. + // Starts listening for live updates rather than catching up. + node.setConnected(true, function(){ + node.log("Matrix client connected"); + }); + } else if(prevState === "SYNCING" && state === "RECONNECTING") { + // Occurs when the live update fails. + node.setConnected(false, function(){ + node.error("Connection to Matrix server lost"); + }); + } else if(prevState === "RECONNECTING" && state === "RECONNECTING") { + // Can occur if the update calls continue to fail, + // but the keepalive calls (to /versions) succeed. + node.setConnected(false, function(){ + node.error("Connection to Matrix server lost"); + }); + } else if(prevState === "RECONNECTING" && state === "ERROR") { + // Occurs when the keepalive call also fails + node.setConnected(false, function(){ + node.error("Connection to Matrix server lost"); + }); + } else if(prevState === "ERROR" && state === "SYNCING") { + // Occurs when the client has performed a + // live update after having previously failed. + node.setConnected(true, function(){ + node.log("Matrix client connected"); + }); + } else if(prevState === "ERROR" && state === "ERROR") { + // Occurs when the client has failed to + // keepalive for a second time or more. + node.setConnected(false, function(){ + node.error("Connection to Matrix server lost"); + }); + } else if(prevState === "SYNCING" && state === "SYNCING") { + // Occurs when the client has performed a live update. + // This is called after processing. + node.setConnected(true, function(){ + node.log("Matrix client connected"); + }); + } else if(state === "STOPPED") { + // Occurs once the client has stopped syncing or + // trying to sync after stopClient has been called. + node.setConnected(false, function(){ + node.error("Connection to Matrix server lost"); }); } - - let room = node.matrixClient.getRoom(event.getRoomId()); - node.emit("Room.invite", { - type : 'm.room.member', - userId : event.getSender(), - topic : event.getRoomId(), - topicName : (room ? room.name : null) || null, - event : event, - eventId : event.getId(), - }); - } - }); - - node.matrixClient.on(ClientEvent.Sync, async function(state, prevState, data) { - node.debug("SYNC [STATE=" + state + "] [PREVSTATE=" + prevState + "]"); - if(prevState === null && state === "PREPARED" ) { - // Occurs when the initial sync is completed first time. - // This involves setting up filters and obtaining push rules. - node.setConnected(true, function(){ - node.log("Matrix client connected"); - }); - } else if(prevState === null && state === "ERROR") { - // Occurs when the initial sync failed first time. - node.setConnected(false, function(){ - node.error("Failed to connect to Matrix server", {}); - }); - } else if(prevState === "ERROR" && state === "PREPARED") { - // Occurs when the initial sync succeeds - // after previously failing. - node.setConnected(true, function(){ - node.log("Matrix client connected"); - }); - } else if(prevState === "PREPARED" && state === "SYNCING") { - // Occurs immediately after transitioning to PREPARED. - // Starts listening for live updates rather than catching up. - node.setConnected(true, function(){ - node.log("Matrix client connected"); - }); - } else if(prevState === "SYNCING" && state === "RECONNECTING") { - // Occurs when the live update fails. - node.setConnected(false, function(){ - node.error("Connection to Matrix server lost", {}); - }); - } else if(prevState === "RECONNECTING" && state === "RECONNECTING") { - // Can occur if the update calls continue to fail, - // but the keepalive calls (to /versions) succeed. - node.setConnected(false, function(){ - node.error("Connection to Matrix server lost", {}); - }); - } else if(prevState === "RECONNECTING" && state === "ERROR") { - // Occurs when the keepalive call also fails - node.setConnected(false, function(){ - node.error("Connection to Matrix server lost", {}); - }); - } else if(prevState === "ERROR" && state === "SYNCING") { - // Occurs when the client has performed a - // live update after having previously failed. - node.setConnected(true, function(){ - node.log("Matrix client connected"); - }); - } else if(prevState === "ERROR" && state === "ERROR") { - // Occurs when the client has failed to - // keepalive for a second time or more. - node.setConnected(false, function(){ - node.error("Connection to Matrix server lost", {}); - }); - } else if(prevState === "SYNCING" && state === "SYNCING") { - // Occurs when the client has performed a live update. - // This is called after processing. - node.setConnected(true, function(){ - node.log("Matrix client connected"); - }); - } else if(state === "STOPPED") { - // Occurs once the client has stopped syncing or - // trying to sync after stopClient has been called. - node.setConnected(false, function(){ - node.error("Connection to Matrix server lost", {}); - }); - } - }); + }); - node.matrixClient.on(HttpApiEvent.SessionLoggedOut, async function(errorObj){ - // Example if user auth token incorrect: - // { - // errcode: 'M_UNKNOWN_TOKEN', - // data: { - // errcode: 'M_UNKNOWN_TOKEN', - // error: 'Invalid macaroon passed.', - // soft_logout: false - // }, - // httpStatus: 401 - // } + node.matrixClient.on(HttpApiEvent.SessionLoggedOut, async function(errorObj){ + // Example if user auth token incorrect: + // { + // errcode: 'M_UNKNOWN_TOKEN', + // data: { + // errcode: 'M_UNKNOWN_TOKEN', + // error: 'Invalid macaroon passed.', + // soft_logout: false + // }, + // httpStatus: 401 + // } - node.error("Authentication failure: " + errorObj, {}); - stopClient(); - }); + node.error("Authentication failure: " + errorObj); + stopClient(); + }); - async function run() { - try { - if(node.e2ee){ - node.log("Initializing crypto..."); - await node.matrixClient.initCrypto(); - node.matrixClient.getCrypto().globalBlacklistUnverifiedDevices = false; // prevent errors from unverified devices + // incoming device-verification requests from other users/devices + node.matrixClient.on(CryptoEvent.VerificationRequestReceived, function(request) { + try { + node.log("Received device verification request from " + request.otherUserId); + node.trackVerificationRequest(request); + } catch(e) { + node.error("Failed to handle incoming verification request: " + e); } - node.log("Connecting to Matrix server..."); - await node.matrixClient.startClient({ - initialSyncLimit: node.initialSyncLimit - }); - } catch(error) { - node.error(error, {}); - } - } + }); - // do an authed request and only continue if we don't get an error - // this prevent the matrix client from crashing Node-RED on invalid auth token - (function checkAuthTokenThenStart() { - if(node.matrixClient.clientRunning) { - return; - } - - /** - * We do a /whoami request before starting for a few reasons: - * - validate our auth token - * - make sure auth token belongs to provided node.userId - * - fetch device_id if possible (only available on Synapse >= v1.40.0 under MSC2033) - */ - node.matrixClient.whoami() - .then( - function(data) { - if((typeof data['device_id'] === undefined || !data['device_id']) && !node.deviceId && !getStoredDeviceId(localStorage)) { - node.error("/whoami request did not return device_id. You will need to manually set one in your configuration because this cannot be automatically fetched.", {}); + async function run() { + try { + if(node.e2ee){ + node.log("Initializing crypto..."); + ensureIndexedDBShim(); + // If the device ID has changed (e.g. a new login), the + // persisted crypto store belongs to the old device and + // cannot be loaded - discard it and start fresh. + // Otherwise restore the previously persisted state. + let effectiveDeviceId = node.matrixClient.getDeviceId(), + storedDeviceId = getStoredDeviceId(localStorage); + if(storedDeviceId && effectiveDeviceId && storedDeviceId !== effectiveDeviceId) { + node.warn(`Device ID changed (${storedDeviceId} -> ${effectiveDeviceId}); discarding the encryption store from the old device.`); + await discardCryptoStore(); + } else { + await restoreCryptoStore(cryptoSnapshotPath); } - if('device_id' in data && data['device_id'] && !node.deviceId) { - // if we have no device_id configured lets use the one - // returned by /whoami for this access_token - node.matrixClient.deviceId = data['device_id']; - } - - // make sure our userId matches the access token's - if(data['user_id'].toLowerCase() !== node.userId.toLowerCase()) { - node.error(`User ID provided is ${node.userId} but token belongs to ${data['user_id']}`, {}); - return; - } - run().catch((error) => node.error(error)); - }, - function(err) { - // if the error isn't authentication related retry in a little bit - if(err.code !== "M_UNKNOWN_TOKEN") { - retryStartTimeout = setTimeout(checkAuthTokenThenStart, 15000); - node.error("Auth check failed: " + err, {}); + await node.matrixClient.initRustCrypto({ + useIndexedDB: true, + cryptoDatabasePrefix: cryptoDbPrefix, + }); + let crypto = node.matrixClient.getCrypto(); + if(crypto) { + // Blacklist (refuse to encrypt to) unverified devices only + // when the user has explicitly unticked "Allow unverified + // devices". Default/undefined allows them, as before. + crypto.globalBlacklistUnverifiedDevices = (node.allowUnknownDevices === false); } + // periodically persist crypto state so it survives an unclean shutdown + cryptoSnapshotInterval = setInterval(persistCrypto, 5 * 60 * 1000); } - ) - })(); + node.log("Connecting to Matrix server..."); + await node.matrixClient.startClient({ + initialSyncLimit: node.initialSyncLimit + }); + } catch(error) { + node.error(error); + } + } + + // do an authed request and only continue if we don't get an error + // this prevent the matrix client from crashing Node-RED on invalid auth token + (function checkAuthTokenThenStart() { + if(node.matrixClient.clientRunning) { + return; + } + + /** + * We do a /whoami request before starting for a few reasons: + * - validate our auth token + * - make sure auth token belongs to provided node.userId + * - fetch device_id if possible (only available on Synapse >= v1.40.0 under MSC2033) + */ + node.matrixClient.whoami() + .then( + function(data) { + if((typeof data['device_id'] === undefined || !data['device_id']) && !node.deviceId && !getStoredDeviceId(localStorage)) { + node.error("/whoami request did not return device_id. You will need to manually set one in your configuration because this cannot be automatically fetched."); + } + if('device_id' in data && data['device_id'] && !node.deviceId) { + // if we have no device_id configured lets use the one + // returned by /whoami for this access_token + node.matrixClient.deviceId = data['device_id']; + } + + // make sure our userId matches the access token's + if(data['user_id'].toLowerCase() !== node.userId.toLowerCase()) { + node.error(`User ID provided is ${node.userId} but token belongs to ${data['user_id']}`); + return; + } + run().catch((error) => node.error(error)); + }, + function(err) { + // if the error isn't authentication related retry in a little bit + if(err.code !== "M_UNKNOWN_TOKEN") { + retryStartTimeout = setTimeout(checkAuthTokenThenStart, 15000); + node.error("Auth check failed: " + err); + } + } + ) + })(); + } } } @@ -459,54 +736,201 @@ module.exports = function(RED) { userId: { type: "text", required: true }, accessToken: { type: "text", required: true }, deviceId: { type: "text", required: false }, - url: { type: "text", required: true } + url: { type: "text", required: true }, + password: { type: "password", required: false } } }); RED.httpAdmin.post( "/matrix-chat/login", RED.auth.needsPermission('flows.write'), - function(req, res) { + async function(req, res) { let userId = req.body.userId || undefined, password = req.body.password || undefined, baseUrl = req.body.baseUrl || undefined, deviceId = req.body.deviceId || undefined, displayName = req.body.displayName || undefined; - const matrixClient = sdk.createClient({ - baseUrl: baseUrl, - deviceId: deviceId, - timelineSupport: true, - localTimeoutMs: '30000' - }); + try { + const sdk = await sdkPromise; + // Resolve .well-known delegation so users can enter their domain. + baseUrl = await resolveHomeserverUrl(sdk, baseUrl); + const matrixClient = sdk.createClient({ + baseUrl: baseUrl, + deviceId: deviceId, + timelineSupport: true, + localTimeoutMs: '30000' + }); - matrixClient.timelineSupport = true; + matrixClient.login( + 'm.login.password', { + identifier: { + type: 'm.id.user', + user: userId, + }, + password: password, + initial_device_display_name: displayName + }) + .then( + function(response) { + res.json({ + 'result': 'ok', + 'token': response.access_token, + 'device_id': response.device_id, + 'user_id': response.user_id, + }); + }, + function(err) { + res.json({ + 'result': 'error', + 'message': err + }); + } + ); + } catch(err) { + res.json({ + 'result': 'error', + 'message': err + }); + } + }); - matrixClient.login( - 'm.login.password', { - identifier: { - type: 'm.id.user', - user: userId, - }, - password: password, - initial_device_display_name: displayName - }) - .then( - function(response) { - res.json({ - 'result': 'ok', - 'token': response.access_token, - 'device_id': response.device_id, - 'user_id': response.user_id, - }); - }, - function(err) { - res.json({ - 'result': 'error', - 'message': err - }); + /** + * Interactive Secure Secret Storage (4S) / cross-signing setup for the + * config editor's "Set up secure backup" button. + * + * Secured with the same flows.write permission as the login endpoint, so it + * is not publicly exposed. Operates on the live, connected client of an + * already-deployed server configuration node (identified by req.body.id). + * + * Actions: + * - status : report connection / cross-signing / secret-storage state + * - unlock : unlock existing 4S with a recovery key/passphrase, then set up + * cross-signing for this device + * - reset : create brand new cross-signing keys and secret storage + * (requires the account password); returns the new recovery key + */ + RED.httpAdmin.post( + "/matrix-chat/secure-backup", + RED.auth.needsPermission('flows.write'), + async function(req, res) { + try { + const serverNode = RED.nodes.getNode(req.body.id); + if(!serverNode || !serverNode.matrixClient) { + return res.json({ result: 'error', message: 'Server configuration not found. Save and deploy the server configuration node first.' }); + } + if(typeof serverNode.isConnected !== 'function' || !serverNode.isConnected()) { + return res.json({ result: 'error', message: 'The Matrix client is not connected. Deploy the server configuration and wait for it to connect, then try again.' }); + } + const crypto = serverNode.matrixClient.getCrypto(); + if(!crypto) { + return res.json({ result: 'error', message: 'End-to-end encryption is not enabled on this server configuration.' }); + } + const secretStorage = serverNode.matrixClient.secretStorage; + const action = req.body.action || 'status'; + + if(action === 'status') { + const defaultKeyId = await secretStorage.getDefaultKeyId(); + return res.json({ + result: 'ok', + crossSigningReady: await crypto.isCrossSigningReady(), + secretStorageReady: await crypto.isSecretStorageReady(), + secretStorageExists: !!defaultKeyId, + }); + } + + if(action === 'unlock') { + const cryptoApi = await cryptoApiPromise; + const recoveryInput = String(req.body.recoveryKey || '').trim(); + if(!recoveryInput) { + return res.json({ result: 'error', message: 'A recovery key or passphrase is required.' }); } - ); + const keyId = await secretStorage.getDefaultKeyId(); + if(!keyId) { + return res.json({ result: 'error', message: 'This account has no secure backup to unlock. Use Reset to create one.' }); + } + const stored = await secretStorage.getKey(keyId); + const keyInfo = stored && stored[1]; + if(!keyInfo) { + return res.json({ result: 'error', message: 'Could not read the secure backup key description from the account.' }); + } + + let keyBytes = null; + try { + keyBytes = cryptoApi.decodeRecoveryKey(recoveryInput.replace(/\s+/g, '')); + } catch(e) { /* not a recovery key - fall back to passphrase */ } + if(!keyBytes && keyInfo.passphrase) { + keyBytes = await cryptoApi.deriveRecoveryKeyFromPassphrase( + recoveryInput, keyInfo.passphrase.salt, keyInfo.passphrase.iterations); + } + if(!keyBytes) { + return res.json({ result: 'error', message: 'Could not read that value as a recovery key or passphrase.' }); + } + if(!(await secretStorage.checkKey(keyBytes, keyInfo))) { + return res.json({ result: 'error', message: 'That recovery key / passphrase is not correct.' }); + } + + serverNode._secretStorageKeyCache = [keyId, keyBytes]; + await crypto.bootstrapCrossSigning({ + authUploadDeviceSigningKeys: async function(makeRequest) { + if(req.body.password) { + await makeRequest({ + type: 'm.login.password', + identifier: { type: 'm.id.user', user: serverNode.userId }, + password: req.body.password, + }); + } else { + await makeRequest(null); + } + }, + }); + try { await crypto.checkKeyBackupAndEnable(); } catch(e) { /* best effort */ } + serverNode.log("Secure backup unlocked; cross-signing set up."); + return res.json({ + result: 'ok', + message: 'Secure backup unlocked. Cross-signing is now set up for this bot.', + crossSigningReady: await crypto.isCrossSigningReady(), + }); + } + + if(action === 'reset') { + const password = req.body.password; + if(!password) { + return res.json({ result: 'error', message: 'The account password is required to reset secure backup.' }); + } + const newKey = await crypto.createRecoveryKeyFromPassphrase(); + // Replace secret storage FIRST. This makes the new 4S key + // (whose private key we hold and cache via cacheSecretStorageKey) + // the default before cross-signing is reset. bootstrapCrossSigning + // exports the new signing keys into whatever 4S is current, so if + // the old 4S were still default it would need the old (unknown) + // key and fail with "getSecretStorageKey callback returned falsey". + await crypto.bootstrapSecretStorage({ + setupNewSecretStorage: true, + createSecretStorageKey: async function() { return newKey; }, + }); + await crypto.bootstrapCrossSigning({ + setupNewCrossSigning: true, + authUploadDeviceSigningKeys: async function(makeRequest) { + await makeRequest({ + type: 'm.login.password', + identifier: { type: 'm.id.user', user: serverNode.userId }, + password: password, + }); + }, + }); + serverNode.log("Cross-signing and secure backup were reset."); + return res.json({ + result: 'ok', + message: 'Cross-signing and secure backup have been reset. Store the new recovery key somewhere safe - it is shown only once.', + recoveryKey: newKey.encodedPrivateKey, + }); + } + + return res.json({ result: 'error', message: 'Unknown action: ' + action }); + } catch(error) { + res.json({ result: 'error', message: String(error && error.message || error) }); + } }); function upgradeDirectoryIfNecessary(node, storageDir) { @@ -526,7 +950,7 @@ module.exports = function(RED) { fs.copySync(oldStorageDir, dir); } } catch (err) { - node.error(err, {}); + node.error(err); } }); diff --git a/src/matrix-verification-action.html b/src/matrix-verification-action.html new file mode 100644 index 0000000..7914136 --- /dev/null +++ b/src/matrix-verification-action.html @@ -0,0 +1,126 @@ + + + + + diff --git a/src/matrix-verification-action.js b/src/matrix-verification-action.js new file mode 100644 index 0000000..4688860 --- /dev/null +++ b/src/matrix-verification-action.js @@ -0,0 +1,139 @@ +module.exports = function(RED) { + function MatrixVerificationAction(n) { + RED.nodes.createNode(this, n); + + let node = this; + + this.name = n.name; + this.server = RED.nodes.getNode(n.server); + this.mode = n.mode || "accept"; + + node.status({ fill: "red", shape: "ring", text: "disconnected" }); + + if (!node.server) { + node.error("No configuration node"); + return; + } + node.server.register(node); + + node.server.on("disconnected", function() { + node.status({ fill: "red", shape: "ring", text: "disconnected" }); + }); + node.server.on("connected", function() { + node.status({ fill: "green", shape: "ring", text: "connected" }); + }); + + node.on("input", async function(msg) { + if (!node.server || !node.server.matrixClient) { + msg.error = "No matrix server selected"; + node.error(msg.error, msg); + node.send([null, msg]); + return; + } + + if (!node.server.isConnected()) { + msg.error = "Matrix server connection is currently closed"; + node.error(msg.error, msg); + node.send([null, msg]); + return; + } + + const crypto = node.server.matrixClient.getCrypto(); + if (!crypto) { + msg.error = "End-to-end encryption is not enabled on the Matrix server config"; + node.error(msg.error, msg); + node.send([null, msg]); + return; + } + + // msg.mode overrides the node's configured mode if provided + const mode = msg.mode || node.mode; + + try { + if (mode === "request") { + // Start a new verification request. + // - msg.userId + msg.deviceId : verify a specific device (to-device) + // - msg.userId + msg.topic : verify a user in a DM room + // - otherwise : verify our own other devices + let request; + if (msg.userId && msg.deviceId) { + request = await crypto.requestDeviceVerification(msg.userId, msg.deviceId); + } else if (msg.userId && msg.topic) { + request = await crypto.requestVerificationDM(msg.userId, msg.topic); + } else { + request = await crypto.requestOwnUserVerification(); + } + + if (typeof node.server.trackVerificationRequest === "function") { + node.server.trackVerificationRequest(request); + } + msg.verificationId = request.transactionId; + node.send([msg, null]); + return; + } + + // Every other mode acts on an existing tracked request. + const request = node.server.verificationRequests.get(msg.verificationId); + if (!request) { + throw new Error(`No active verification found for msg.verificationId '${msg.verificationId}'`); + } + + switch (mode) { + case "accept": + await request.accept(); + break; + + case "start": { + // Begin SAS (emoji) verification. The SAS emoji is delivered + // through the matrix-verification node when it becomes ready. + let verifier = request.verifier; + if (!verifier) { + verifier = await request.startVerification("m.sas.v1"); + } + verifier.verify().catch(function(e) { + node.warn("Verification ended: " + e); + }); + break; + } + + case "confirm": { + const sas = node.server.verificationSas.get(msg.verificationId); + if (!sas) { + throw new Error("This verification has no SAS awaiting confirmation"); + } + await sas.confirm(); + break; + } + + case "mismatch": { + const sas = node.server.verificationSas.get(msg.verificationId); + if (!sas) { + throw new Error("This verification has no SAS awaiting confirmation"); + } + sas.mismatch(); + break; + } + + case "cancel": + await request.cancel(); + break; + + default: + throw new Error("Unknown verification action mode: " + mode); + } + + msg.verificationId = request.transactionId; + node.send([msg, null]); + } catch (e) { + msg.error = String(e && e.message || e); + node.error("Verification action failed: " + msg.error, msg); + node.send([null, msg]); + } + }); + + node.on("close", function() { + node.server.deregister(node); + }); + } + RED.nodes.registerType("matrix-verification-action", MatrixVerificationAction); +} diff --git a/src/matrix-verification.html b/src/matrix-verification.html new file mode 100644 index 0000000..93f550e --- /dev/null +++ b/src/matrix-verification.html @@ -0,0 +1,199 @@ + + + + + diff --git a/src/matrix-verification.js b/src/matrix-verification.js new file mode 100644 index 0000000..839e6f2 --- /dev/null +++ b/src/matrix-verification.js @@ -0,0 +1,113 @@ +module.exports = function(RED) { + function MatrixVerification(n) { + RED.nodes.createNode(this, n); + + let node = this; + + this.name = n.name; + this.server = RED.nodes.getNode(n.server); + + // Phase filter - emit only the ticked phases. Undefined (config saved + // before these options existed) is treated as ticked, so old nodes + // keep emitting every phase. + this.phases = { + requested: n.phaseRequested !== false, + ready: n.phaseReady !== false, + started: n.phaseStarted !== false, + sas: n.phaseSas !== false, + done: n.phaseDone !== false, + cancelled: n.phaseCancelled !== false, + }; + this.initiatedBy = n.initiatedBy || 'any'; // any | me | notme + this.verificationType = n.verificationType || 'any'; // any | room | device + this.selfVerification = n.selfVerification || 'any'; // any | self | others + this.userFilter = (n.userFilter || '').split(',') + .map(function(s){ return s.trim().toLowerCase(); }) + .filter(Boolean); + this.roomFilter = (n.roomFilter || '').split(',') + .map(function(s){ return s.trim(); }) + .filter(Boolean); + + node.status({ fill: "red", shape: "ring", text: "disconnected" }); + + if (!node.server) { + node.error("No configuration node"); + return; + } + node.server.register(node); + + // Returns true if a verification update message passes every configured + // filter. All filters AND-combine; each defaults to "pass everything". + function passesFilters(m) { + // phase + if ((m.phase in node.phases) && !node.phases[m.phase]) { + return false; + } + // initiated by + if (node.initiatedBy === 'me' && !m.initiatedByMe) { + return false; + } + if (node.initiatedBy === 'notme' && m.initiatedByMe) { + return false; + } + // verification type - room verifications carry a roomId (msg.topic), + // to-device verifications do not + if (node.verificationType === 'room' && !m.topic) { + return false; + } + if (node.verificationType === 'device' && m.topic) { + return false; + } + // self-verification (the other party is one of the bot's own devices) + if (node.selfVerification === 'self' && !m.isSelfVerification) { + return false; + } + if (node.selfVerification === 'others' && m.isSelfVerification) { + return false; + } + // user id allowlist + if (node.userFilter.length && + (!m.userId || node.userFilter.indexOf(m.userId.toLowerCase()) === -1)) { + return false; + } + // room id filter - only constrains room verifications; device + // verifications have no room and are not affected + if (node.roomFilter.length && m.topic && + node.roomFilter.indexOf(m.topic) === -1) { + return false; + } + return true; + } + + const onConnected = function() { + node.status({ fill: "green", shape: "ring", text: "connected" }); + }; + const onDisconnected = function() { + node.status({ fill: "red", shape: "ring", text: "disconnected" }); + }; + const onVerificationUpdate = function(verificationMsg) { + if (!passesFilters(verificationMsg)) { + return; + } + node.status({ fill: "blue", shape: "dot", text: verificationMsg.phase }); + // clone so multiple verification nodes don't share/mutate one object + node.send(RED.util.cloneMessage(verificationMsg)); + }; + + node.server.on("connected", onConnected); + node.server.on("disconnected", onDisconnected); + node.server.on("Verification.update", onVerificationUpdate); + + if (node.server.isConnected && node.server.isConnected()) { + onConnected(); + } + + node.on("close", function() { + node.server.removeListener("connected", onConnected); + node.server.removeListener("disconnected", onDisconnected); + node.server.removeListener("Verification.update", onVerificationUpdate); + node.server.deregister(node); + }); + } + RED.nodes.registerType("matrix-verification", MatrixVerification); +}