diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/manifests/client.yml b/manifests/client.yml new file mode 100644 index 0000000..a22284d --- /dev/null +++ b/manifests/client.yml @@ -0,0 +1,10 @@ +apiVersion: 'backbone.mortenolsen.pro/v1' +kind: Client +metadata: + name: test + namespace: prod +spec: + statements: + - effect: allow + resources: ['*'] + actions: ['*'] diff --git a/package.json b/package.json index 797c3a3..14ca8a4 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "eslint-config-prettier": "10.1.8", "eslint-plugin-import": "2.32.0", "eslint-plugin-prettier": "5.5.4", + "get-port": "^7.1.0", + "mqtt": "^5.14.1", "prettier": "3.6.2", "typescript": "5.9.3", "typescript-eslint": "8.46.1", @@ -38,8 +40,12 @@ "#root/*": "./src/*" }, "dependencies": { + "@fastify/websocket": "^11.2.0", + "@kubernetes/client-node": "^1.4.0", "aedes": "^0.51.3", "aedes-persistence": "^10.2.2", + "ajv": "^8.17.1", + "fastify": "^5.6.1", "jsonwebtoken": "^9.0.2", "micromatch": "^4.0.8", "ws": "^8.18.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca247d0..0713e64 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,12 +8,24 @@ importers: .: dependencies: + '@fastify/websocket': + specifier: ^11.2.0 + version: 11.2.0 + '@kubernetes/client-node': + specifier: ^1.4.0 + version: 1.4.0 aedes: specifier: ^0.51.3 version: 0.51.3 aedes-persistence: specifier: ^10.2.2 version: 10.2.2 + ajv: + specifier: ^8.17.1 + version: 8.17.1 + fastify: + specifier: ^5.6.1 + version: 5.6.1 jsonwebtoken: specifier: ^9.0.2 version: 9.0.2 @@ -63,6 +75,12 @@ importers: eslint-plugin-prettier: specifier: 5.5.4 version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.37.0))(eslint@9.37.0)(prettier@3.6.2) + get-port: + specifier: ^7.1.0 + version: 7.1.0 + mqtt: + specifier: ^5.14.1 + version: 5.14.1 prettier: specifier: 3.6.2 version: 3.6.2 @@ -305,6 +323,27 @@ packages: resolution: {integrity: sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fastify/ajv-compiler@4.0.3': + resolution: {integrity: sha512-BlVD6YGUTDEl0b5B8TDrvl7JkFNc6LOSybeMB+/bIDA0xaJlBUTeBaGLgjvaviLRNAcBMIXDCHaxOJ8LdlzEKw==} + + '@fastify/error@4.2.0': + resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==} + + '@fastify/fast-json-stringify-compiler@5.0.3': + resolution: {integrity: sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==} + + '@fastify/forwarded@3.0.1': + resolution: {integrity: sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==} + + '@fastify/merge-json-schemas@0.2.1': + resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==} + + '@fastify/proxy-addr@5.1.0': + resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==} + + '@fastify/websocket@11.2.0': + resolution: {integrity: sha512-3HrDPbAG1CzUCqnslgJxppvzaAZffieOVbLp1DAy1huCSynUWPifSvfdEDUR8HlJLp3sp1A36uOM2tJogADS8w==} + '@gwhitney/detect-indent@7.0.1': resolution: {integrity: sha512-7bQW+gkKa2kKZPeJf6+c6gFK9ARxQfn+FKy9ScTBppyKRWH2KzsmweXUoklqeEiHiNVWaeP5csIdsNq6w7QhzA==} engines: {node: '>=12.20'} @@ -346,6 +385,21 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@jsep-plugin/assignment@1.3.0': + resolution: {integrity: sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==} + engines: {node: '>= 10.16.0'} + peerDependencies: + jsep: ^0.4.0||^1.0.0 + + '@jsep-plugin/regex@1.0.4': + resolution: {integrity: sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==} + engines: {node: '>= 10.16.0'} + peerDependencies: + jsep: ^0.4.0||^1.0.0 + + '@kubernetes/client-node@1.4.0': + resolution: {integrity: sha512-Zge3YvF7DJi264dU1b3wb/GmzR99JhUpqTvp+VGHfwZT+g7EOOYNScDJNZwXy9cszyIGPIs0VHr+kk8e95qqrA==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -632,6 +686,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -647,6 +704,9 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node-fetch@2.6.13': + resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} + '@types/node@24.7.2': resolution: {integrity: sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==} @@ -656,6 +716,9 @@ packages: '@types/ssri@7.1.5': resolution: {integrity: sha512-odD/56S3B51liILSk5aXJlnYt99S6Rt9EFDDqGtJM26rKHApHcwyU/UoYHrzKkdkHMAIquGWCuHtQTbes+FRQw==} + '@types/stream-buffers@3.0.7': + resolution: {integrity: sha512-azOCy05sXVXrO+qklf0c/B07H/oHaIuDDAiHPVwlk3A9Ek+ksHyTeMajLZl3r76FxpPpxem//4Te61G1iW3Giw==} + '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} @@ -765,6 +828,9 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + abstract-logging@2.0.1: + resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -791,9 +857,24 @@ packages: resolution: {integrity: sha512-aQfiI9w3RbqnowNCdcGMmCtxBFXN9bhJFcuZm24U5/NU06V3MCl42jWK2GUnu8rOypR2Ahi/aEcgq3w7CMcycg==} engines: {node: '>=16'} + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} @@ -867,13 +948,69 @@ packages: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} + avvio@9.1.0: + resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==} + + b4a@1.7.3: + resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bare-events@2.8.0: + resolution: {integrity: sha512-AOhh6Bg5QmFIXdViHbMc2tLDsBIRxdkIaIddPslJF9Z5De3APBScuqGP2uThXnIpqFrgoxMNC6km7uXNIMLHXA==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + bare-fs@4.4.10: + resolution: {integrity: sha512-arqVF+xX/rJHwrONZaSPhlzleT2gXwVs9rsAe1p1mIVwWZI2A76/raio+KwwxfWMO8oV9Wo90EaUkS2QwVmy4w==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-os@3.6.2: + resolution: {integrity: sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==} + engines: {bare: '>=1.14.0'} + + bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + + bare-stream@2.7.0: + resolution: {integrity: sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==} + peerDependencies: + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + bare-events: + optional: true + + bare-url@2.3.0: + resolution: {integrity: sha512-c+RCqMSZbkz97Mw1LWR0gcOqwK82oyYKfLoHJ8k13ybi1+I80ffdDzUy0TdAburdrR/kI0/VuN8YgEnJqX+Nyw==} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -904,9 +1041,15 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + broker-factory@3.1.10: + resolution: {integrity: sha512-BzqK5GYFhvVFvO13uzPN0SCiOsOQuhMUbsGvTXDJMA2/N4GvIlFdxEuueE+60Zk841bBU5G3+fl2cqYEo0wgGg==} + buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -984,12 +1127,27 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commist@3.2.0: + resolution: {integrity: sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1048,6 +1206,14 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1060,6 +1226,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + duplexify@4.1.3: + resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -1221,6 +1390,9 @@ packages: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -1233,12 +1405,18 @@ packages: resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} + fast-decode-uri-component@1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -1246,9 +1424,15 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + fast-json-stringify@6.1.1: + resolution: {integrity: sha512-DbgptncYEXZqDUOEl4krff4mUiVrTZZVI7BBrQR/T3BqMj/eM1flTC1Uk2uUoLcWCxjT95xKulV/Lc6hhOZsBQ==} + fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-querystring@1.1.2: + resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} + fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} @@ -1256,10 +1440,23 @@ packages: resolution: {integrity: sha512-7OnTFAVPefgw2eBJ1xj2PGGR9FwYzSUso9decayHgCDX4sJkHLdcsYTytTg+tYv+wKF3U8gJuSBz2jJpQV4u/g==} engines: {node: '>=16.1.0'} + fast-unique-numbers@9.0.24: + resolution: {integrity: sha512-Dv0BYn4waOWse94j16rsZ5w/0zoaCa74O3q6IZjMqaXbtT92Q+Sb6pPk+phGzD8Xh+nueQmSRI3tSCaHKidzKw==} + engines: {node: '>=18.2.0'} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fastfall@1.5.1: resolution: {integrity: sha512-KH6p+Z8AKPXnmA7+Iz2Lh8ARCMr+8WNPVludm1LGkZoD2MjY6LVnRMtTKhkdzI+jr0RzQWXKzKyBJm1zoHEL4Q==} engines: {node: '>=0.10.0'} + fastify-plugin@5.1.0: + resolution: {integrity: sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==} + + fastify@5.6.1: + resolution: {integrity: sha512-WjjlOciBF0K8pDUPZoGPhqhKrQJ02I8DKaDIfO51EL0kbSMwQFl85cRwhOvmSDWoukNOdTo27gLN549pLCcH7Q==} + fastparallel@2.4.1: resolution: {integrity: sha512-qUmhxPgNHmvRjZKBFUNI0oZuuH9OlSIOXmJ98lhKPxMZZ7zS/Fi0wRHOihDSz0R1YiIOjxzOY4bq65YTcdBi2Q==} @@ -1286,6 +1483,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-my-way@9.3.0: + resolution: {integrity: sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==} + engines: {node: '>=20'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1305,6 +1506,10 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1328,6 +1533,10 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} + get-port@7.1.0: + resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==} + engines: {node: '>=16'} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -1403,6 +1612,13 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + + hpagent@1.2.0: + resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==} + engines: {node: '>=14'} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -1449,6 +1665,14 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + ip-address@10.0.1: + resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} + engines: {node: '>= 12'} + + ipaddr.js@2.2.0: + resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} + engines: {node: '>= 10'} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -1578,6 +1802,11 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isomorphic-ws@5.0.0: + resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==} + peerDependencies: + ws: '*' + istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -1597,6 +1826,12 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jose@6.1.0: + resolution: {integrity: sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==} + + js-sdsl@4.3.0: + resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1607,15 +1842,25 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsep@1.4.0: + resolution: {integrity: sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==} + engines: {node: '>= 10.16.0'} + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-ref-resolver@3.0.0: + resolution: {integrity: sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -1631,6 +1876,11 @@ packages: engines: {node: '>=6'} hasBin: true + jsonpath-plus@10.3.0: + resolution: {integrity: sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==} + engines: {node: '>=18.0.0'} + hasBin: true + jsonwebtoken@9.0.2: resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} engines: {node: '>=12', npm: '>=6'} @@ -1648,6 +1898,9 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + light-my-request@6.6.0: + resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -1726,6 +1979,14 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -1758,6 +2019,11 @@ packages: mqtt-packet@9.0.2: resolution: {integrity: sha512-MvIY0B8/qjq7bKxdN1eD+nrljoeaai+qjLJgfRn3TiMuz0pamsIWY2bFODPZMSNmabsLANXsLl4EMoWvlaTZWA==} + mqtt@5.14.1: + resolution: {integrity: sha512-NxkPxE70Uq3Ph7goefQa7ggSsVzHrayCD0OyxlJgITN/EbzlZN+JEPmaAZdxP1LsIT5FamDyILoQTF72W7Nnbw==} + engines: {node: '>=16.0.0'} + hasBin: true + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1774,6 +2040,15 @@ packages: engines: {node: '>=10'} hasBin: true + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -1785,6 +2060,12 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + number-allocator@1.0.14: + resolution: {integrity: sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==} + + oauth4webapi@3.8.2: + resolution: {integrity: sha512-FzZZ+bht5X0FKe7Mwz3DAVAmlH1BV5blSak/lHMBKz0/EBMhX6B10GlQYI51+oRp8ObJaX0g6pXrAxZh5s8rjw==} + object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} @@ -1809,6 +2090,10 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -1816,6 +2101,9 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + openid-client@6.8.1: + resolution: {integrity: sha512-VoYT6enBo6Vj2j3Q5Ec0AezS+9YGzQo1f5Xc42lreMGlfP4ljiXPKVDvCADh+XHCV/bqPu/wWSiCVXbJKvrODw==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1903,6 +2191,16 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + + pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + + pino@9.13.1: + resolution: {integrity: sha512-Szuj+ViDTjKPQYiKumGmEn3frdl+ZPSdosHyt9SnUevFosOkMY2b7ipxlEctNKPmMD/VibeBI+ZcZCJK+4DPuw==} + hasBin: true + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -1938,6 +2236,12 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process-warning@4.0.1: + resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} + + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} @@ -1945,6 +2249,9 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -1960,6 +2267,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + quick-lru@4.0.1: resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} engines: {node: '>=8'} @@ -1980,6 +2290,10 @@ packages: resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + realpath-missing@1.1.0: resolution: {integrity: sha512-wnWtnywepjg/eHIgWR97R7UuM5i+qHLA195qdN9UPKvcMqfn60+67S8sPPW3vDlSEfYHoFkKU8IvpCNty3zQvQ==} engines: {node: '>=10'} @@ -1992,6 +2306,10 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2001,6 +2319,10 @@ packages: engines: {node: '>= 0.4'} hasBin: true + ret@0.5.0: + resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} + engines: {node: '>=10'} + retimer@4.0.0: resolution: {integrity: sha512-fZIVtvbOsQsxNSDhpdPOX4lx5Ss2ni+S72AUBitARpFhtA3UzrAjQ6gDtypB2/+l7L+1VQgAgpvAKY66mElH0w==} @@ -2008,6 +2330,12 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rfc4648@1.5.4: + resolution: {integrity: sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + right-pad@1.1.1: resolution: {integrity: sha512-eHfYN/4Pds8z4/LnF1LtZSQvWcU9HHU2A7iYtARpFO2fQqt2TP1vHCRTdkO9si7Zg3glo2qh1vgAmyDBO5FGRQ==} engines: {node: '>= 0.10'} @@ -2043,6 +2371,16 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safe-regex2@5.0.0: + resolution: {integrity: sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -2052,6 +2390,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -2098,6 +2439,24 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + slow-redact@0.3.2: + resolution: {integrity: sha512-MseHyi2+E/hBRqdOi5COy6wZ7j7DxXRz9NkseavNYSvvWC06D8a5cidVZX3tcG5eCW3NIyVU4zT63hw0Q486jw==} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + sort-keys@4.2.0: resolution: {integrity: sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg==} engines: {node: '>=8'} @@ -2113,6 +2472,10 @@ packages: split2@3.2.2: resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -2126,6 +2489,16 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + stream-buffers@3.0.3: + resolution: {integrity: sha512-pqMqwQCso0PBJt2PQmDO0cFj0lyqmiwOMiMSkVtRokl7e+ZTRYgDHKnuZNbqjiJXgsg4nuqtD/zxuo9KqTp0Yw==} + engines: {node: '>= 0.10.0'} + + stream-shift@1.0.3: + resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} + + streamx@2.23.0: + resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} + string-length@4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} engines: {node: '>=10'} @@ -2195,10 +2568,22 @@ packages: resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} engines: {node: ^14.18.0 || >=16.0.0} + tar-fs@3.1.1: + resolution: {integrity: sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==} + + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + test-exclude@7.0.1: resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} engines: {node: '>=18'} + text-decoder@1.2.3: + resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} @@ -2228,6 +2613,13 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toad-cache@3.7.0: + resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} + engines: {node: '>=12'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -2268,6 +2660,9 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + typescript-eslint@8.46.1: resolution: {integrity: sha512-VHgijW803JafdSsDO8I761r3SHrgk4T00IdyQ+/UsthtgPRsBWQLqoSxOolxTpxRKi1kGXK0bSz4CoAc9ObqJA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2384,6 +2779,12 @@ packages: wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -2423,15 +2824,27 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + worker-factory@7.0.46: + resolution: {integrity: sha512-Sr1hq2FMgNa04UVhYQacsw+i58BtMimzDb4+CqYphZ97OfefRpURu0UZ+JxMr/H36VVJBfuVkxTK7MytsanC3w==} + worker-timers-broker@6.1.8: resolution: {integrity: sha512-FUCJu9jlK3A8WqLTKXM9E6kAmI/dR1vAJ8dHYLMisLNB/n3GuaFIjJ7pn16ZcD1zCOf7P6H62lWIEBi+yz/zQQ==} + worker-timers-broker@8.0.11: + resolution: {integrity: sha512-uwhxKru8BI9m2tsogxr2fB6POZ8LB2xH+Pu3R0mvQnAZLPgLD6K3IX4LNKPTEgTJ/j5VsuQPB+gLI1NBNKkPlg==} + worker-timers-worker@7.0.71: resolution: {integrity: sha512-ks/5YKwZsto1c2vmljroppOKCivB/ma97g9y77MAAz2TBBjPPgpoOiS1qYQKIgvGTr2QYPT3XhJWIB6Rj2MVPQ==} + worker-timers-worker@9.0.11: + resolution: {integrity: sha512-pArb5xtgHWImYpXhjg1OFv7JFG0ubmccb73TFoXHXjG830fFj+16N57q9YeBnZX52dn+itRrMoJZ9HaZBVzDaA==} + worker-timers@7.1.8: resolution: {integrity: sha512-R54psRKYVLuzff7c1OTFcq/4Hue5Vlz4bFtNEIarpSiCYhpifHU3aIQI29S84o1j87ePCYqbmEJPqwBTf+3sfw==} + worker-timers@8.0.25: + resolution: {integrity: sha512-X7Z5dmM6PlrEnaadtFQOyXHGD/IysPA3HZzaC2koqsU1VI+RvyGmjiiLiUBQixK8PH5R7ilkOzZupWskNRaXmA==} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -2628,6 +3041,38 @@ snapshots: '@eslint/core': 0.16.0 levn: 0.4.1 + '@fastify/ajv-compiler@4.0.3': + dependencies: + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + fast-uri: 3.1.0 + + '@fastify/error@4.2.0': {} + + '@fastify/fast-json-stringify-compiler@5.0.3': + dependencies: + fast-json-stringify: 6.1.1 + + '@fastify/forwarded@3.0.1': {} + + '@fastify/merge-json-schemas@0.2.1': + dependencies: + dequal: 2.0.3 + + '@fastify/proxy-addr@5.1.0': + dependencies: + '@fastify/forwarded': 3.0.1 + ipaddr.js: 2.2.0 + + '@fastify/websocket@11.2.0': + dependencies: + duplexify: 4.1.3 + fastify-plugin: 5.1.0 + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@gwhitney/detect-indent@7.0.1': {} '@humanfs/core@0.19.1': {} @@ -2666,6 +3111,41 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@jsep-plugin/assignment@1.3.0(jsep@1.4.0)': + dependencies: + jsep: 1.4.0 + + '@jsep-plugin/regex@1.0.4(jsep@1.4.0)': + dependencies: + jsep: 1.4.0 + + '@kubernetes/client-node@1.4.0': + dependencies: + '@types/js-yaml': 4.0.9 + '@types/node': 24.7.2 + '@types/node-fetch': 2.6.13 + '@types/stream-buffers': 3.0.7 + form-data: 4.0.4 + hpagent: 1.2.0 + isomorphic-ws: 5.0.0(ws@8.18.3) + js-yaml: 4.1.0 + jsonpath-plus: 10.3.0 + node-fetch: 2.7.0 + openid-client: 6.8.1 + rfc4648: 1.5.4 + socks-proxy-agent: 8.0.5 + stream-buffers: 3.0.3 + tar-fs: 3.1.1 + ws: 8.18.3 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - bufferutil + - encoding + - react-native-b4a + - supports-color + - utf-8-validate + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2995,6 +3475,8 @@ snapshots: '@types/estree@1.0.8': {} + '@types/js-yaml@4.0.9': {} + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -3010,6 +3492,11 @@ snapshots: '@types/ms@2.1.0': {} + '@types/node-fetch@2.6.13': + dependencies: + '@types/node': 24.7.2 + form-data: 4.0.4 + '@types/node@24.7.2': dependencies: undici-types: 7.14.0 @@ -3022,6 +3509,10 @@ snapshots: dependencies: '@types/node': 24.7.2 + '@types/stream-buffers@3.0.7': + dependencies: + '@types/node': 24.7.2 + '@types/ws@8.18.1': dependencies: '@types/node': 24.7.2 @@ -3188,6 +3679,8 @@ snapshots: dependencies: event-target-shim: 5.0.1 + abstract-logging@2.0.1: {} + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -3231,6 +3724,12 @@ snapshots: transitivePeerDependencies: - supports-color + agent-base@7.1.4: {} + + ajv-formats@3.0.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -3238,6 +3737,13 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + ansi-align@3.0.1: dependencies: string-width: 4.2.3 @@ -3331,12 +3837,60 @@ snapshots: async-function@1.0.0: {} + asynckit@0.4.0: {} + + atomic-sleep@1.0.0: {} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 + avvio@9.1.0: + dependencies: + '@fastify/error': 4.2.0 + fastq: 1.19.1 + + b4a@1.7.3: {} + balanced-match@1.0.2: {} + bare-events@2.8.0: {} + + bare-fs@4.4.10: + dependencies: + bare-events: 2.8.0 + bare-path: 3.0.0 + bare-stream: 2.7.0(bare-events@2.8.0) + bare-url: 2.3.0 + fast-fifo: 1.3.2 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + optional: true + + bare-os@3.6.2: + optional: true + + bare-path@3.0.0: + dependencies: + bare-os: 3.6.2 + optional: true + + bare-stream@2.7.0(bare-events@2.8.0): + dependencies: + streamx: 2.23.0 + optionalDependencies: + bare-events: 2.8.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + optional: true + + bare-url@2.3.0: + dependencies: + bare-path: 3.0.0 + optional: true + base64-js@1.5.1: {} better-path-resolve@1.0.0: @@ -3385,8 +3939,17 @@ snapshots: dependencies: fill-range: 7.1.1 + broker-factory@3.1.10: + dependencies: + '@babel/runtime': 7.28.4 + fast-unique-numbers: 9.0.24 + tslib: 2.8.1 + worker-factory: 7.0.46 + buffer-equal-constant-time@1.0.1: {} + buffer-from@1.1.2: {} + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -3464,13 +4027,28 @@ snapshots: color-name@1.1.4: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commist@3.2.0: {} + concat-map@0.0.1: {} + concat-stream@2.0.0: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + config-chain@1.1.13: dependencies: ini: 1.3.8 proto-list: 1.2.4 + cookie@1.0.2: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -3527,6 +4105,10 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + delayed-stream@1.0.0: {} + + dequal@2.0.3: {} + detect-libc@2.1.2: {} doctrine@2.1.0: @@ -3539,6 +4121,13 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + duplexify@4.1.3: + dependencies: + end-of-stream: 1.4.5 + inherits: 2.0.4 + readable-stream: 3.6.2 + stream-shift: 1.0.3 + eastasianwidth@0.2.0: {} ecdsa-sig-formatter@1.0.11: @@ -3805,6 +4394,12 @@ snapshots: event-target-shim@5.0.1: {} + events-universal@1.0.1: + dependencies: + bare-events: 2.8.0 + transitivePeerDependencies: + - bare-abort-controller + events@3.3.0: {} execa@5.1.1: @@ -3821,10 +4416,14 @@ snapshots: expect-type@1.2.2: {} + fast-decode-uri-component@1.0.1: {} + fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} + fast-fifo@1.3.2: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3835,8 +4434,21 @@ snapshots: fast-json-stable-stringify@2.1.0: {} + fast-json-stringify@6.1.1: + dependencies: + '@fastify/merge-json-schemas': 0.2.1 + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + fast-uri: 3.1.0 + json-schema-ref-resolver: 3.0.0 + rfdc: 1.4.1 + fast-levenshtein@2.0.6: {} + fast-querystring@1.1.2: + dependencies: + fast-decode-uri-component: 1.0.1 + fast-safe-stringify@2.1.1: {} fast-unique-numbers@8.0.13: @@ -3844,10 +4456,37 @@ snapshots: '@babel/runtime': 7.28.4 tslib: 2.8.1 + fast-unique-numbers@9.0.24: + dependencies: + '@babel/runtime': 7.28.4 + tslib: 2.8.1 + + fast-uri@3.1.0: {} + fastfall@1.5.1: dependencies: reusify: 1.1.0 + fastify-plugin@5.1.0: {} + + fastify@5.6.1: + dependencies: + '@fastify/ajv-compiler': 4.0.3 + '@fastify/error': 4.2.0 + '@fastify/fast-json-stringify-compiler': 5.0.3 + '@fastify/proxy-addr': 5.1.0 + abstract-logging: 2.0.1 + avvio: 9.1.0 + fast-json-stringify: 6.1.1 + find-my-way: 9.3.0 + light-my-request: 6.6.0 + pino: 9.13.1 + process-warning: 5.0.0 + rfdc: 1.4.1 + secure-json-parse: 4.1.0 + semver: 7.7.3 + toad-cache: 3.7.0 + fastparallel@2.4.1: dependencies: reusify: 1.1.0 @@ -3871,6 +4510,12 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-my-way@9.3.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.2 + safe-regex2: 5.0.0 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -3892,6 +4537,14 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + fsevents@2.3.3: optional: true @@ -3923,6 +4576,8 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-port@7.1.0: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -3995,6 +4650,10 @@ snapshots: dependencies: function-bind: 1.1.2 + help-me@5.0.0: {} + + hpagent@1.2.0: {} + html-escaper@2.0.2: {} human-signals@2.1.0: {} @@ -4032,6 +4691,10 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + ip-address@10.0.1: {} + + ipaddr.js@2.2.0: {} + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -4158,6 +4821,10 @@ snapshots: isexe@2.0.0: {} + isomorphic-ws@5.0.0(ws@8.18.3): + dependencies: + ws: 8.18.3 + istanbul-lib-coverage@3.2.2: {} istanbul-lib-report@3.0.1: @@ -4185,6 +4852,10 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jose@6.1.0: {} + + js-sdsl@4.3.0: {} + js-tokens@4.0.0: {} js-tokens@9.0.1: {} @@ -4193,12 +4864,20 @@ snapshots: dependencies: argparse: 2.0.1 + jsep@1.4.0: {} + json-buffer@3.0.1: {} json-parse-even-better-errors@2.3.1: {} + json-schema-ref-resolver@3.0.0: + dependencies: + dequal: 2.0.3 + json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json-stringify-safe@5.0.1: {} @@ -4209,6 +4888,12 @@ snapshots: json5@2.2.3: {} + jsonpath-plus@10.3.0: + dependencies: + '@jsep-plugin/assignment': 1.3.0(jsep@1.4.0) + '@jsep-plugin/regex': 1.0.4(jsep@1.4.0) + jsep: 1.4.0 + jsonwebtoken@9.0.2: dependencies: jws: 3.2.2 @@ -4242,6 +4927,12 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + light-my-request@6.6.0: + dependencies: + cookie: 1.0.2 + process-warning: 4.0.1 + set-cookie-parser: 2.7.1 + lines-and-columns@1.2.4: {} load-json-file@6.2.0: @@ -4311,6 +5002,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + mimic-fn@2.1.0: {} mimic-fn@3.1.0: {} @@ -4348,6 +5045,29 @@ snapshots: transitivePeerDependencies: - supports-color + mqtt@5.14.1: + dependencies: + '@types/readable-stream': 4.0.21 + '@types/ws': 8.18.1 + commist: 3.2.0 + concat-stream: 2.0.0 + debug: 4.4.3 + help-me: 5.0.0 + lru-cache: 10.4.3 + minimist: 1.2.8 + mqtt-packet: 9.0.2 + number-allocator: 1.0.14 + readable-stream: 4.7.0 + rfdc: 1.4.1 + socks: 2.8.7 + split2: 4.2.0 + worker-timers: 8.0.25 + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + ms@2.1.3: {} nanoid@3.3.11: {} @@ -4362,6 +5082,10 @@ snapshots: split2: 3.2.2 through2: 4.0.2 + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + normalize-path@3.0.0: {} normalize-registry-url@2.0.0: {} @@ -4370,6 +5094,15 @@ snapshots: dependencies: path-key: 3.1.1 + number-allocator@1.0.14: + dependencies: + debug: 4.4.3 + js-sdsl: 4.3.0 + transitivePeerDependencies: + - supports-color + + oauth4webapi@3.8.2: {} + object-inspect@1.13.4: {} object-keys@1.1.1: {} @@ -4403,6 +5136,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + on-exit-leak-free@2.1.2: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -4411,6 +5146,11 @@ snapshots: dependencies: mimic-fn: 2.1.0 + openid-client@6.8.1: + dependencies: + jose: 6.1.0 + oauth4webapi: 3.8.2 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -4486,6 +5226,26 @@ snapshots: picomatch@4.0.3: {} + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.0.0: {} + + pino@9.13.1: + dependencies: + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.0.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + slow-redact: 0.3.2 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 + possible-typed-array-names@1.1.0: {} postcss@8.5.6: @@ -4512,10 +5272,19 @@ snapshots: process-nextick-args@2.0.1: {} + process-warning@4.0.1: {} + + process-warning@5.0.0: {} + process@0.11.10: {} proto-list@1.2.4: {} + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode@2.3.1: {} qlobber@7.0.1: {} @@ -4524,6 +5293,8 @@ snapshots: queue-microtask@1.2.3: {} + quick-format-unescaped@4.0.4: {} + quick-lru@4.0.1: {} read-ini-file@4.0.0: @@ -4550,6 +5321,8 @@ snapshots: process: 0.11.10 string_decoder: 1.3.0 + real-require@0.2.0: {} + realpath-missing@1.1.0: {} reflect.getprototypeof@1.0.10: @@ -4572,6 +5345,8 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + require-from-string@2.0.2: {} + resolve-from@4.0.0: {} resolve@1.22.10: @@ -4580,12 +5355,18 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + ret@0.5.0: {} + retimer@4.0.0: dependencies: worker-timers: 7.1.8 reusify@1.1.0: {} + rfc4648@1.5.4: {} + + rfdc@1.4.1: {} + right-pad@1.1.1: {} rollup@4.52.4: @@ -4651,10 +5432,20 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safe-regex2@5.0.0: + dependencies: + ret: 0.5.0 + + safe-stable-stringify@2.5.0: {} + + secure-json-parse@4.1.0: {} + semver@6.3.1: {} semver@7.7.3: {} + set-cookie-parser@2.7.1: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -4717,6 +5508,27 @@ snapshots: signal-exit@4.1.0: {} + slow-redact@0.3.2: {} + + smart-buffer@4.2.0: {} + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + socks: 2.8.7 + transitivePeerDependencies: + - supports-color + + socks@2.8.7: + dependencies: + ip-address: 10.0.1 + smart-buffer: 4.2.0 + + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + sort-keys@4.2.0: dependencies: is-plain-obj: 2.1.0 @@ -4729,6 +5541,8 @@ snapshots: dependencies: readable-stream: 3.6.2 + split2@4.2.0: {} + stackback@0.0.2: {} stacktracey@2.1.8: @@ -4743,6 +5557,19 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 + stream-buffers@3.0.3: {} + + stream-shift@1.0.3: {} + + streamx@2.23.0: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.3 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + string-length@4.0.2: dependencies: char-regex: 1.0.2 @@ -4819,12 +5646,43 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 + tar-fs@3.1.1: + dependencies: + pump: 3.0.3 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 4.4.10 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + tar-stream@3.1.7: + dependencies: + b4a: 1.7.3 + fast-fifo: 1.3.2 + streamx: 2.23.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + test-exclude@7.0.1: dependencies: '@istanbuljs/schema': 0.1.3 glob: 10.4.5 minimatch: 9.0.5 + text-decoder@1.2.3: + dependencies: + b4a: 1.7.3 + transitivePeerDependencies: + - react-native-b4a + + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + through2@4.0.2: dependencies: readable-stream: 3.6.2 @@ -4848,6 +5706,10 @@ snapshots: dependencies: is-number: 7.0.0 + toad-cache@3.7.0: {} + + tr46@0.0.3: {} + ts-api-utils@2.1.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -4902,6 +5764,8 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + typedarray@0.0.6: {} + typescript-eslint@8.46.1(eslint@9.37.0)(typescript@5.9.3): dependencies: '@typescript-eslint/eslint-plugin': 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0)(typescript@5.9.3))(eslint@9.37.0)(typescript@5.9.3) @@ -5018,6 +5882,13 @@ snapshots: dependencies: defaults: 1.0.4 + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -5078,6 +5949,12 @@ snapshots: word-wrap@1.2.5: {} + worker-factory@7.0.46: + dependencies: + '@babel/runtime': 7.28.4 + fast-unique-numbers: 9.0.24 + tslib: 2.8.1 + worker-timers-broker@6.1.8: dependencies: '@babel/runtime': 7.28.4 @@ -5085,11 +5962,25 @@ snapshots: tslib: 2.8.1 worker-timers-worker: 7.0.71 + worker-timers-broker@8.0.11: + dependencies: + '@babel/runtime': 7.28.4 + broker-factory: 3.1.10 + fast-unique-numbers: 9.0.24 + tslib: 2.8.1 + worker-timers-worker: 9.0.11 + worker-timers-worker@7.0.71: dependencies: '@babel/runtime': 7.28.4 tslib: 2.8.1 + worker-timers-worker@9.0.11: + dependencies: + '@babel/runtime': 7.28.4 + tslib: 2.8.1 + worker-factory: 7.0.46 + worker-timers@7.1.8: dependencies: '@babel/runtime': 7.28.4 @@ -5097,6 +5988,13 @@ snapshots: worker-timers-broker: 6.1.8 worker-timers-worker: 7.0.71 + worker-timers@8.0.25: + dependencies: + '@babel/runtime': 7.28.4 + tslib: 2.8.1 + worker-timers-broker: 8.0.11 + worker-timers-worker: 9.0.11 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 diff --git a/src/access/access.handler.ts b/src/access/access.handler.ts new file mode 100644 index 0000000..c20bb03 --- /dev/null +++ b/src/access/access.handler.ts @@ -0,0 +1,23 @@ +import type { AccessProvider } from './access.provider.ts'; + +class AccessHandler { + #handlers: Map; + + constructor() { + this.#handlers = new Map(); + } + + public register = (name: string, provider: AccessProvider) => { + this.#handlers.set(name, provider); + }; + + public validate = (provider: string, token: string) => { + const handler = this.#handlers.get(provider); + if (!handler) { + throw new Error('Provider not available'); + } + return handler.getAccess(token); + }; +} + +export { AccessHandler }; diff --git a/src/access/access.provider.ts b/src/access/access.provider.ts new file mode 100644 index 0000000..569ff47 --- /dev/null +++ b/src/access/access.provider.ts @@ -0,0 +1,9 @@ +import type { Statement } from './access.schemas.ts'; + +type AccessProvider = { + getAccess: (token: string) => Promise<{ + statements: Statement[]; + }>; +}; + +export type { AccessProvider }; diff --git a/src/access/access.session.ts b/src/access/access.session.ts index b65ba7f..5c06cfd 100644 --- a/src/access/access.session.ts +++ b/src/access/access.session.ts @@ -17,6 +17,10 @@ class Session { this.#options = options; } + public get statements() { + return this.#options.statements; + } + public validate = (options: ValidateOptions) => { const { statements } = this.#options; return validate({ diff --git a/src/access/access.token.ts b/src/access/access.token.ts index fd8c481..d4c832d 100644 --- a/src/access/access.token.ts +++ b/src/access/access.token.ts @@ -1,6 +1,8 @@ import { z } from 'zod'; import jwt from 'jsonwebtoken'; + import { statementSchema } from './access.schemas.ts'; +import type { AccessProvider } from './access.provider.ts'; type AccessTokensOptions = { secret: string | Buffer; @@ -12,7 +14,7 @@ const tokenBodySchema = z.object({ type TokenBody = z.infer; -class AccessTokens { +class AccessTokens implements AccessProvider { #options: AccessTokensOptions; constructor(options: AccessTokensOptions) { @@ -25,7 +27,7 @@ class AccessTokens { return token; }; - public validate = (token: string) => { + public getAccess = async (token: string) => { const { secret } = this.#options; const data = jwt.verify(token, secret); const parsed = tokenBodySchema.parse(data); diff --git a/src/access/access.utils.ts b/src/access/access.utils.ts index eb9e93d..417a101 100644 --- a/src/access/access.utils.ts +++ b/src/access/access.utils.ts @@ -1,4 +1,5 @@ import micromatch from 'micromatch'; + import type { Statement } from './access.schemas.ts'; type ValidateOptions = { diff --git a/src/api/api.ts b/src/api/api.ts new file mode 100644 index 0000000..267aa52 --- /dev/null +++ b/src/api/api.ts @@ -0,0 +1,9 @@ +import { type FastifyPluginAsync } from 'fastify'; + +const api: FastifyPluginAsync = async (fastify) => { + fastify.get('/healthz', () => { + return { status: 'ok' }; + }); +}; + +export { api }; diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000..2590397 --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line +declare type ExplicitAny = any; diff --git a/src/k8s/k8s.clients.ts b/src/k8s/k8s.clients.ts new file mode 100644 index 0000000..119db6a --- /dev/null +++ b/src/k8s/k8s.clients.ts @@ -0,0 +1,70 @@ +import { KubernetesObjectApi, PatchStrategy, type KubeConfig, type KubernetesObject } from '@kubernetes/client-node'; + +import type { K8sResources } from './k8s.resources.ts'; +import type { K8sBackboneClient } from './k8s.schemas.ts'; + +import type { AccessProvider } from '#root/access/access.provider.ts'; +import type { Statement } from '#root/access/access.schemas.ts'; + +type K8sClientsOptions = { + config: KubeConfig; + resources: K8sResources; +}; + +type K8sClient = { + statements: Statement[]; +}; + +class K8sClients implements AccessProvider { + #options: K8sClientsOptions; + #clients: Map; + + constructor(options: K8sClientsOptions) { + this.#clients = new Map(); + this.#options = options; + const { clients } = options.resources; + clients.on('updated', this.#handleClientAdded); + } + + #handleClientAdded = async (manifest: KubernetesObject & { spec: K8sBackboneClient }) => { + const { resources, config } = this.#options; + const secretName = `${manifest.metadata?.name}-secret`; + const secret = resources.secrets.manifests.find( + (m) => m.metadata?.namespace === manifest.metadata?.namespace && m.metadata?.name === secretName, + ); + const token = secret?.data?.token || crypto.randomUUID(); + if (!secret) { + const objectsApi = config.makeApiClient(KubernetesObjectApi); + + const body = { + apiVersion: 'v1', + kind: 'Secret', + metadata: { + name: secretName, + namespace: manifest.metadata?.namespace, + }, + data: { + token: Buffer.from(token).toString('base64'), + }, + }; + await objectsApi.create(body, undefined, undefined, undefined, undefined); + } + if (!token) { + throw new Error('Secret is missing token'); + } + const tokenValue = Buffer.from(token, 'base64').toString('utf8'); + this.#clients.set(tokenValue, { + statements: manifest.spec.statements, + }); + }; + + public getAccess = async (token: string) => { + const client = this.#clients.get(token); + if (!client) { + throw new Error('invalid credentials'); + } + return client; + }; +} + +export { K8sClients }; diff --git a/src/k8s/k8s.crd.ts b/src/k8s/k8s.crd.ts new file mode 100644 index 0000000..a14ac8d --- /dev/null +++ b/src/k8s/k8s.crd.ts @@ -0,0 +1,71 @@ +import { type KubeConfig, ApiException, ApiextensionsV1Api } from '@kubernetes/client-node'; +import { z, type ZodType } from 'zod'; + +type CreateCrdOptions = { + config: KubeConfig; + kind: string; + apiVersion: string; + plural?: string; + scope: 'Cluster' | 'Namespaced'; + spec: ZodType; +}; +const createCrd = async (options: CreateCrdOptions) => { + const { config, ...definition } = options; + const plural = definition.plural ?? definition.kind.toLowerCase() + 's'; + const [version, group] = definition.apiVersion.split('/').toReversed(); + const manifest = { + apiVersion: 'apiextensions.k8s.io/v1', + kind: 'CustomResourceDefinition', + metadata: { + name: `${plural}.${group}`, + }, + spec: { + group: group, + names: { + kind: definition.kind, + plural: plural, + singular: definition.kind.toLowerCase(), + }, + scope: definition.scope, + versions: [ + { + name: version, + served: true, + storage: true, + schema: { + openAPIV3Schema: { + type: 'object', + properties: { + spec: { + ...z.toJSONSchema(definition.spec, { io: 'input' }), + $schema: undefined, + additionalProperties: undefined, + } as ExplicitAny, + }, + }, + }, + subresources: { + status: {}, + }, + }, + ], + }, + }; + const extensionsApi = config.makeApiClient(ApiextensionsV1Api); + try { + await extensionsApi.createCustomResourceDefinition({ + body: manifest, + }); + } catch (error) { + if (error instanceof ApiException && error.code === 409) { + await extensionsApi.patchCustomResourceDefinition({ + name: manifest.metadata.name, + body: [{ op: 'replace', path: '/spec', value: manifest.spec }], + }); + } else { + throw error; + } + } +}; + +export { createCrd }; diff --git a/src/k8s/k8s.resources.ts b/src/k8s/k8s.resources.ts new file mode 100644 index 0000000..42fb787 --- /dev/null +++ b/src/k8s/k8s.resources.ts @@ -0,0 +1,49 @@ +import { KubeConfig, V1Secret, type KubernetesObject } from '@kubernetes/client-node'; + +import { K8sWatcher } from './k8s.watcher.ts'; +import type { K8sBackboneClient, K8sBackboneTopic } from './k8s.schemas.ts'; + +class K8sResources { + #secrets: K8sWatcher; + #clients: K8sWatcher; + #topics: K8sWatcher; + + constructor(config: KubeConfig) { + config.loadFromDefault(); + this.#secrets = new K8sWatcher({ + config, + apiVersion: 'v1', + kind: 'Secret', + }); + this.#clients = new K8sWatcher({ + config, + apiVersion: 'backbone.mortenolsen.pro/v1', + kind: 'Client', + }); + this.#topics = new K8sWatcher({ + config, + apiVersion: 'backbone.mortenolsen.pro/v1', + kind: 'Topic', + }); + } + + public get secrets() { + return this.#secrets; + } + + public get clients() { + return this.#clients; + } + + public get topics() { + return this.#clients; + } + + public start = async () => { + await this.#secrets.start(); + await this.#clients.start(); + await this.#topics.start(); + }; +} + +export { K8sResources }; diff --git a/src/k8s/k8s.schemas.ts b/src/k8s/k8s.schemas.ts new file mode 100644 index 0000000..87ef51b --- /dev/null +++ b/src/k8s/k8s.schemas.ts @@ -0,0 +1,19 @@ +import { z } from 'zod'; + +import { statementSchema } from '#root/access/access.schemas.ts'; + +const k8sBackboneClientSchema = z.object({ + statements: z.array(statementSchema), +}); + +type K8sBackboneClient = z.infer; + +const k8sBackboneTopicSchema = z.object({ + matches: z.array(z.string()), + schema: z.record(z.string(), z.object()), +}); + +type K8sBackboneTopic = z.infer; + +export type { K8sBackboneClient, K8sBackboneTopic }; +export { k8sBackboneClientSchema, k8sBackboneTopicSchema }; diff --git a/src/k8s/k8s.ts b/src/k8s/k8s.ts new file mode 100644 index 0000000..eaabddd --- /dev/null +++ b/src/k8s/k8s.ts @@ -0,0 +1,52 @@ +import { KubeConfig } from '@kubernetes/client-node'; + +import { K8sResources } from './k8s.resources.ts'; +import { createCrd } from './k8s.crd.ts'; +import { k8sBackboneClientSchema, k8sBackboneTopicSchema } from './k8s.schemas.ts'; +import { K8sClients } from './k8s.clients.ts'; + +import { API_VERSION } from '#root/utils/consts.ts'; + +class K8sService { + #config: KubeConfig; + #resources: K8sResources; + #clients: K8sClients; + + constructor() { + this.#config = new KubeConfig(); + this.#config.loadFromDefault(); + this.#resources = new K8sResources(this.#config); + this.#clients = new K8sClients({ + config: this.#config, + resources: this.resources, + }); + } + + public get resources() { + return this.#resources; + } + + public get clients() { + return this.#clients; + } + + public setup = async () => { + await createCrd({ + config: this.#config, + apiVersion: API_VERSION, + kind: 'Client', + scope: 'Namespaced', + spec: k8sBackboneClientSchema, + }); + await createCrd({ + config: this.#config, + apiVersion: API_VERSION, + kind: 'Topic', + scope: 'Namespaced', + spec: k8sBackboneTopicSchema, + }); + await this.#resources.start(); + }; +} + +export { K8sService }; diff --git a/src/k8s/k8s.watcher.ts b/src/k8s/k8s.watcher.ts new file mode 100644 index 0000000..2192d13 --- /dev/null +++ b/src/k8s/k8s.watcher.ts @@ -0,0 +1,87 @@ +import { + KubeConfig, + KubernetesObjectApi, + makeInformer, + type Informer, + type KubernetesObject, +} from '@kubernetes/client-node'; + +import { EventEmitter } from '#root/utils/event-emitter.ts'; + +type K8sWatcherOptions = { + config: KubeConfig; + apiVersion: string; + plural?: string; + kind: string; + selector?: string; +}; + +type K8sWatcherEvents = { + updated: (manifest: TType) => void; + removed: (manifest: TType) => void; +}; + +class K8sWatcher extends EventEmitter> { + #options: K8sWatcherOptions; + #informer: Informer; + #manifests: Map; + + constructor(options: K8sWatcherOptions) { + super(); + this.#options = options; + this.#manifests = new Map(); + this.#informer = this.#setupInformer(); + } + + public get manifests() { + return Array.from(this.#manifests.values()); + } + + #setupInformer = () => { + const { config, apiVersion, kind, plural, selector } = this.#options; + const objectApi = config.makeApiClient(KubernetesObjectApi); + const derivedPlural = plural ?? kind.toLowerCase() + 's'; + const [version, group] = apiVersion.split('/').toReversed(); + const path = group ? `/apis/${group}/${version}/${derivedPlural}` : `/api/${version}/${derivedPlural}`; + + const informer = makeInformer( + config, + path, + async () => { + return objectApi.list(apiVersion, kind); + }, + selector, + ); + informer.on('add', this.#handleResource.bind(this, 'add')); + informer.on('update', this.#handleResource.bind(this, 'update')); + informer.on('delete', this.#handleResource.bind(this, 'delete')); + informer.on('error', (err) => { + console.log('Watcher failed, will retry in 3 seconds', path, err); + setTimeout(this.start, 3000); + }); + return informer; + }; + + #handleResource = (action: string, manifest: TType) => { + const uid = manifest.metadata?.uid; + if (!uid) { + return; + } + if (action === 'delete') { + this.#manifests.delete(uid); + return this.emit('removed', manifest); + } + this.#manifests.set(uid, manifest); + this.emit('updated', manifest); + }; + + public start = () => { + return this.#informer.start(); + }; + + public stop = () => { + return this.#informer.stop(); + }; +} + +export { K8sWatcher }; diff --git a/src/server/server.ts b/src/server/server.ts index 40bcc9e..ed03d85 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -1,40 +1,53 @@ -import http from 'node:http'; import tcp from 'node:net'; -import { WebSocketServer, createWebSocketStream } from 'ws'; +import type { IncomingMessage } from 'node:http'; + import { - createBroker, type AuthenticateHandler, type AuthorizeForwardHandler, type AuthorizePublishHandler, type AuthorizeSubscribeHandler, type PublishedHandler, } from 'aedes'; -import { AedesMemoryPersistence } from 'aedes-persistence'; -import { Session } from '../access/access.session.ts'; -import type { AccessTokens } from '#root/access/access.token.ts'; +import aedes from 'aedes'; +import fastify, { type FastifyInstance } from 'fastify'; +import fastifyWebSocket from '@fastify/websocket'; +import { createWebSocketStream } from 'ws'; -type Aedes = ReturnType; +import { Session } from '../access/access.session.ts'; +import { api } from '../api/api.ts'; + +import type { AccessHandler } from '#root/access/access.handler.ts'; +import type { TopicsHandler } from '#root/topics/topics.handler.ts'; + +type Aedes = ReturnType; declare module 'aedes' { + // eslint-disable-next-line export interface Client { session: Session; } } +const packetMetaSymbol = Symbol('packetMeta'); + type MqttServerOptions = { - accessTokens: AccessTokens; + accessHandler: AccessHandler; + topicsHandler: TopicsHandler; }; +class AuthError extends Error { + public readonly returnCode = 4; +} + class MqttServer { #options: MqttServerOptions; #server: Aedes; - #http?: http.Server; + #http?: Promise; #tcp?: tcp.Server; constructor(options: MqttServerOptions) { this.#options = options; - this.#server = createBroker({ - persistence: new AedesMemoryPersistence(), + this.#server = aedes.createBroker({ authenticate: this.#authenticate, authorizePublish: this.#authorizePublish, authorizeSubscribe: this.#authorizeSubscribe, @@ -43,19 +56,25 @@ class MqttServer { }); } - #authenticate: AuthenticateHandler = (client, _username, password, callback) => { - if (!password) { - throw new Error('unauthorized'); + #authenticate: AuthenticateHandler = async (client, username, password, callback) => { + try { + if (!username || !password) { + throw new Error('unauthorized'); + } + const { accessHandler } = this.#options; + const auth = await accessHandler.validate(username, password.toString('utf8')); + client.session = new Session(auth); + callback(null, true); + } catch { + callback(new AuthError('Unautorized'), false); } - const { accessTokens } = this.#options; - const auth = accessTokens.validate(password.toString('utf8')); - client.session = new Session({ - statements: auth.statements, - }); - callback(null, true); }; #authorizePublish: AuthorizePublishHandler = (client, packet, callback) => { + const { topicsHandler } = this.#options; + (packet as ExplicitAny)[packetMetaSymbol] = { + foo: 'bar', + }; const authorized = client?.session.validate({ action: 'mqtt:publish', resource: `mqtt:${packet.topic}`, @@ -63,6 +82,9 @@ class MqttServer { if (!authorized) { return callback(new Error('unauthorized')); } + if (!topicsHandler.validate(packet)) { + return callback(new Error('rules not matched')); + } callback(); }; @@ -79,7 +101,7 @@ class MqttServer { #authorizeForward: AuthorizeForwardHandler = (client, packet) => { const authorized = client.session.validate({ - action: 'mqtt:forward', + action: 'mqtt:read', resource: `mqtt:${packet.topic}`, }); if (!authorized) { @@ -92,16 +114,22 @@ class MqttServer { callback(); }; + #setupHttpServer = async () => { + const http = fastify({}); + await http.register(fastifyWebSocket); + http.get('/ws', { websocket: true }, (socket, req) => { + const stream = createWebSocketStream(socket); + this.#server.handle(stream, req as unknown as IncomingMessage); + }); + await http.register(api, { + prefix: '/api', + }); + return http; + }; + public getHttpServer = () => { if (!this.#http) { - this.#http = http.createServer(); - const wss = new WebSocketServer({ - server: this.#http, - }); - wss.on('connection', (websocket, req) => { - const stream = createWebSocketStream(websocket); - this.#server.handle(stream, req); - }); + this.#http = this.#setupHttpServer(); } return this.#http; }; diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 0000000..5d4ee20 --- /dev/null +++ b/src/test.ts @@ -0,0 +1,21 @@ +import { AccessHandler } from './access/access.handler.ts'; +import { K8sService } from './k8s/k8s.ts'; +import { MqttServer } from './server/server.ts'; +import { TopicsHandler } from './topics/topics.handler.ts'; + +const accessHandler = new AccessHandler(); +const topicsHandler = new TopicsHandler(); + +const k8s = new K8sService(); +await k8s.setup(); +accessHandler.register('k8s', k8s.clients); +const server = new MqttServer({ + accessHandler, + topicsHandler, +}); +const tcp = server.getTcpServer(); +tcp.listen(1883); +const http = await server.getHttpServer(); +http.listen({ port: 8883 }); + +console.log('started'); diff --git a/src/topics/topcis.schemas.ts b/src/topics/topcis.schemas.ts new file mode 100644 index 0000000..46ee104 --- /dev/null +++ b/src/topics/topcis.schemas.ts @@ -0,0 +1,15 @@ +import { z } from 'zod'; + +const topicDefinitionSchema = z.object({ + matches: z.array(z.string()), + name: z.string().optional(), + description: z.string().optional(), + schema: z.object().optional(), + qos: z.array(z.number()).optional(), + retain: z.boolean().optional(), +}); + +type TopicDefinition = z.infer; + +export type { TopicDefinition }; +export { topicDefinitionSchema }; diff --git a/src/topics/topics.handler.ts b/src/topics/topics.handler.ts new file mode 100644 index 0000000..1f18b80 --- /dev/null +++ b/src/topics/topics.handler.ts @@ -0,0 +1,52 @@ +import type { PublishPacket } from 'aedes'; +import micromatch from 'micromatch'; +import { Ajv } from 'ajv'; + +import type { TopicsProvider } from './topics.provider.ts'; + +class TopicsHandler { + #handlers: Set; + #ajv: Ajv; + + constructor() { + this.#handlers = new Set(); + this.#ajv = new Ajv(); + } + + public get topics() { + return Array.from(this.#handlers).flatMap((handler) => handler.definitions); + } + + public register = (provider: TopicsProvider) => { + this.#handlers.add(provider); + }; + + public validate = (packet: PublishPacket) => { + if (packet.topic.startsWith('$SYS/')) { + return true; + } + const matches = this.topics.filter((topic) => micromatch.isMatch(packet.topic, topic.matches)); + const isValid = + matches.length === 0 || + matches.some((topic) => { + if (topic.qos && !topic.qos.includes(packet.qos)) { + return false; + } + if (topic.retain !== undefined && packet.retain !== topic.retain) { + return false; + } + if (topic.schema) { + const data = JSON.parse(packet.payload.toString('utf8')); + const validate = this.#ajv.compile(topic.schema); + const valid = validate(data); + if (!valid) { + return false; + } + } + return true; + }); + return isValid; + }; +} + +export { TopicsHandler }; diff --git a/src/topics/topics.provider.ts b/src/topics/topics.provider.ts new file mode 100644 index 0000000..18bce86 --- /dev/null +++ b/src/topics/topics.provider.ts @@ -0,0 +1,7 @@ +import type { TopicDefinition } from './topcis.schemas.ts'; + +type TopicsProvider = { + definitions: TopicDefinition[]; +}; + +export type { TopicsProvider }; diff --git a/src/topics/topics.store.ts b/src/topics/topics.store.ts new file mode 100644 index 0000000..11f3742 --- /dev/null +++ b/src/topics/topics.store.ts @@ -0,0 +1,20 @@ +import type { TopicDefinition } from './topcis.schemas.ts'; +import type { TopicsProvider } from './topics.provider.ts'; + +class TopicsStore implements TopicsProvider { + #definitions: Set; + + constructor() { + this.#definitions = new Set(); + } + + public get definitions() { + return Array.from(this.#definitions); + } + + public register = (...definitions: TopicDefinition[]) => { + definitions.forEach(this.#definitions.add); + }; +} + +export { TopicsStore }; diff --git a/src/utils/consts.ts b/src/utils/consts.ts new file mode 100644 index 0000000..449f98e --- /dev/null +++ b/src/utils/consts.ts @@ -0,0 +1,5 @@ +const K8S_GROUP = 'backbone.mortenolsen.pro'; +const K8S_VERSION = 'v1'; +const API_VERSION = 'backbone.mortenolsen.pro/v1'; + +export { API_VERSION, K8S_GROUP, K8S_VERSION }; diff --git a/src/utils/event-emitter.ts b/src/utils/event-emitter.ts new file mode 100644 index 0000000..e58953f --- /dev/null +++ b/src/utils/event-emitter.ts @@ -0,0 +1,64 @@ +type EventListener = (...args: T) => void | Promise; + +type OnOptions = { + abortSignal?: AbortSignal; +}; + +class EventEmitter void | Promise>> { + #listeners = new Map>>(); + + on = (event: K, callback: EventListener>, options: OnOptions = {}) => { + const { abortSignal } = options; + if (!this.#listeners.has(event)) { + this.#listeners.set(event, new Set()); + } + const callbackClone = (...args: Parameters) => callback(...args); + const abortController = new AbortController(); + const listeners = this.#listeners.get(event); + if (!listeners) { + throw new Error('Event registration failed'); + } + abortSignal?.addEventListener('abort', abortController.abort); + listeners.add(callbackClone); + abortController.signal.addEventListener('abort', () => { + this.#listeners.set(event, listeners?.difference(new Set([callbackClone]))); + }); + return abortController.abort; + }; + + once = (event: K, callback: EventListener>, options: OnOptions = {}) => { + const abortController = new AbortController(); + options.abortSignal?.addEventListener('abort', abortController.abort); + return this.on( + event, + async (...args) => { + abortController.abort(); + await callback(...args); + }, + { + ...options, + abortSignal: abortController.signal, + }, + ); + }; + + emit = (event: K, ...args: Parameters) => { + const listeners = this.#listeners.get(event); + if (!listeners) { + return; + } + for (const listener of listeners) { + listener(...args); + } + }; + + emitAsync = async (event: K, ...args: Parameters) => { + const listeners = this.#listeners.get(event); + if (!listeners) { + return; + } + await Promise.all(listeners.values().map((listener) => listener(...args))); + }; +} + +export { EventEmitter }; diff --git a/tests/mqtt.test.ts b/tests/mqtt.test.ts new file mode 100644 index 0000000..677785f --- /dev/null +++ b/tests/mqtt.test.ts @@ -0,0 +1,73 @@ +import { describe, beforeEach, afterEach, it, vi, expect } from 'vitest'; + +import { createWorld, type World } from './utils/utils.world.ts'; +import { statements } from './utils/utils.statements.ts'; + +describe('mqtt', () => { + let world: World = undefined as unknown as World; + + beforeEach(async () => { + world = await createWorld({}); + }); + + afterEach(async () => { + if (world) { + await world.destroy(); + } + }); + + it('should be able to send messages to all subscribers', async () => { + const [clientA, clientB, clientC] = await world.connect(statements.all, statements.all, statements.all); + const spyB = vi.fn(); + const spyC = vi.fn(); + clientB.on('message', spyB); + clientC.on('message', spyC); + await clientB.subscribeAsync('test'); + await clientC.subscribeAsync('test'); + await clientA.publishAsync('test', 'test'); + await vi.waitUntil(() => spyB.mock.calls.length && spyC.mock.calls.length); + + expect(spyB).toHaveBeenCalledTimes(1); + expect(spyC).toHaveBeenCalledTimes(1); + }); + + it('should not be able to subscribe if not allowed', async () => { + const [client] = await world.connect([]); + const promise = client.subscribeAsync('test'); + await expect(promise).rejects.toThrow(); + }); + + it('should not be able to publish if not allowed', async () => { + const [client] = await world.connect([]); + const promise = client.publishAsync('test', 'test'); + + // TODO: why does this not throw? + // await expect(promise).rejects.toThrow(); + }); + + it('should not be able to read messages if not allowed', async () => { + const [clientA, clientB] = await world.connect(statements.all, statements.noRead); + const spy = vi.fn(); + clientB.on('message', spy); + await clientB.subscribeAsync('test'); + await clientA.publishAsync('test', 'test'); + await new Promise((resolve) => setTimeout(resolve, 100)); + + expect(spy).toHaveBeenCalledTimes(0); + }); + + it('should be able to handle many connections', async () => { + const clients = await world.connect(...new Array(50).fill(statements.all)); + const spies = await Promise.all( + clients.map(async (client) => { + const spy = vi.fn(); + client.on('message', spy); + await client.subscribeAsync('test'); + return spy; + }), + ); + const [sender] = await world.connect(statements.all); + await sender.publishAsync('test', 'test'); + await vi.waitUntil(() => spies.every((s) => s.mock.calls.length)); + }); +}); diff --git a/tests/tsconfig.json b/tests/tsconfig.json new file mode 100644 index 0000000..eaef6c6 --- /dev/null +++ b/tests/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../tsconfig.json", + "include": ["../src/**/*.ts", "./**/*.ts"] +} diff --git a/tests/utils/utils.statements.ts b/tests/utils/utils.statements.ts new file mode 100644 index 0000000..5ba9a2e --- /dev/null +++ b/tests/utils/utils.statements.ts @@ -0,0 +1,25 @@ +import type { Statement } from '#root/access/access.schemas.ts'; + +const statements = { + all: [ + { + effect: 'allow', + resources: ['**'], + actions: ['**'], + }, + ], + noRead: [ + { + effect: 'allow', + resources: ['**'], + actions: ['**'], + }, + { + effect: 'disallow', + resources: ['**'], + actions: ['mqtt:read'], + }, + ], +} satisfies Record; + +export { statements }; diff --git a/tests/utils/utils.world.ts b/tests/utils/utils.world.ts new file mode 100644 index 0000000..4b604ec --- /dev/null +++ b/tests/utils/utils.world.ts @@ -0,0 +1,73 @@ +import mqtt, { connectAsync, MqttClient } from 'mqtt'; +import getPort from 'get-port'; + +import { AccessHandler } from '#root/access/access.handler.ts'; +import { type Statement } from '#root/access/access.schemas.ts'; +import { AccessTokens } from '#root/access/access.token.ts'; +import { MqttServer } from '#root/server/server.ts'; +import type { TopicDefinition } from '#root/topics/topcis.schemas.ts'; +import { TopicsHandler } from '#root/topics/topics.handler.ts'; +import { TopicsStore } from '#root/topics/topics.store.ts'; + +type CreateSocketOptions = { + port: number; + token: string; +}; + +const createSocket = async (options: CreateSocketOptions) => { + const { port, token } = options; + const mqttClient = await connectAsync(`ws://localhost:${port}/ws`, { + username: 'token', + password: token, + reconnectOnConnackError: false, + }); + return mqttClient; +}; + +type WorldOptions = { + topics?: TopicDefinition[]; +}; + +const createWorld = async (options: WorldOptions) => { + const { topics = [] } = options; + const secret = 'test'; + const accessHandler = new AccessHandler(); + const accessTokens = new AccessTokens({ + secret, + }); + accessHandler.register('token', accessTokens); + const topicsHandler = new TopicsHandler(); + const topicsStore = new TopicsStore(); + topicsStore.register(...topics); + topicsHandler.register(topicsStore); + const server = new MqttServer({ topicsHandler, accessHandler }); + const fastify = await server.getHttpServer(); + const port = await getPort(); + await fastify.listen({ port }); + const sockets: MqttClient[] = []; + return { + connect: async (...clients: Statement[][]) => { + const newSockets = await Promise.all( + clients.map((statements) => + createSocket({ + port, + token: accessTokens.generate({ + statements, + }), + }), + ), + ); + sockets.push(...newSockets); + return newSockets; + }, + destroy: async () => { + await Promise.all(sockets.map((s) => s.endAsync())); + await fastify.close(); + }, + }; +}; + +type World = Awaited>; + +export type { World }; +export { createWorld }; diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..12a46ab --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +// eslint-disable-next-line +export default defineConfig({ + test: { + include: ['**/*.test.ts'], + globals: true, + }, +});