From 1f7bb45549c5b56c44e8902ab606a3414e22799c Mon Sep 17 00:00:00 2001 From: Type-Style Date: Thu, 28 Mar 2024 16:39:28 +0100 Subject: [PATCH] update master (#57) * [Task] #6 provide fallback index.html * [Task] #6 production ready code (m) move httpdocs folder to dist have compile without sourcemaps for faster speed * [Task] #6 create github action for upload when main is updated (#21) * [change] #6 new ftp upload action * [Fix] #6 replace host with server in ftp action * [Task] #6 basic log (#26) * [CHANGE] #6 revert back to require output for production * [Task] #6 add ability to manually upload to prod * [Task] #9 enable manual start of codechecks * 10 webhook for writing (#36) * [Change] #3 clean up npm scripts, to have clean folder before build * [Task] #10 created data types in typescript * [Temp] #10 created subroute for writing, and folder structure * [Change] #3 include to use relative paths from src folder in ts and node https://stackoverflow.com/questions/43281741/how-can-i-use-paths-in-tsconfig-json See comment from Remo H. Hansen with at least 100 upvoted * [Change] Update VSCode to keep files open * [Task] #18 setup dotenv for secret variables * [Temp, Task] #10 Validate inputs using express-validator and custom functions * [Task] #18 prevent parameter pollution * [Task] #10 validating incoming parameter and logging errors * [Task] #7 add basic cache to express * [Changes] #7 Error Handling, to include basic custom Error Handling * [Task] #10 enhanced validation to only allow known parameters * [Change] #35 added Jest, tests for helper functions when writing * [Task] #10 better error Handling * [Task] #35 add tests for writing webhook validation * [TASK] #18 protect Webhook using KEY * [Fix] #35 test know import path structure now * [Task] #35 add test for protected webhook * [Task] #35 refactor build to run jest tests * [Task] #10 switched to crypto instead of bcrypt for dependency issue see synk inflight * [Fix] #36 PRQ Feedback * [Task] #3 improve error handling, logger and added chalk to colorize console output. Had to use chalk version 4 because of typescript converting to require, and chalk5 do want import syntax. * [Change] #3 nodemon to clear console when in dev mode * [!Task] #32 webhook creates folder and file based on date * [Change] #35 relocated tests and refactor write, also added file check * [Task] #18, installed helmet, configured self as CSP origin * [Fix] moved chalk out of dev dependency * [Task] #32 error logging and text output improvement, log string instead of "object" * [Task] #18 CSP Update to allow localhost for testing * [Fix] #3 debugging setup improvments * [FIX] #10 Error Handling * [Task] #10 writing basic non calculated data to file * [Fix] #10 avoid Header Modification after sending the request * [Task] #10 JSON Data pretty output * [Task] #32 update types to reflect subobjects of entry * [Task] #10 write time * [Task] #32 added logging for time edgecases * [Task] #10 output seconds * [Task] #10 calculate distance based on lat and lon * [Task] #32 writing tests for time and distance * [Task] #32 change distance calculation to use pythagoras * [Task] #38 add favicon * [Task] #32 time converted to seconds * [Taskk] #32 speed calculation and output and tests * [Task] #32 speed tests * [Task] #33 add ignore * [Task] #32 test finetuning * [Task] #32 add angle between entries * [Task] #32 test for angle, extracted getData function * [change] #32 test to include optional leading 0 for days * [!!!Task] #18 add uncaughtExeption handler as last resort * [Task] #7 enhance static options to include common filetypes; index file start is used as index file to avoid collisions with host provider * [change] #32 validation to be used more explictly * [change] #32 add index to log while writing * [Task] #32 test if 1000 calls can be made with randomized data * [!!! Task] #32 limit JSON Data to be 1000 lines: replace last line with most recent entry * [Change, Task] #32 if 1000 entries exceeded, only replace last if hdop is good * [Change] build action enable button to on manually * [temp] test y tests fail * Create node.js.yml * Create main.yml * [!!!Fix] Created new workflow to build / test node, commented tests back in. Increased time between server calls in test, to check difference time more accurately * [Task] #33 moved ignore to its own file since it creates data rather than validating it * 42 output json (#44) * [Task] #42, created route to output json * [Task] #42 added tests for read json * 41 add rate limiter (#45) * [Task] #18, limit request size for security reasons * [Task] #43, introduce gzip to transfer data * [Task] #34 improve error handling, log server shutdowns * [Task] #34 installed and integrated tooBusy to send 503 when load is high * [Task] #34 improved tooBusy, improved formatting * [Task, Temp] #41 installed ratelimiter and slowDown * [Task] #42 cleanup ipv6 addresses * [Change] #10 error handling for better gitBash and txt output, also reduced stack in case of validation errors * [Task] #41 prepare Log for RateLImit errors * [Temp] #41 write route rateLImited temp: see Todos * [Task] #34 colorize prefix in console * [Task] #42 extract middlewares and move to folder * [Task] #41 ratelimiter cleaning up periodicly * [Task] #41 skip tests in rateLimiting * Bump follow-redirects from 1.15.5 to 1.15.6 (#47) Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.5 to 1.15.6. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.5...v1.15.6) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * 43 secure output route (#46) * [Task] #43 create color pallette via atmos * [Task] #43 create color pallette via atmos * [Task] #43 cleanup colors and svg * [Task] #41 remove test code * [CHANGE] #3 reconfigured nodemon to copy static files * [Task] #18 replaced getRawBody with builtIn express urlEncoded * [Temp, Task] #43 basic login page, not yet used as middleware * [Temp] #43, create and validate json web token * [Task] #43, add slowDown and RateLimit for failed login attempts * [Task] #43, ratelimit for login page * [Task] #43, add global ratelimiter * [fix] #7, improve error handeling for express errors * [Task] #43 rework body limitations to be checked only appropiate methods * [Task] #43 added check for data before using it * [Task] #43 check that body is ignored for GET in request * [Task] #43 login test * [Task] #43 create tests for login * [Task] #43 fine tune error handling * [Task] #43, finished login and jwt related tests * [Change] #34, no further need for test logging * [Task] #43, fine tune jwt, middleware process improved * [CHANGE] #43 created new esLint to have clientside js without ts * [Temp] #43 test to see new linter configuration * [Change] #43 switched to bcrypt for passwords * [Task] #43 read return json in all cases * [Task] #43 introduced color classes * [Task] #43, prq feedback * [Temp} #43 figuring out why tests dont run on github * [Task] #43 code cleanup * 48 move login to seperate controller (#49) * [Task] #43, add label to form * [Task] #48 login controller * 50 integrate csrf protection for login form (#53) * [Task] #50, create CSRF Validation for login form * [Task] #43, added icon to repository for later use * [Task] #50, cleanup cetntralized; rename token functions * [Task] #50, reduced token length and improved error handling * [Task] #50 csrf tests added to login * [Task] #50, added test case for csrf, repaired integration * fix: upgrade express-rate-limit from 7.1.5 to 7.2.0 (#52) Snyk has created this PR to upgrade express-rate-limit from 7.1.5 to 7.2.0. See this package in npm: https://www.npmjs.com/package/express-rate-limit See this project in Snyk: https://app.snyk.io/org/type-style/project/e2bcd002-cb74-409c-ba55-fb6349df1cbc?utm_source=github&utm_medium=referral&page=upgrade-pr Co-authored-by: snyk-bot * fix: package.json & package-lock.json to reduce vulnerabilities (#54) The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-EXPRESS-6474509 Co-authored-by: snyk-bot * [Snyk] Upgrade express from 4.18.2 to 4.18.3 (#51) * fix: upgrade express from 4.18.2 to 4.18.3 Snyk has created this PR to upgrade express from 4.18.2 to 4.18.3. See this package in npm: https://www.npmjs.com/package/express See this project in Snyk: https://app.snyk.io/org/type-style/project/e2bcd002-cb74-409c-ba55-fb6349df1cbc?utm_source=github&utm_medium=referral&page=upgrade-pr * 50 integrate csrf protection for login form (#53) * [Task] #50, create CSRF Validation for login form * [Task] #43, added icon to repository for later use * [Task] #50, cleanup cetntralized; rename token functions * [Task] #50, reduced token length and improved error handling * [Task] #50 csrf tests added to login * [Task] #50, added test case for csrf, repaired integration * fix: upgrade express-rate-limit from 7.1.5 to 7.2.0 (#52) Snyk has created this PR to upgrade express-rate-limit from 7.1.5 to 7.2.0. See this package in npm: https://www.npmjs.com/package/express-rate-limit See this project in Snyk: https://app.snyk.io/org/type-style/project/e2bcd002-cb74-409c-ba55-fb6349df1cbc?utm_source=github&utm_medium=referral&page=upgrade-pr Co-authored-by: snyk-bot --------- Co-authored-by: snyk-bot * [Task] update dev after main merge --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: snyk-bot --- .eslintrc.json | 2 +- .github/workflows/build.yml | 25 - .github/workflows/eslint.yml | 9 +- .github/workflows/main.yml | 48 + .gitignore | 2 + .vscode/launch.json | 32 +- .vscode/tasks.json | 18 + httpdocs/color-table.svg | 159 + httpdocs/css/colors.css | 95 + httpdocs/css/login.css | 20 + httpdocs/favicon.ico | Bin 0 -> 1150 bytes httpdocs/icon.png | Bin 0 -> 50481 bytes httpdocs/js/.eslintrc.json | 12 + httpdocs/js/login.js | 1 + jest.config.js | 1 + nodemon-static.json | 8 + nodemon-ts.json | 9 + nodemon.json | 6 - package-lock.json | 3895 ++++++++++++++++- package.json | 39 +- src/app.test.ts | 15 - src/app.ts | 94 +- src/controller/login.ts | 58 + src/controller/read.ts | 37 + src/controller/write.test.ts | 100 - src/controller/write.ts | 46 +- src/middleware/cache.ts | 15 + src/middleware/error.ts | 45 + src/middleware/limit.ts | 71 + src/middleware/logged-in.ts | 13 + src/models/entry.ts | 131 +- src/scripts/angle.ts | 9 + src/scripts/crypt.ts | 23 +- src/scripts/distance.ts | 30 + src/scripts/file.ts | 62 + src/scripts/ignore.ts | 13 + src/scripts/logger.ts | 52 +- src/scripts/speed.ts | 19 + src/scripts/time.ts | 34 + src/scripts/token.ts | 87 + src/tests/app.test.ts | 42 + src/tests/integration.test.ts | 325 ++ src/tests/login.test.ts | 94 + .../entry.test.ts => tests/unit.test.ts} | 6 +- tsconfig.json | 1 + types.d.ts | 81 +- views/login-form.ejs | 31 + 47 files changed, 5464 insertions(+), 451 deletions(-) delete mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/main.yml create mode 100644 .vscode/tasks.json create mode 100644 httpdocs/color-table.svg create mode 100644 httpdocs/css/colors.css create mode 100644 httpdocs/css/login.css create mode 100644 httpdocs/favicon.ico create mode 100644 httpdocs/icon.png create mode 100644 httpdocs/js/.eslintrc.json create mode 100644 httpdocs/js/login.js create mode 100644 nodemon-static.json create mode 100644 nodemon-ts.json delete mode 100644 nodemon.json delete mode 100644 src/app.test.ts create mode 100644 src/controller/login.ts create mode 100644 src/controller/read.ts delete mode 100644 src/controller/write.test.ts create mode 100644 src/middleware/cache.ts create mode 100644 src/middleware/error.ts create mode 100644 src/middleware/limit.ts create mode 100644 src/middleware/logged-in.ts create mode 100644 src/scripts/angle.ts create mode 100644 src/scripts/distance.ts create mode 100644 src/scripts/file.ts create mode 100644 src/scripts/ignore.ts create mode 100644 src/scripts/speed.ts create mode 100644 src/scripts/token.ts create mode 100644 src/tests/app.test.ts create mode 100644 src/tests/integration.test.ts create mode 100644 src/tests/login.test.ts rename src/{models/entry.test.ts => tests/unit.test.ts} (93%) create mode 100644 views/login-form.ejs diff --git a/.eslintrc.json b/.eslintrc.json index 30be6b6..aa4c83b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -22,6 +22,6 @@ //"@typescript-eslint/no-unused-vars": "warn" "jest/no-conditional-expect": "off" }, - "ignorePatterns": ["dist", "jest.config.js"] + "ignorePatterns": ["dist", "jest.config.js", "httpdocs"] } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 4d0f1e0..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,25 +0,0 @@ -# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs - -name: Node.js CI - -on: - push: - branches: [ "main", "dev" ] - pull_request: - branches: [ "dev" ] - -jobs: - build-node: - runs-on: ubuntu-latest - container: node:20 - steps: - - run: node --version - - uses: actions/checkout@v3 - - run: npm ci - - run: npm run build:prod --if-present - - name: Start server - run: | - npm start & - sleep 5 # Give server some time to start - - run: npm test diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index 3aed66a..95578d7 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -15,8 +15,7 @@ jobs: with: node-version: 16 - run: npm ci # or yarn install - - uses: sibiraj-s/action-eslint@v3 - with: - eslint-args: '--ignore-path=.gitignore --quiet' - extensions: 'js,jsx,ts,tsx' - annotations: true + - name: Lint server-side code + run: npx eslint src/ --fix + - name: Lint client-side code + run: npx eslint httpdocs/js/ --fix diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..e31f256 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,48 @@ +name: Tests + +on: + workflow_dispatch: + push: + branches: [ "dev", "main" ] + pull_request: + branches: [ "dev", "main" ] + +jobs: + build: + runs-on: ubuntu-latest + env: + NODE_ENV: ${{ vars.NODE_ENV }} + LOCALHOST: ${{ vars.LOCALHOST }} + LOCALHOSTV6: ${{ vars.LOCALHOSTV6 }} + KEYA: ${{ secrets.KEYA }} + KEYB: ${{ secrets.KEYB }} + USER_TEST: ${{ secrets.USER_TEST }} + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: '20' + cache: 'npm' + - run: echo "NODE_ENV = $NODE_ENV" + - run: npm ci + - run: npm run build --if-present + - name: Start server + run: | + sudo NODE_ENV=$NODE_ENV LOCALHOST=$LOCALHOST LOCALHOSTV6=$LOCALHOSTV6 KEYA=$KEYA KEYB=$KEYB USER_TEST=$USER_TEST npm start & + sleep 15 # Give server some time to start + - name: Check if server is running + run: | + curl --fail http://localhost:80 || exit 1 + - name: Run app tests + run: npm run test:app + - name: Run login tests + run: npm run test:login + - name: Run unit tests + run: npm run test:unit + - name: Run integration tests + run: npm run test:integration + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore index c6bba59..c53815f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +data/ + # Logs logs *.log diff --git a/.vscode/launch.json b/.vscode/launch.json index c56ec22..8425a2b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,21 +1,15 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Debug server.ts", - "skipFiles": [ - "/**" - ], - "program": "${workspaceFolder}\\src\\app.ts", - "preLaunchTask": "tsc: build - tsconfig.json", - "outFiles": [ - "${workspaceFolder}/dist/**/*.js" - ] - } - ] + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Debug TypeScript", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}\\src\\app.ts", + "preLaunchTask": "build" + } + ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..864577f --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,18 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "type": "shell", + "command": "npx tsc -p tsconfig.json", + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "silent" + }, + "problemMatcher": "$tsc" + } + ] +} diff --git a/httpdocs/color-table.svg b/httpdocs/color-table.svg new file mode 100644 index 0000000..d299941 --- /dev/null +++ b/httpdocs/color-table.svg @@ -0,0 +1,159 @@ + + + + + + HEX + + + main + + + info + + + alert + + + success + + + neutral + + + + OKLCH + + + + + 900 + + + + 750 + + + + 625 + + + 500 + + + 375 + + + 250 + + + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/httpdocs/css/colors.css b/httpdocs/css/colors.css new file mode 100644 index 0000000..d77a7fd --- /dev/null +++ b/httpdocs/css/colors.css @@ -0,0 +1,95 @@ +/* +created by atmos https://app.atmos.style/65cc9eaec76d443c0a796d4b + +** base configuration colors ** +Main: #f90 +Info: #231aee +Danger: #ff0000 +Success: #59ec04 +Neutral: #131211 +*/ + +[class*=color] { + --lightness: 67.66%; + --hue: 64.55; + --chroma: 0.007; + color: oklch(var(--lightness) var(--chroma) var(--hue)); + + &[class*=l1] {--lightness: 10%;} + &[class*=l2] {--lightness: 25%;} + &[class*=l3] {--lightness: 37.5%;} + &[class*=l4] {--lightness: 50%;} + &[class*=l5] {--lightness: 62.5%;} + &[class*=l6] {--lightness: 77.2%;} + &[class*=l7] {--lightness: 90%;} + + &[class*=main] { + --lightness: 77.2%; + --chroma: 0.1738; + --hue: 64.55; + + &[class*=l1] {--chroma: 0.02;} + &[class*=l2] {--chroma: 0.056;} + &[class*=l3] {--chroma: 0.085;} + &[class*=l4] {--chroma: 0.114;} + &[class*=l5] {--chroma: 0.142;} + &[class*=l6] {--chroma: 0.1738;} /* base */ + &[class*=l7] {--chroma: 0.06;} + } + + &[class*=info] { + --lightness: 44.87%; + --chroma: 0.2838; + --hue: 268.0; + + &[class*=l1] {--chroma: 0.055;} + &[class*=l2] {--chroma: 0.158;} + &[class*=l3] {--chroma: 0.237;} + &[class*=l4] {--chroma: 0.2838;} /* base */ + &[class*=l5] {--chroma: 0.19;} + &[class*=l6] {--chroma: 0.109;} + &[class*=l7] {--chroma: 0.04;} + } + + &[class*=alert] { + --lightness: 62.8%; + --chroma: 0.2577; + --hue: 29.23; + + &[class*=l1] {--chroma: 0.036;} + &[class*=l2] {--chroma: 0.103;} + &[class*=l3] {--chroma: 0.154;} + &[class*=l4] {--chroma: 0.195;} + &[class*=l5] {--chroma: 0.2577;} /* base */ + &[class*=l6] {--chroma: 0.133;} + &[class*=l7] {--chroma: 0.045;} + } + + &[class*=success] { + --lightness: 83%; + --chroma: 0.2607; + --hue: 138.96; + + &[class*=l1] {--chroma: 0.029;} + &[class*=l2] {--chroma: 0.083;} + &[class*=l3] {--chroma: 0.124;} + &[class*=l4] {--chroma: 0.157;} + &[class*=l5] {--chroma: 0.208;} + &[class*=l6] {--chroma: 0.2607;} /* base */ + &[class*=l7] {--chroma: 0.201;} + } + + &[class*=neutral] { + --lightness: 18.3%; + --chroma: 0.0026; + --hue: 67.66; + + &[class*=l1] {--chroma: 0.001;} + &[class*=l2] {--chroma: 0.0026;} /* base */ + &[class*=l3] {--chroma: 0.006;} + &[class*=l4] {--chroma: 0.007;} + &[class*=l5] {--chroma: 0.009;} + &[class*=l6] {--chroma: 0.011;} + &[class*=l7] {--chroma: 0.004;} + } +} diff --git a/httpdocs/css/login.css b/httpdocs/css/login.css new file mode 100644 index 0000000..b9d1d7c --- /dev/null +++ b/httpdocs/css/login.css @@ -0,0 +1,20 @@ +form { + margin-inline: auto; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + max-width: 500px; + gap: 10px; +} +input, button { + flex-grow: 1; +} +textarea, h1 { + flex-basis: 100%; +} +textarea { + height: 50vh; +} +h1 { + text-align: center; +} diff --git a/httpdocs/favicon.ico b/httpdocs/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..040a17f731119f6a80ecfd6391ed9bbb0eeb2a0d GIT binary patch literal 1150 zcmbVKOG*Pl5UmhHP}Bqw-I)k3at$w#E4cL*USS}gBe-yFG$`(}&KU$x5Mp4!kn)v1 zJyWSv6R^WeSHDkBb<;#O{Mv28f0ynLh%Shz2Y><-cuYjllH;s%NZE>ABtwNDIT<1U zRIqVfX{E5I$i72nk1Z*Wmf;-Zf9_-M;qt26NPf!`O#bg)hQ%Fue#?3J$XTzqj^5Pl zWi)4VBi!S_ybbL)^~sd0ccirbGf&F*rFdH&BQLm}@!V=ZjJ z=_Gt#_R;zJVB?-kd!&9_aWD4Z&6MR^``Wy$8}C&QdXMJIc28bbbK*b8d5&J0KNftW lHK{)zh#v2Ve$bJ0m1CWEfELgM>cAJUD1dqmZVip$`vtMm3?%>n literal 0 HcmV?d00001 diff --git a/httpdocs/icon.png b/httpdocs/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..827ec59bfce7546e32bc5aaa563e86ee6a924cc9 GIT binary patch literal 50481 zcmaHSRalhY+clliCDI`!h=6oT|4_Q6y9W`58oH5Iq=)YAkq+tZ4(S*=W@x_Q|DJyb zbIk<@&og`Pb=O*JN2;kP;NiT%K|(^p`%h6;0|^QF>g9ulf%wnx-1-m17qY8{f;95t zAg4d#5A1J>`mRVwxFjzhWTdo=*GNb`bN|VH((=qW0%Lk=YtO-D@Z#l(2uaao6Nviz zvdz0Hg*ZeG^%Nd0-Q+QCW+rug!CqD~H34o|ZIL({GSc1z%nWE)glY6`SUF+L%nS_A zr{zXti|}2I#P7r>car1YKUYB0W5OQKV_!kP1CYq0dd+L}IRF1&KtSjeKTs{ngib*h zBllw!_K#%D95%sMa-Y+%MVoUIoC?a`49!Isvd`vx4I>)|;I;-s*l?olXqN(4cFhseZ;JemHTnc^vv=#kxd9%vM(CU0{{P z!FqQByeM_R7x123oG3WdmpEZf6g3jQOW&@WX;nt(v24$LZ5SRxOO-I;i@WK(5QaYQ ztS|!G z;8H6dL>svRddY|Rp3b8>n4VX;YaV7J^)B4dck#@KTSB^)zCdb8EX}6Ha490G#wc+u^?d{zZ}{)0xZUb76|)SAO}F_|33`*)u?5f(7kOk6EgK$0a;Y`7M`n`KW2BB&a%?c@2w&a_&K5Q zW`bi9!!{ZKSw#%Ccj_)75Fjrx@+ASw&*e zGKt@8ke?PdlK@#BFHoH|&*={G@N$2bm=El%^n&~=8uYEc8x~A(_#_5hTvTk4#AO!* z$Q+XOD*jRQ8jE?`S?{3~Cl@C4FHf!EV5g65Tud6YAJ{H#A9P5$%-zKPA^M4h<^qo2 zyF7_Rxlw;*wbcLa8U3_Nj^KrkLicxfd|iI* z7tg?c1Nq?mDY>!G+t9a-NgI|Zuf6iol<}-+zGtp0rs02fI9sFz@Y&T^vn{U-<7?hS z&8GM&tw+U_kL*AOQHND^$7qtt0O@)=TauK9NUAs~RyS25^!qU839NNNcL3l@Ld=O_ ze+3DArEnIfDJVfYN%)@!_;bS3a@)WkSM-bZ*TFz7Uct?`M_G%wy>qwhCY1ZIhL@yU z{)UZu$S&45i9+!|CsGxYr?@V?n}=7-@UIM?n}fu}r+g|>gmu@^&^6&DAc9x&36PAS8Y z%Qc&Ff>z(0%a(gP@y_|)P{LyTiRBVj)ibA8jvaoygz=tw{h7t{1MQ97&9L5WN0#}9 zt80SysYcJEdsP7DZ&M=0o_3FkgTFtHkEm9)_HAXfG+CuTDSs>Ge1Bc2oXZKgUJtjp zFQ)j`bFmcXB9w`E=6l*Kj|s-4=b3Z z-2SAD($uwiH&omKT*|E8I=>=)Efof=0&#c?!Iz^n9=4b{X06k z$KcIT$tWu1{lrs=3ak{3*gr{+zcLj`dhp^ExPHb0v5GPL+*ew3ut8wvD?2F{L3jK1 zyJ~pLeZqaR3nOQ`U%$ON?Qb{i5Q@xVQ`$Yd+$RzCi2$7NRU>qfw=_7}^9|z(iurjW zzDhIGzfos^afd5;Rh-fnz!t75-(Xwwy7N5M9@*xgVk-4+-yKA6Ql^7t<)!@X!<~p@ z1NqtPsekO-@i7uYip41|Fq$CF^4Ited=-NqIpfv(&oSCW8*0Ed*_frS4C%*M@z6ri z$x@xQ_n3|tlN9uytl-neqJh5WBs>HD9@+a!5Xqc(N_N9FR7_B!zX%?EdGT1`i|mPF~>(~ z9HZ09-v{o=uP;WmMVAPRQ0e&3BR@1BA5i@vz2@s-Zowy0Z1F-(lH?;mV2Z*0i*Jg< zB4Q_p<#O0MBw#(p!A=%g@?%sM@sGAYQs%juquEsr>dm#4ja#8*ZRw3#4pZhItv2$7 zJOWQvzx3NMWqiOq;gQeFdc9gQzW+(}KerOyM65XRtqoH3=06tS=}2AIJrS@Isp{^^ ze~+?V&zg4GZ#%q|+r?JE=tyhVDFvY?!|k{@+MMY=6*t#%6T)?oKD%6&jutM6Qy;ZQ zT6mc8S@i)I$5ixtIqh#trC3-4A?+dLyn*k}1;cC3PQ*C<>_p~#E|A?3VUK8In|xys zyEuo=Mapd-6~rEAI{oYuxFRvA*iqOCK;fJJIfCRYR+fo_GH3jhNbO_GyqN}``DTG#Bjy;u znyr9u{Z0)lqNQw=9PNZ5KdQZUAsM1Pdg=S-LWX_^vYD)CFt<8AmuA^w{c@d_XD)d< z+>TTi6ucPgk#@S`DC*xWOI^X1$JGV%ga2I-CAam={!v8+{$;BFRlTqhc+0-q)NdP+ zW!X&Krs4sPvgLBhY$hVLFsq15^hj{O_S;^`3VoRGH-%U0s=ZmH(O#@r@uRCoz^H;O zw#q)j!j48w42pmBu*p^a2o>UNcjh6k5@G8u804^vA@{lH8$^w>WkM7_5#+_S&$A?* ziHp!eb2(GB3GH7EN7v`<;J-@(zbprqjXay{!@LQIt|y0<1_vdeknocZ_b~exJ0~qHO0L^O@x|o?$f4a+RIK~%v~{MzDO)UMp=_4g|!3) zz27mTc$&*%=R;VB?8(WD$4S?E51y|YU+stcB6b`7v;y%9BuR$&mb3q*hxf!smxoU! zxEur2byWrdH6lHg#ED0QWy?__>Fa6x7TL|OrsiwxJHSKB#Qv^GscjV4&c5&vR<*Rr zv12#|qRRwRY$$3_@DMpIzMJlef5kI*#n_~H7)CE5W5k)+8a}#P+gm0AY#GjORm`cq7G&gZ z@5wWp_WxEkLnf&mN(m@P>Vl2|PTD0&n2CipDf0on;L+!iW?1pMWGeNQ^knXb#hCb^ z#a(u1aQA60kdr^ZsaEZy?9lV_>!U`1<-4Z(nEy5Ij{osdr+DBt`ka#!pNYWlq;lABTcRC6zzssT+K{YRK$Sm@l>Eqvou#@mBtM zu_jl5VFVRC>Fkz>To698BJDLfhJk(}R^nIljk$#k=6g>+*UE=g0^k0TI{h*C=g%MI zp$W}y!l-;~8MM12^hIWrlg59wJ%w=64GP2mjtKT0PVpdr)nBS%+zP+H=C8qZ~HS2H`wie zQiie){+eW|4bh&WnuYynU+vp0QOId-tFF5(nru**XNdXA&!mXvnnoifn2(LKy(ClQ zknvOO`T?nQCOf3*YFXD}Ry4SpZ<7VGUiJc4=?yg{8^<@djNds^`b|1yJmSWn}f%59qXtKyoBPg3MLN*{W! z0gus%qPk|c%QDrjU!AE3?KWWdNr>}t%<-|J#~*D$M>HgXZ1Xu^S9+p$a&5dgv~kOO zW4}m&e=bY!)N|6P%;%|IZ`vl6M9!-R5rwZTuJDQ3Hp!E2TFZ1E&g8Cg!xl z4lJ}sMU3Dg$XY)Bq7jtPrt#74u?_AmJ#P?X{V!rwWC5E6IScSd--VkvhJoV-6uSy) zX9H$p{iyX;Z}Td{#K#B>YOZ_;lXV6pc`a_uM75a7q^SR^#@shv!6Y-+$&vs3hNy}j z2wcF2BOXM`_7+C-$pdk2G1G80tW2e)Voq20YS&Apw*boHchz?_o<~pHzKMN6*ZZ~+ z^O^@eHhtQVRRn8=_k8)5*I7UA>T^$!4YzxJ{G>VSo#{J8lKR1S^HOomU)~3GYbIOS zyN_1kTcqW93N&6ixg9TVTsLnvC(ajf6p;$%Q4B*DQTLTI9^G(e$DJ6uR0PrO=HNDZ zS==XjVFeyLoBt--*{q8ZsgNS1c1C4^XIp#w$&MVs){yTA&%~`7A`SkG89(0rz(D-3 z@PO03W_)45L52Ts#;8@@rGy)+3=sU|yAprZYHQ|JG&Hr+sSVRvA$Mlf{>Qc9h1Kr? zA9`G>JO7aG1&0+s=a+;_tTeqGwZSo*l^&i*<<0=pXrQBIp%4&1(PGBuwHc%~b5ks_JJk$ySaN=fXA_jRp z*9%!$jd*MzcyWwpB>;<-QZHe#>gZpurqLfC`5R~QK%?OKl?o9h;nFT& z6?jao6PQEvE@+insmfkReg~g0(&X$YcLOcN@t{fpOlqBZA;y1Xlcu? zt!c8G1LfU)OIGt@;%tQz?l~;AD>YXp}VI#MlBcyW)&FL6;- zXS^E#6|F8>|tMefaUSx<-y|VeQKxntA;XCVm*PvsNm5o zWv_kw3G9^|6`(@-VZP#y(^b>i%(8BzFxGHZB;hp9uxFtNV!u!~7B1)=!9dK~HJSi) zK>$Uh=toOA=JSt<^sFS{+Qje^@PoXWXJ%sG?l?>B63|9C4>wnTN&b`-rTH~0b$;yz zH}aH!3&rPW!l+zlI$3@9caV8B@+fWGPz(JEIh2*nj#!y)Bd>@1@yjDbiQBzeA*t<5ueQnip;C0oP-b`m&>A^G+Kot?<$w^os>(xO1=htB)%X2>x z;@l%9zPRDK@GdmWGf1sX*s)l=*L&qA(82He=X0@7$Nnc-feEs!z0yEA5u8HIPD^9U zPmg!UdXI3k1(L=~#u{ zW7pmbxpfZy96zDqwFJcD7N79|H!XG;Q9(df7MF+FwpUhX3!1uvdCeTM{uS_LI}xhm zBF*RU^7SH%N1M}4wTD?i6%}ID7(ZCV&8AuDOeY_si2S`ujtt2T6K7JGzWC0jLfhQ< zgvSg={&c-c@Az7t;G9dB_vi*v6(1-j<>8PYnO7fEN-Q-}{geB8G48GDe1IRDGISrd z)MZ{{AODTH4zJPgv6(F{5~>Fv(2IPf5j7w;mQ6-7GfTYvMg$ae$GVrG zsU&X@M3sHOH(M)u9BWwjRC2q_)a-qzAOr7gMH~@%22+LH+nSiVQ=72K(X;F-Vt&z& z<5D&)gq%IYDwp>@$l~oyArq#agIBHD3TeJz{6}XJ&zDHYFyJ9akqexxz^Z}?2%hy2g}S#K-vT2jrZv|jAo=1 zj$*Ei!UG{*51fzQ{9ZA+CXv^P9>%H?EXE|eQdw(p^~oxND{gh-;nFb34%7XqOm#A- zy-v@M%P|fbR`7fncZFF;xNQ8M-n`sGhAE`--&pFQqn|^&=?Nb2dWj@x#mBc=SL<`f zItrM|?tWKT*6#~8$%G^=S%5_yikbjB(pwp>n>dKyoX5Pg?34d`H}aT|P0fRbQ2Z3- zyADs>5?^AXWv7mXG7?HiAEV`akr#M3XnO5QFsjW4oLqlBb>bj7*#ym?34ri}u5S&o zFQz7ug+w@!ys45#x)u z_;P8zauw)k*52Z< z*Vf#EQ>EY-x9n?ltjn4ZSIr>meJC$~>3fFw@#5gp5@a?yGq2shZzn?8Q|Kt!V>0{e zV=-&aW@{FP|NG9FWTN;nPXBrd4R=?Lu}j`9xAdKiuQ?G8pXteQ!k@NkM5?!Xb7(`f zzv=bc@=cX@8#EKI?#;#@^zRo#c2%uMP$X(P^VPU_o*-0k`H=UkI>1W6!xLSuP#$>M z0q>YAt{kvoDGTkd?fkL(+tCI8w4GGH)U%fv zjf=@x8X>F5UB3P*MGSqHGN15ouEJ36e(rAQO();XJ}h^rPe(?X`S;h;gxeVAjI!gF z@S*tw!{43_6>?RNIhg_OgKDZNB*pyY*Oyj*3zAhffOm@D=EtY^u6zo11}z->Q8;h2 zC6zQqo7$dUUdu-M);QG4w}^48c~x&>g)(3Iem7C>bDVmdE%H^^cU7a0e~O(GCuRcV ze#KN2rk%ChrT^%x_@t6WWuUivdDVWOBQeCPPoHVqDYk-Kf8doq_zmrQbR(cAeD>3h zV!s1x-f3RHEW5!BD*J-ijW+eL;}+~q&r8LvmPDkpS0Hp;e~3OO=j65a{UBkJvjTlw zlU`#Ab9R*Q=$n0@IHpo{&28E3t7IwxleVIxoOcYdMeo~n;)~!Z*GogE+T8|!iZB6w zl{qSAK-Vlo%X$K)?0M9%p*G;wC^0QH`=#}!T4>-hDw$`q%ove#ndxkaPmBNC7#{61d2OLBy53lkPV`{h2 zC&CQ`_lVk(%ulaC>V5jB2=75Ga6Yt_t(-BEasMgW+R@Yrtr3tKZ%LP&X9@nxD1yY95}YS=U$3! zFH$NeIB;3fzc))lU$h3zZFP*WnG*>e!0xm93$F6@+TC3fex&vWvl&pGc)}Q&GEF7k zBZXS*^fQ$jgQYKC+b)h6xF$TG-?PQGVUWE;T{X@_(3*BwWWzz~H>;0&_wz1_&CODt z8|R}j-zlM&`#QvPF5iPRe}gB-q$D9~6RBf&HEMsg2Ilf80_LN=Kp_>veRI0X^>HPp z>Jmqm;)ALO32&a`>{x@>OGdW#o`)8<_O@HWN2l8U=_eP~T^Yn{S4d>8bcU$<$@vF& z-^x~@y#K}7ZJ6hlgT2Um1$Qa~f_5S!L#lm$;IJ${m$ zbk=6Oslv~g*UDOM;#yo-ge;7l-6#c%ZE%@w_`a)rA=3xkG^*X}mZSAWLt{F1`I6s{ z_uAYjCW3CWrhpYkPgR1;*94_sn2iO_e9D)2>^Yt#!2EtX49C~-qL86D7}m7ZmcQ-4 z>kc&Uq>=-)Y5f>Ao}DX_wUO_9r@n5deZ0Hy8*gw^zsfEYZu&LcoYTkkHI8|e3kZx5 z1Bw`qXfI#sh-C&`eGb^BZ%QuWTUZSL3V6UOJM%_# z@2?u!dw4(K$~(7c%?ek4!w=GpTHwvEvp0XY0m6D$KdwsXFi(xP32N4dav4k_ z23dp<1(sb%oTWMae8U`kL(=$X%HFo#p5+1wd};ht<~q+e%$5c5`qt^?HxIKk=WE=Q zerU{xEJQgof2RC3yDkqJVDC#5zY~*_BkqX_s-d7Qd7|+B#amTiQ3wa1+{xxV+tRJ) zJ=3jjE1v3Sjb9!o+>nbY^%>Q=Ua%w*z6}EoX?rk-%ig?gAc&ojlMHH!gB>VFM!q;79?Q- z)wQ=nVzj!FeEP^DgRR19lEK~o)Rk$ZSQ=|+J7&BqtOk$qTFP+-=lE-?EriK|?}?JW zdzE~I@*L8qxJ6$3I#tNE7hI$?@Fzc)(JthbF}VFS%ty8-h+D<_yG-jJQ55bcogFUR z&p+APB}^a%iEBG)0<63mcVx34YJ(59)lI4<=A1N!tLUF5zE#yf3oo8t85z5;+1!fz z_RX$%8H(UxHM-CFfz|%7;{lK#`BW8qbzOd*Rx$_5f8R+k?(~tHJroy0no`kMt3@V& zN#3;>@)4(JbzwQTmvY}N-a#!yhaFDz0z=+KC0K%?iBu4#omEO{M7V}vX7W(HHo~bAD_s7>Yn@wcg{^2hg<9hP78V2u)VW1p>yzi!J zWO{^v66UU}kveWx=lBziRlV;bO7H3QkLc}gL6GyMs7Vl$_l!u96%|xbg%|QNUydE<`_Jr2 zmNDf^(vg~#o}$hS2zGrba&aCra{N<>KV!029cbhXu{1~wabBpHL|*BdsU z&rU{r?g6&9N>PlVN){*%NQB&k(^7)st{}SBoL`DoENPQ_?ADv9Yk7FT*92!H6&fLa z^2v-nMlF~%;`2-g2o*(~Ci1s)Yw>`8w-lbpO9V)yzh9;eTLYReJYC5^CzThnC9u zRk^BwSV52I(aGNxjAj5;8P!-~9A5nNQhOUKiGdAFgleAPh1AKh^dfBZh2fL0{h_3c z*)cC^yG+$hWGF>Neaz@wL2JGk`i0Ua=9>5Ehk{1#Qu?ehJ@7aR5#>C(WQ^y#F?u() zyh&H!AHS1|S~toBYkW7i7ARau@VQ$_6_U{NEP-v8eqfqG2S&eV3ET@we}B5hXwSim zZArPcENlAvN-M=voq(A!-jCb>JvVFbh&qBSLQ_;Dca|%Tg3DR16USZu{V(1%aJub) zm9fsE5C{untFg$WN6JbIQM3o7F%8F!Mx8#wAv>xPXkxHu=GZT7`8aL^Fw{k)WJ33m z<48n2%74FC%AZYGBk~e<e(S9no^$51CW`M#6cuKcWaEkrFgC z#}I#;qrWEfNo@3@Ct07+#Oc#d={-q*4@vw!6J;woAss%dfPZQ#xJO^Vp$aH_qBuO? z<9}a!9XwK$o8S%=VRjeiY{c6H>b2+S&iVSX76lCLkhBJL)$O?q?}(` zR&@C@yZK-eWNn?je<2?aJDDhygri^m0)ooe3*qM_ZWTuk!lBCWcnagYD`VjW;g0a- zdD5sIi1hiC+V(TtiTSEJU_wc18AIgwuY5^-%UZ`atiqBQ+2?8YT~LF15c?M8a>B&--RZP)tt*oCgb_dbb!nztfTq5N^UosHPp*5c=&cdG14v~cn6etEQ?ApGU zQZZU9{hfnXVXR=B9;m|Lv7}H0aQRqJ;rQoL(#(FDm-A^oPJQgxf?oOM`$Rolwzm>o zK4yjJ#~d|!ETwYl9&e`$q~1tX{yjsfC28O%>lYregM8j_LlOBT*6zywvuZV14*p*5 z1h=kcH&3-1p`|QPxpLv}g*EvqgSpI1zA7ZxLJFQcs_zO8^t@Hy^H#10_LSi>Ra!JD zUgd2uN}o6Td&?k5H2k1N>uW73_+;Q%`E;Ddo?KS?;@su*VdRK&T)rT>FiIwz{`Zn2 zseSVF{9&XS*LH!!g2m4U_mL(qGW-i;ktpEWTQOl2=}l!Sxtj?`Bt~*xCfsE}mroqV?T0#RJpz(TwFFvT#NbHaFF7-8ee(;PczZ7!TtWu ztmZ-9yk-T!l^8`Er=Zo3(-M_}&#D1-H5b<^EKbT=val)6GRVfgDgMO1{abU?(>~dp z^P~oEI4ich?^Bi8e1?Hq$|OG7)31OK)Z6QG*N0iX`DdnbpB)2B$t2E3D)W|O1xJvV z(_uR-(t6#iPUD}O^ofKBV;bpDs8l{A7Qubr}S35-s6?)WA~ z8}qCsfdm9&xOXUXfhrK*)#nvzHW7S$G+Sw_0EEwB+wO$@fpj6J3 zH(yxn`g)SEzo-n~PD1SB*C=(>e^L^fc~rzGB7rLaKZ~d5joM&p@wgO)&6;0`tfjYS z$Y$cnF3|Seq{yp;{VOUxB?Opo^sz9olFBmz-)+2}gl?rN6gpQQn1We9oN#{_*jhY zzvoDIVv_I){+ZFn+}Uo3vF|KUK@jg6{m^rX*0i`a;p;>R^|>Gbl1&;sOXu@qEDf5v zf&on4HGE<-Hd6h9y@If+=Yo&BPnKni8ky_%TGGU63z}OZFB#fAL3*#itj(0!Sc7g* zAPH2YZT_!=;=OiqV5PX5bN2EiCbZ^naQT+-QZtOtI91V?aHU-WAtmXMHun}HeCiwG z!sfL}D3CM7Ty45$6Kcw4f- zT-O@^@(v5oS<+-@Na&F)e9J<6Rr(=EXUFrZ0XH$vdA zm0|LjIhe$3eI--xPG0Dze~UrlKRw)3y$P%jxLVfypX9%kXym)R+xtYBUVyl*zGc`uaXrY+-0d%?j=t0H3< zjx*`>8MIN~8QBAwBi=`&=L;MFoi3P+uJOTxghR)HSVjaV6ZiW|`=FAa0T*QZeUW`h z*5aZRlD<#m_}qWGj-gx9#Q6D;{xBC+hCOEsLVp{HToD~}0H3h1nyiYJBn%z+%~2;n zL0s4?TgyUOswmbLt<>aa>It{CN@QM$uzHeU{F{-Fg!~7I*RdhA4mIx{gdtU!w+P~zO=vBQ=$>2GB-=(dF36O54a#W-HcmY{4VttIXyqc!@?^!-px zH@NRZK`G^z0vx7f6QmfkF@#WViUN)$S$)vLFZ|X5v~5tvoX%82;}pbnzjg z_q+0q*I0#lGz*Z1sgs$XbVHt(8FRRYVG>;aHp4xw^dNF<%{rSZTSr;(;{@;`eKCFY zi1j^exQbI=zRCx8mjrEufZHAl1G+HaD)26w&gb`6!?IB0?-hRph^(ggEfexo zx9!CdxO`y_#!D!47<^p38jZ!f&QZyuY*~y(zxw-&v)CYPriHKOH9RFl_=h~OZ855An?j}N?*nU| zZAy)wbGi4m)lYd(LSkEhL@UVm`{pyuobPsQ_zEUbTqf<2iK3Tm!K+_38HEl2HgeYg z>HMja{F2F<+5y^khl`@Sxs-p&j&o#=D>|h1Fs;8{h=$4ON&Kl=o=1)Xc40}5A4fiT zVA2;t%{J@|swbtYc=s}(#$52ofTkMEy-2B(^SX)~&5~afmk3;z!k8;VQ5qqH0l|UWxsk@hg7{6>yJ2+9IV6 z+4YB7d?b0}#u!gsphS75r!m}}!VGRVm-h2^4C#>aAB?}#eAXLbBL+|VI8J)z$rK>{ z0>qW1J4djae6klSO1q`r%VSDgmI5w+$xbh@YmO80+WU`V>64&BE_8YJKH=6h=}(SG zcf$?<2juq$H)@?WMBiiEy!-82G`AqH9CZVypeU(bqMudBra+Q?G=R8eU$kvRA)kyD z=~fDdLtefRPjzTvwVcG8IH&2R4$&xJq4cMj7`^N9K*hr*6_pbl0hm|J?Ig-qj*Rlp zm9O_%1HAdhr#^xvex#lK&bCfM==w(W-&SunY-YM=j~xQ7+USb0mB%`sLI*%oJWW>U zT?c1a2L&MJZajgol5aH4f4U|o3ai=zRr<@X9MIhwLO=PQ+6mzm8tMmVXSIjtt(XM% z-pvvh*niY^8ssB-k@LGd*5Y9)FtVe?b}bJPQE(vnld1L3^s4rgO<|OW0^;3N|LD#; z)%D&SVMs1VNBiSvr5i^{IrxOD~DD$XxhBwWfRd>N~+t%`r4^&?_d#X|F7ffR&n zB&kYYWGQ2`S(JWhnsL20;At(OjO}*6Qm5CT!!}|cL)YQ?*=;PhF^Lp|e06w0TKtn$ zkdcg$pd3Ry>%H*Ab@uOksVyN-RTE9k1GbUORaPP>!V3-?YeDaSu$i%QB7*fB#S}b0 zVby`S`WB)>;QyJj64V*$mv}6+VFb)_k=$=5dYFD%rP}u{3km?RQfpu@pJA}owZguq zb(6bn$seVRsdJD+=W#&hRiD;GgX7nnI#$!P;uIU;LoylSrbYlOYgdxu;z%V8CP_?h zi&;knyc@z}gJ8VuveffLq~zO9^KxHjB-r>-=y%wNLupS>V}}1DmE=k@icD}Q`~dt= zm|mzAeRbL+gnZRC_vQ>3Y%c{@WTP&9bE#ML_g~*s9)a$liGlXY>e}{Us?& zCG3zg9sc};J907d?&oZ@r$~}{iZYdOz5IN^bj_H*SyD=p&Pah>gWSS}FSq3tRftRS zX|FBR5Wk#Nx^E{K<*3=-$n_WxL~=npYUkrs&5tzFG3S=llB#R!y?eLY>r-*-oi-EJ z1EMh7yJ20GSbzCN?yPQdCrk$s5U~ML?XX=Dwbe#Gq~@I*k`bD02*$37xxN85JaKq9 z9IGGl4|!M#e56%N60Q$4)Plh`til!F?Th;*2n2|KH2R=UX$QEh9uOlXyom-L5}z zHVaZ1-1~DXz~{5voMQtHZF(B%{L3exfDiK~)M!!&+2r z0EF;mf&*9oGg|rI!iexIY(st{NR@2|kKv?BF0gy0pOX!e8xL(m(=?gNgBXXp13>~> z^7Dxlb&Bh|A)y&#gHRcEaKh>{>X#N_y1ewra*x&N$9WX+=e7LbG3sXU2+_J~pfxvS zoIgt2A;kATqw4KZqFk8a6}9vG@Tx!fT9D&crYeKZq}D}W+?C=#Yqc$rAZ(xSp2%Zq zrcdQ25oVAq&|E6^G7~_0$-&~o&X2Z4EY?<6ydzD0PavP!sesn%NBb2S{=*cPQSx%C zr`+Ye)ZSA5?${Cni^;9?+}U?s8QsST_97`zwTfOT1ciQ*Ofn#Mvx?#~hN{27yZM&1 z#>S5UOYR+N4bKCmtR!_*q_Vg{=MzeH3*N*Vn~hRha9D$78=TU~<&STre)(4h$A=?v z=BowfuWiH=$1FUru1pBrps&ZAa{7i#&+a#A(`ydekcGNIK@>l~3FXn~!2Bs(L6x1w z^etV{klP^dK{d?%E8oIfR~k`d@Ry^!esLZG(Izt9D4tCuAF`R|H^Y%Ommrv-?fH=VBUg!8~ihr z2T8HNSxBV}PIZgh7;A z?X`>go(89{mx#Nv_p5@8S-%`y(cP6F`I5?idth?S0^p%4tfOZ1($^}y%_4~1OTP6Q zXtOMy=1~zyyMP&8$+Xi%AXM-p@$tOYlI*$zkX%`jO@`p{lktPsPLukQ-DDuIdG*p%NTA9-g z)-VxNBU)||KXdsz!DC#PZ{O*(whAmjbHy@YJka6xPZKlx2zb5jq)z5O$)K(VLXN0Bcv6T+U zZIzA@n<82c*b~QRqMyJ+Pa;}B27!`(w=DQ9#yhYkYp|r*=@k8TsAMRmV3oWl8wV!H z`(U=Y8}Awq2+27t#6I#Uh5Y`nNW|L=vXkCL)W&NzhAI^Mf=OA&F} zxVCN#sE=2z7lh-S9y|)|sYmnen{C`R768@6b3HA85_e|fz?&k*Gv)j5LBw9^q!NXX z=bFf1WftP8PO3K*97FT%MdU;9{2Zam^yR&z-clE77h@7fKZ|Hv7MW4if!!gmRh0e2 zoa1>0kJ)2oRdAHkk#-U}i%n0|?QR$EDSdOMkJEQi`s))bJ@G56qhW-T39&@~jfpgo zfyZje-Sv8Wru2x((!|$A@X4}kXI=W~%%+aN;8@hz%W0Rq3&}5yYbNW)?U^`ih?9!>l~R_3GE|J*Zt)e zH$TVUP_m?IVEXM%6rY6N;-5^PCH-+Jnq2m9Px2NvwW-Rrk8R7 z_UH@9vPin`9-}*vdN?H+XyLs78n%MHyJHMGK;uCXe><_5*+NI)#N%wgucuxRa5lwf zk{O2ja!a-fahW1vVcmf0Nle2ZBFF3^SDIg0kAw_7e?6Ob?~1E;*sJn^^;EPlhhuAc z^>XHn>|)I(yqH&Vc#4yoajRB|(;xH!_T5@EYR$opCgJf=FWOl%&6Z8vI;kvnnMSAKSJfk=k1;7x{JI{>q5?*}ZDoV+F-qn#S&mziVVY zP-UVp4rL#2dy5ewg=dn3)%PIJdl~>+_?12(+n}r`m`#UYL(x^c=710_%y<0P{zB#f zeFO3+`)JN-@Z;m0DDP{A+LCC=zbjPxZX;+DB|c6|{07zUU~eGf@1nFVWY?I;pIzzA z{ahzX@&KG|t{Bb8*QO0LlWJ>cgd^<8D$a7nBknFM1Hr1!xSYOXw9ADEu20x{mKvdm zwh`TXGWRu9GTUac4I70gHGR%tojCB^7x9a>Gl_6ozr2s?o$9V^5>L1u(vme%H}?=? z62ks4tNG778@hsGw#ib|wDN;+$FZx?#yC~_9!h>y*G%@W-vcw79^tauOVM@>VX~u< z!(N^#b64M_YT&)rj#9aYqPd;yoTMtKiAjYm%Q@X)IT+p9UGcGPHDMvHs<``K68=Au zu7V+|t_jnf(jcuM-CYX^D6L30NJ+P}G)SX#EvSTa!;(@9Qj2tp)Y8oYOMI92`vJ?D zJ7?z1%ro;0l!p1wyoJb^H?-L|H1mt+6cL_aDCf9ICYC>btE(ocIlnrdfW!~m>PULt zUlutg?L>b|`}t$7r61m&icGYttD&-`uX+uW$jT?bmM=S1YO~aE&Gdi?{h0tKCq+YC z{yKpg%fIH@kndTG+h6;ClJ<-h29f%zblPA4l<`u%V#&k@M|1Ff4RwiL^iIviJr_6J zVpllaasIR<2e4}zTnfzfM#+K`A*e%qn!j4&)P^Rd1?wbXxbLN9r%@rFZb(XOT(0L( z3?b>rOO&UZFR0_5OZ7P;_hr5>)tGw4k|BSw!RWeoQn!vku77cXXf1iR?4MxTtOa#? zlhHef+(sF5vAce9Y1gE@B`4Q=UbQ%=D25p8K(+|MWI}nL;9{$m#`U#S=(808UBKpX zj<`*Tn|KLku)iJuTd52j*GU&=+V7G@4&Z13SYx!NxytVS0jIybxlU*3pbK%j^;Wd@%rxWrDWBsJ8Za(p|Rm?$j=-7JZ#2UX3QOMB2*^pH$?|#gFVn5K|5cHP8 zFDLRkRruCJ4WItIVnAsB=5=@x9@9Gp_7AZ7EqaE59?3E{Cqw@9j#Wk-K90gSKc`+6 zD~zhn#(JnI)0zbkqLeR!W+gCb;U1!pi{PdEX}bS^h2>(daf;`e@_2^_=K00|ffw;4 za_iSWTNNVvGOp07`sE$#$I0`;2Nm%?&v8R`s^HrqMqBTB)`d)>G<3uB+;i6T@7HJA zl&;`CoG2N+beD($W2p0@ooibVuOatrB#+SHz#}56kg>wpYUZNhcbnz6Z z({#21zOEJO`~U$HTXUkfHqGNa6lYYW~K+JEKYIuGX z&FX^S)3q32S~1qo5>-kgTJrd*D}JK!V(N8@;9yTmLV{cjMa<_4mY+BrA)r@Wh~(%) z!t=LjYhxhao`ZjL@x7R*Qqpe6Tdh6{%6j(DtaP)3-TfIzX{uZqRJHW1i)oK z@!l-Pt^XxFC&4}cnz%XmP%y4N$VzWmLaADT`1|jnJzSt`KGE`GzgUnB<~<_?SSMVG zv*?WyXx!hH7Y`VW43Y(|C!%MGzv5<}{+X!`sQVPB6~8nGMC{G@lNa5TsJx7;OB7nX zE9rH*q|-)j)f=*+LW)SY&C5>~I~Gyz^kG#NuE6pN%i?vg1`pGEk-^V%Ga*fI;)%I& zQyRly?x#i{Inslt7=gyHs>O5)N(rvjdx4*C@X(j%6!m*Q{F|P8hjwi;eV>-7bLri7 zGO2&3{hQ{nVv=koF_2+(8A9+a45J~?4^H4^_|q2MH0dopYZ759qw24fOvh!}4DNKt zp3d*QgF%k`0NE_faPi_1M$_9|rEyj6*1E`9OkeI^z`k4lbC8HIpmB3~R!=f-=AVc5H*#cl1PgMw$!pYhgW})UUl)!16X02Ez1!~5NGhW!7JSF)Nv65Id+MdTn;^#F%FQ8!C#Z9Jj(jXk3`wr6qpyuuh8mjPsZBS*(3Y}8_5qgEV-u+C9TuFr)9@4^ND?`&l z;+0Xw(k$~j`=ldV^r2QjU!I_x6%nT*m0)wbEG0();~FporrW*6f^RN^o5!EWZGP6S z6(q@#3nHfRtx|X|h3oakpW>NN>!@B0ffrQzQ&lIWO|Y|#PL}StvW5A%_hYa-Al2AZ z{Lp1d?Rzax=!KTb5(qys?IId;Zf6w~OX3t7p|y>>>rV(E?lE}Harx^V9Rv7FMv zD4mkNxF^PoU(%I{OIk_q!AJ}U+`C%g`0JV`NUO5~?>Zzq65!pp1qB;6s#ryw6g6X6 z)nT4b_@o`70&zG0twW;Tnx+~`S=l57Qv7rn_KT&93l{$jeh=U}X!S4ar z+HP?f475Wj_L)}$&30Qo2F`yNASxTjwL;MS;P%p^&vNywVTxHyPm;#eGre>++ZH`T zfgHm))=i0-V!cSei}atjylZRMiu|k8+!FWxt;s!pJvF?-oMm6L9hmRW$XvQciRX1k zgDax#%Xneky#6FBoEbWHYHs-;Mr`SKq*T>@2~@+pVwyLw-TX3DY0&b5}Fv`)G-1-37^4O&ok#i`0&b53VyBCn}NW{tD2i!$*w zPi|_3n?y`R=pl-!@5mYM`P$4)|7&;d5c1y{*p}O{&cU5l1`f-*s_fAFVF2NdQ_rX- zg?Tc*{-x=1_ggZ^(($}8CfJ>mP4uF)_7der8_;N|>?MoaJnb-Fe0 z(y8t8b%l;4s+C3pW99Z3YscM|pnStdMFY7_X7vz$nr~r2Xr_w8z?Bl7uYZtjcZuan z90G|D=SlY8^;Mc12EQQZ`4Fc8)b0a~Z7EN=5inN8AoRRWTW|nsN9Xp*#DV*eqSjt3 zkzCL;@go7Hz)tUgL@z0rR>z;WcW?l0ix$3-lA1=GDl>9Nvs>ik{QdUhOM1qWCD3(C z>DsDrLUeu2t=5a*_R$5h_f&t{C|-fZyrbgn!8)lK$4fMD1`t*LM|6jGKPjpH5i}L9 zXWX=n&W!$15pOE31gd3SsfgQOK>`<17u2M1)brEYF`KYOE4=W31$hHFLORnn^YmQ}(^Pb`BOHd(9Y;u_?< z{H!w2Q~YzQSAzG4LjE=i%1#6gn|VE9FCC7>y!Xt9KeVH-?7-zYIVqdV_AJS*KOCZhk=ns~W)I~Q1gab1&_JFFX9fZ^0 zK*JM>H))u5n<(Z4N=g>Dzw}EU6=~WUoLw?~H#2 z^&NCAPS_ILI){c#>mLjW7A4Z$Qum$Vh?F4}V?LSB0^U9}JTqGOf|?z8_@N^(f*ppt z_1=nDX6vwjj%K3wF^|skT?3tavvB7Ez&y%p6q!LWSea~IsLMzMaeBU=mFd$Y;$(}G zbvDR=A%p)p#q%P{AVjkM8^|LNnXOwN>8!&r5VpcWP4Vn920$&>xG_A#WPXhR#RRU` z&4c&+LQ2XQArZY;pGXEfHZIEu}2k0X=7T*%Q2qI3s z#-xPxqtKyA(zAX*rJG;z*MQ=EUxrZb$?Ybk0xQ)D9Q8&iX9xpugHE^-Rv*q5EGYt8#y^Zn@M)4zZ=VQ;n;>@9);yt0st3w411Ct*`N)>dO@J{%oj33Bcg z_^-wliV(W(x1k918u5ukOxZgRytE^9@Ttsp0RVYgy70XcYX##qn(cD_-a!x*omr{M zz+;VOT#V{+>Be4OBl+2%N_V#8|g0?wP&kb~=8LEFzrgnT{C}Z9g zg$4auqTB5vfiv*C+6OY&qjc1&M||7&`E0LU8T^uov#Xgh-L*o=Cd|ZMHCjq2CyB6* zGSi((-)gM@YI)S212j}Bve_r_S+zKuG;V4OwT8J5?wf)lrz$P4etX&FR0aW60q#eCBsHksFi$c;CT)+SuZvT|XF%#fD zOyvp*(lq4OkH`ahJQa(xENd+2@KHV|Z~*(o4%8{exIl&Op_!U%aft>XEf?r90y6<~ z0ltu&3T?!^TaTcS_y^*;ZovMxzsv9^lsE)*&&Zwwg6iH5o276SxVsQK3}P^Br-xW|*j`tSW-A};0xE;$~7 zGr;TWZg1vpc8~^B*7Y*4d486md!ZRyp)-Pf@i#ib!IYuA`NXM=WJaKuJPrD?LL{4< z?V8=i5n`uB4Zc;zp4n?YwS)0`S%>RJx=fd~uWp6Qh!eMFADeFkBOSco zh>M~?*@hi|dejKl&n&bLEJc0LK!sG{ym)i;`WZs=7qFxS@(9?T@!~yvKFP;-opZ^s z$a?~J317mv8QOog>KHW_1Y~U(EF)MFej6CJgq~~i97iB@Q{U4+JRA<86LGc2Pn8Yj z5J|J%;!0TGWP zwWAT9OZns!@0_KGimK!9Zkd)9pUhHdhy6!^PG?-J{H$2hGZ%M24@*=&z~-Y7-_Gs^ zN?mN|3vju%WYjc8akT&$oPzbPji`ebrmKXTkgIelhXnvRUml@*$s>h{RNJ}ZU)u&B z*L_m1l>V~Ss5F0#wGg3&mk2hFYjx+Z&XqC~i*U~kS`nu`caWP>nI^D4;a8U{?g(Ay zoSakFtBY19@ODA#(@r?~tyT~;YgIvi`*U$Sf3vej_Qt4|P^b)*D5T~gj; zYD!G^Ax?8crx@{jecHqm^$gO(Dn_=_ZduiV`qQ^kkdm^aC6n2g49>FVU_fuNAR?7~ zu|hSqJ9%(6?x7_P>VHE~+;Zg0L;nD1^_B%3M{vx92t5!o3C07*bxElQYdqL}|H)ObibJ6j7~Vby4O8f5 z%gkYbTviCfINFxmmR^0lT<5Lo2?Ht^o110vzUNN&oKD%R=&^TEmT#kbz3NMh+jbpG zKa>g|@wyXuVQ)bwyHwMvE!B|GA}PH3f^F=u;()jHcnWn@|W-z%x@?(7kyXKm$OT!!*#WK&-yp#aIbcw zzPW}gUq$++u*TKlMSAcB*49dfeEs;2a%$?DmnX}FrFIrVE-dr0gPH+K6k`2XFj=c0^`IvBi3y zY^ed|{-t-fWLMEpz^h<)+Rlz!@G&p6l$mw+rf5&a7j)*rW}QgnO;;g;3jg}-6^Kd) zPI<2RizvTt;8f=|7(o}=R~0Q2x>9$EzI-{R+mjP^eX@gkDcg$cVMJYemQ%$e#xCiH zzHH*v!8m(hrlvc$T){|5b2_yc^xZqKt4)l)^=`Fyt*?BklgMCOYpx4IbPAP;0tN#u zJ**eWrv5{UE+oxcc7=)EkQgrV@Wm7zY(TBT*$ID^Un4LzFcSCTab`_X~uY? zoDv%R0lb$irEIYGZlw*~J(=tuuvr!LTsI`^6TtSdiGkFjo);5|k1}kFa*vb$E-{n} z{;gNp8C8WLbvMhZs6>-(Pavdyso&hOGm)tOz2nrQ@gay4TmYBwSD!6(1P7e^F}{jR z@(vfiV)>i1=oyhQ=3O5QY?`J0JgsvC&6U%veyh#-CWsJc3mPqsT<`o;6*>UQfNCUh zQc|D_&)MGDN!uiBcJ+h;wyvBT>eM{Vtp82@=Z@}HPm@7nvP%W zaxPLue#qT=fr@pvb!YBe!}VQN+4|*0x1?{8PuH9X(uezI?}d72;Db9O^506pddU~? zPEzrzqDp6-Y@!Z`aixj`SeH9^a5iGq%0}L2o&a47C&xu?CbdC625@u{}ybGwq@Jb?Wm1E%Gn(MWaZ^0L;zKTFWUvtLn%hE zQAS+nqRQrD9<6i~VwHzK_Oa_zWRL@W?XBEVY{!{5>bH6I(N@wY7^+jm3$j$%7a4?$ zVdzvXRSr+7-hRB@nFqihhhA#O1Eb4(JMqU&qMoCG-uG)MvjIxW7m({A8cfT|)>jOz z<)caTR_}(r&W+TR?DfRN7)x2GgB_Ub7cfod60ng-wsJfGRd_uUn;&c7Dnp*>aU{Q(U?-ZZ~-s8V& z#keW^J@!u_&&9=`?o;YTAGG*VSQsFvhc896?)+F>UQ%u&ICr!2$T=j5a-*I0H@)`6+Uy}}q_yj`|b(#*I!fZ$6RZl*~s6qbhrO@%)?|*(`x~LxZ@1ZX<#90};**IZ3D zvxBcR99H9a+8g61OuK4o2om{|7QU&?(DuF#Ye{>?hiuJfkCo+Us5@~8z*AQqn@Y=a z=L(14I9|0Ige_Ga$ufe7&qgdt4qaG0JF?nauZk0?ixgbVl1OeiJ_XmgxQg_9X|x+8 zmlEzD-&onl-JADYDK$2_caQ<>CTkaS_KHcWrm=Q-aOKx211EJf+$^SA8=q)9iX>*3%`8Gfg-81x96!Giw}LjzKf3(~_T({hl$8oIU$C zf|`Xb4=Ut*n*{|iK}nv}-P;}1BRY^POQ1ooLyh1)L7LmF28fxs7uOA0s|YiuWBNIftzs~^pmKeo@j1LwX7(nx$t>#b zsy;1XnZfbH(idB^1{xzv%EnbXHC@bQ^?6Cs8LW32Up{|JJ2b1Wovnr{=udSqgoLk- z$AJSafW4By*e(_SR zqk>|ojsypT;`}9p4b>4cMU}j|kowgeRc{=~t`|jqkn1jdy^$jIkijR)v>tcZ%%2Y1 z)^rCU#QFmNshJzcOy>!wG9;QWaX`bd;^iX4#Blh}tPOVHz|)(um%T$>2Jy~h0XIPp zm|>-#o}BCY^7SlTreNNEOBZ2<-y&LUt1jln`#N0OW;A@ogphN6>8%{;9H!;q809Zq zX7q1+woOa0x|F#h4h`9KINmUZtP5879q8&$*~F-kqar<|ta@|!tp1G8Fkd@)uYT@P z24^b^?tY@R{F!|`Qd&%5+k{Se$qKFl)>970~`q=5@@5wsRdXSQ2l)yP2;qV+rb zeGQD|Adj8+h7tr$C5rRgTiG|}EvqgjV?2ewX6v?rp&18Ob{`xMMm1ZhT0@_Uo-Fhx z-c9%f-bSnM8#ATHCV^J2=xQ&o#noGU$f9!1BjRmUN0K5LMJo;bO$Q%Jc?HD6b)F`$tY4O0>^Z;p6*ynUKnwyLBbIclD8%_3~ zReFdJ9#s4l7M@>1lgJP^Y>%Sbh-InAI*wNU}cI#-Ncf@{iSHVm) zvLB@pglvia$unpr8ldR?mj%BOgo$xSj%bygd=|M7Ha33d`5b}=dQMhs5SBkP=-K#1 zvF)&b;Cu9KFat(dY1or<8^l)%uYO9)u$33@V7mDF&S^TZ0|SZm!>Z(rM0c@PkGk1C zQs)Tfo09=>?iNUVz)%t?{0 z2RP|f`Uz$KME(lUNnK+4u72=t#;@U-&CTn{oyr$OPtK<-SePrlX7;$M-+i_zgVyt$ z_7iPgjw!GFOAMByLX&^T{1R4@uMFeZLX{V@S}|&Na$cYQn&kYpQ^{qN0}&AD@Sk9! z(l%*(H#Tii^9FpJ!8vE);hBM}?*mT{CkKt^hK{y>@}gqP9sXrVXXu{79$-eEn!a)< zI}G2wO(!QvhN>tSw8<^4ndyMY(K3^CiNHC&@TA_9vEM4465u8)~G*D&~(zpNY1)%)<_g zw(Yq{cbrxEukE;Mw>HIUJ+GX_FKgq?E79DnO!4=rgUDvu?`exyq|kS$=oKHLIHT7V z?|%h6aY&4{*x|+IGD^!^CwLr$@jZI^b~eJAjmx#=-U@XCeH9}wKhe2KB)YK zq6nS-(y1Rm)RmcJ6XHiE#dg`}PGk0lwA*m?sqwN%?bfJlrrWj&L7Nr!+b%NllLnaG z@>03Z#XH~y2P&Tl8e`%0rT^D2RcMTVub8K4+5g94SGHQ}kp`W(|w` zVcKLw+T0kf3^T^SE*-JooBmqA5(sIVQx?N%nH`St3!MKhwixMDFY9Vgv3#y6rZnba zVD-Td*g+y%IJB!BBQWdIqmo1b1s?o_Aff8M*aVH+;H@GGap7uwS>MW2zYulkoN+qK z8Mfckx*m+mW6h|Pb}kB1PtxPFU@(s0Girv?`n$d>gBVdZW^TT)hf zWur6RqPtf`0$0J6$4=;}OD>N~3$?2bIP4@nY@a58vYnJ`aJG zNm2soMY{XK{>UmH&t*lU^8x)4K`|D#Fot$YwnF}POb-x@jMi_q0DFo%W1V*~>7u$l zl9g-W=57SIV%&WOU)JFn?eKcRA>1$4R3Uh6b2!9Ry*KEcf9eiO=7H0syO9(6^RuB@-Bw-90LH=iP}&&Vnk zp^0V7wS}vUDachtfmS5dpsbcnwUp7jDT~~Qf!%Mv9dXF=KYwoRVA!}O?jTyLOuF{= z1Z{fjhmFl$tN4yU$M(&y@0I{L@%I@P@f}U@gAOr&Re0k`4((r@j85;&F`PJ1np`Q{ zh;ks;cT5I)CWTV9spa*5NINKhq~)agLF#pJ1_>(mFs!4|(2EX?@30}Yz0ia%plbas zPZ3)O9X=N~lQTIHU;BKuwxl`tU`brDcZ7F+hR%PI-cKF)ngL7f&Fr82&&6BQB_i); zGiE2B+0)aJGf$B-N?5nGH~vLh zO>nYPJyFquL9G7oWKK={XyZpo#y)rfpYF+p)=g)aYt%|)dt~0pLGeJa+!qnl+@&e% z!~nN>kzKuthiy-TTXnR7r3Urq9Vh723Oq{A1DdV z0;Iv+fb$^j=%2MRViunNzNId4kmRDx$*uWUw7MpKxRdEE0DMMXY#q}&G&p~M z+v`TE-}9c{rx%GcD2XGRv$f!1QkP{2?qtZUm?1DY{)_Q6^V+(r;5qQFuNlG-ExY>yGOT=T@x)Q7dz6t^5@e@i-2GG($;bgk-dcO%g-P3-$Lm7nlrZ<1>o;&{TD zj)iHn-fi(=sdvnKNNDYDwQFIEi+>VE*a!4P^`pdVU*4idAHnA>-FOw$g;|$2>{izu zq&QmRul|t+zu-T-v6_sG`(`M#m}WIdjIbI5UYWi|d$EfE)eEk{;SLPV^ThJLl>|rC@n>N(kz_l@2A{nujHr>c}P!qxBU%g zM-OKiuEbxB%QIe1YKRN3b9NQ*g);4iqU%)kS1hLgH0(Voto5i*$)f2u`FOc~w?Y!3 zTk_d@Vfps0^snB(#rZA7ZE)&=OOvD>gqFhL!+CFZa7f=Qt4r^pglWo=E1xrYm17|h zjd@ZV>K+;6uOj``?sJtW>yc44ftE!d0tlFTi055XgMZfycj#xFh8iF0;Ez^hG^xsw zzg^5vt>m7Xtc;D@X)wj$S{y~}32bhcb8NA%hdI9-CHUtiPh9CRtC+1*kbs~~!|%ZUGy>33(`%hCxQ=EnkY0yIW^$y^^qs$`OO^xUu7+>hfA z?N|4lVWEA#gPB$z!J=zl4##nc`NO|0X5uX2Kqfnh79iJ-rQ>3vC?9QRANnSe1@4`$ z4BPo2CZN_PpPV{1U}S`P*&bRJFx!4#eO zUi6)AA&GJiWn4?;t9L@_cLyTe{)LRT_pqXP+?cN!rY7YbPYSu2hr z?rz3|&Rp8Xch_1g?CUiD9uCSrn3XN~0{=@(RVh|Dl@x~)aJm(G9aqERe{l|k=y^F# z@+HG{&mUsr)W(uJ3Z_U>rI&`9}U`I^7c|veUGT>pYsjeNim) z*|7k10p?_ zgyQ_r)0AMLsx@&*hPoBkP}4 zJDrrEst>|F2@3}Dw!LKX)d2)UoSH;FsXFf7G8;;py5|V2_oW&cy@Dg%1hT|;?W+`- z(=NDOrUB0E3-i*2(NOIMnm^2=U0RtgzQy~zVOKxzF9;}FtA|E7eQyG)JXLf zD6Gf9tKm25ZB_#TO|<0!CHZXvR1{YCuIy{@p0VJgBs6(-RO~I2#ODt(My*QgGWerY zpQ7*~q-b4Y=$0Z*r6)Ff6$#(~UYPO|EtM7ui_rI4oVwMs9;_uQkL!5sZ;7X2T zMI`>$2p-2kN6HIN;r6O0xmzKK(6#fcwDx-#-4Io@R-$6Zt~{|ild)u}#t@M(AliO; zCACJgo$`^BFIgfG1FPhFRHyP8B1y%*m2>jGDBS0H0$$fKe`cU)@}vagu>Urbxoti^ zh8@Avk%RA)u;erJFqdqpObxSyZ2~f{c@pa!<}e0Ea;>(YYBEO7&MmN=eQNe^M@J*l zHCeK#aSHyEhvUw@O%2IDZP4}Tu92UcUfRvTIFKA`=`I(K>W?+EwWlroNW zI=?^}me6LpbT}a|nl4ccy<j)iu^?qxz(e++ez z?TAEXG*U!#*uDN7$M7>S57CiC?K>Y_;lHWzG%X)U*?)wFrERZ$p~9w>7fT;6!&q*S zd5m>wB{Exj%WCDG8P^c{0MSjSK%!Sd#~Teoi|fO{D%Z6K=C1PgL~qLmQ+N}Xj5JNv z$PwT65xyhhTIWY3qZbBk*qcEc7+&FA{O3kQ(si` zjRkz#ye@zCTt}3yrgme0Tt)TMlL5A2Pr2x}lHT=k=)q_kUPJ$yx}llL?K>CmT3C6s z%?)a)EXJ=TtVy}m2??>^PwWug4a&QAb7~KLD2Xc0*c&Ti=zjBvG@vZ*LMVnZ6&Kp7 zhTPm22<(^SC}POSD#VM(+IOh&IThC23)X?fh=9cW;@vRqY(n&$kAA}D5AU(pNd<@? z3e5Vd^8h};0U5+QJ0(Of0%%$NUBvUiIgwA_YLa8-#A&~%FkQ$`B-2y)M$_+zVcNQm z4X;VaqWEQkJ*xmc21foGwB$Atx=5dw8zVcY@Gu03$%7UMa{l>S?_ca08oh_|=Utty zC$qp6Eboe)f2iQ~yRT~BU9WrHD;X(Xh!BM+XnI<#u(F;^CkJSATk(>-!*{dNv3An@ z`1D&qrEP*+3piS91~2IwvZD+qQUO>Lc}#eH4HA4FDZR4yT)U!VsmyQNT55A0-*m02M0SX0cq8h(t7>LNxbK_S4(tHLLDtRir$P|n`vknX5u&7W@n#6`pgta}F#h8p| zGT-DBkQZWist4hGaWU=jo4RD#>{5b7Dp)>?B3`Y9{>yf6;ViDI^zeyVkFgzrH)5F+ zef)hCq{USFrCWc8*y4|KoWwy4{`tETcT%rHZTs1@Xyctw$a#Z>F2VZNkEcDU0M5cm zdf4~5DflP=O+F8!6BtQldtk{hJcN=d8V5!3z1R8762?nDW4<7R(nDr5-0*~93MJYh zucQDIKq0v|V#`gLN(C&CgNp*2Rjp0n47(rLAJisqSKmb_6v}POq-J z2Tgh4eos3E%O**kLLMXnxvG3Z!OEe_>;9Ize0Bm+X4G3A#7BNSfRK2v*FE^^*bZpq zOERSl3AINIw^HIsE3 z;klLtGdP3NUDT~fiQR{!zJD`?lSy$tJe`x_rt_n#8OxfGt$1yzYElt(7HSNhU)|z?od~uIPcc zflq}5fVt4Mm=Q`#us-_Uj>yY*27-SI)m=cu zZF0{f+8R@e$#{Z;%3X+lY$T#zNDNd3mUW@uMwjg`J>D{L)%Kwg!rQS5uC9EQ| zK-$%)cGhH0qql^`rd^@=Vgz9QUh$1`sEi)u1Ck0p)~sP z=c-nGzW&bvZwgz^@)VeBPx_n0V+uc)d@Szs*@lw8Zb zz4j}HORbhdazP?prWm8Y@Y~^<+ckmsB#O94(X-b^-(N|rr_`5}oU9Lt`yLPGY zd=gq|o)?Im2UG(}EumHI_uxhLENt)CW~tm{ zx%v8kpWnKb6~PiQyRefBi|nYcwL7nTK`Pb;A|dOJy%?>9j0aH9)OOcaxaR+f8{J?3 z{&&f`OH8P+Ms_kk-8Rnu`%%4IRKv#v#7vWOn&*Kh=*uoz#_=5*_wFPy(u40LAOw)d z4yWP>iKlD^S^Zj;mGCKDVkq+PdwL^5go5RPpb2bP>iuGVlY--0TDiKH7vCBB-qQ}& zsD1yiCkYQ*H$Wn~EVtv!Z&xxf(Bz+g8ejDzB5Mn>);zLRIJJA$UCTl> zX|!p08Ap-|0z9SEGSa%zOMyu{SazKH`^9gH`|Gb|20bL!PE8GvzjMSTtK(LYDIlg& zv>Mh6d3EIcf}6aUfLFa=3~-(If~m>TBSOu)!@Vj6v|=4Qi&cGrZe$jJlDY=i`8uP~ z|I0Q2W$0q~Y%YaoHFNW*a&p0!FwpY8JwEy7evl#mbTISice0aJEzJja42@S@HH%S2$owIS(C)quxevstWe?D0Q{eettu=5d>TA~?E;0vy}}x1d4zXREL)I4!$i zC<$yA(kmLH>O6R|${y@}ZUy*x7~Xi|w}1!u2jV=T#ljMKk)2A(1CNY-OlyQ;`I)wZ z)9^|0qX%_2C1o9^gSPIlHoNuV5Lv$YeKdJ`jKq<>u%w6N*zd)KVR^K<(M}j0 z_MiGDA{FFlc(|i26q40RbARkQkM&EmLD8KXzr>9K&ayi%5>Lsz!@pZ$9iYWG0 zecIl`ITV%DGZbuw9#HHH4a0`AwzdM^X`m=e|YZ~Th*^2$QJ{aIKk7tn}P3#Ri3 z7NETM5bg9olGxB_TR7@?$wYERuwM{_nJ$wOzEQ6z%J`Th+}PigWqg?RW6F2Ig_H^K zB_OE(%!u^wEsl*dpKx2ih!K{T-+F6B&YH!W9&>3@pk0mGouWi#xheGel?y3c+~$er zvnXD2Jz{5hF$Gf+#%w12?~AQ<|M~!%BBJ5b@0gGFS&n>sW@d23m>%k*f{&QdLYY)i z>}=>N9)_h}NwCA*e`|i`dPT@~@Mwc+-z3NjYGzXC|8Y)vZ|-B04k_48Qi}`+jy#X9 z>7zKK|Kfks;fsS2_5wYz+A&n);fm`?C!HH+6u$qPw>5SRt90MAspAOTYxl^skk3exNFe;?YE+ zQ~quK?#PE}r&t9`3tV-C-gw~od3a_VB(9qS*e)<1_M$(kIQWR}@l!&1(C=d!5~0)u zTs5Lh#<144|9^*(mb;{}%=GX(6$Mr$JgF{dzTMbK$t%}?!5t#}J5BvpoG0#Id0n>= zX3W6U!qY3xJ9&bmOTva2sj|W_m!+^rhu*ZW@Kj+;-myYpbQIQ&ptHsab!>>;)3FA- zk$3_bHX&}(I#^we3^H&z@g~OACyTyJH5` zaBZ!R^Swpa>tt}pCJOXOTn&f-F~yKtAg<^H)~l|)PH5ztrQT8Oy`?2vC&P=hQ~t6U ze2eZXBOqJlFVly~bJn|a=ufv>)mTQ4U(@6(N)+Eop)%8O8W2K)gCT&ipXb3NdvhTy zTH(fm0~zXh)Fzh-K=wFFu`>II*Ya(7-)YS#x2&y8 z|G)iz&(54V=b97OnMn~%r+)QeNY&unZKVh=`Y8V%OBIH% zaqCn0(}-ZX2f9Z~QX(!bE{F-)B)zI6<*8MR_wag0Gx_6hCOg$s-`m@`bp7+?R(!t` z1YoNN0;$S$OjL9ocv4Z`nop*Do-|x_=BP*jOwn>O`ZIS$!s0CRN?Sj;6eIeH_}5v< zV;A;$Gk$k`4)k=W4ZQLFML7Ek5ZaoxJO;wTqlbTVNc z>JE^`%$sVH&8sxWF>_4_S$ciJSDTN1EakiPdi#M24iWOIO}usbqxl-t3AJdtnj)wJ zdaV>AFKMw)e~x=moa)fvgNTC|^fKL+zs>d5-XZYPpb9WwnzR-quV?;N+9Qy+nd`O< z#$`S@%LyQ$MwtK7%pI3i7Z{;ouklAMR^db+RmKm{U2bI3tYthjfBx{w-d@ah^kG}W zmBK<(dCjU)2qZce^H3@#UxY=>Hq z!uuHr0HB9Y88^I3TKr-ii32B;`t#l8xwU<0@BL?WpNX%$#tHz6JPODP@zWo%F<;Q@ z-5CFQ=?oW6s=DyD1Ec^#x-eMhAF9nLG}r$bXBG60RlT*^Z(7xtP5g&Y!q$~} z9(2_wMtCt{jxg=I&h2*I8~oF7DAW%?F)<}f7zux zqo*QzVNRwQYOCL{ALVH~ivVZfaa+zO;rR_ll`Z5Z10Naw3YTtW0@tAP3Uz9k4lO%E zBo@%fUxZWVYEk!1t!Sj*#$h;ES7S=!ER29E#*_XC5U@0@8WEnXa8gbt3k`Z@URN+wMBT0L>SJ@y4bY{E=&~>|2aOjHIoJubG*#PEw|{ zhXl8}Q6HcC*YPELzcL90c+5zzmx_;}ZUop7To@oJWwb^Xm5j#z#FRV>eZ@rv^YM>- zjUY9>f6ji@6fP9R5nvVa&udF5epPybV(*b*&ZXC4i@Gu+Z`CWWoEl$EOL>e%(H`UN z!~X8yr;*GBcEIPfQGbtxObI()UskiT?;5KtDaENcijxSUgDJY_S zGdm9++llaebmtS7i_&)-;E^hu)xETB3?E|{s}GI{s=bXYy5`Ho-edhh!>te?fWF+p zaZH2*L{>rE^@j;OQj>i>XBD= zVrG5PWsLq*4#Vsgg`NPH^5*X5wEugiqp6!s*Zgl__$>TeuI&$|?&B&26c76wOrYrB zHQFJcL^Xd2)@6U4xbtmnutNjoUF26%@xrO!CV&N#R_PAz;L5a`@~G^7BsBtmnTp4i^IK>m0M7l2((*iLe99KZ$q2oo5Q^}1X?ySQ%h@$w2#8M=m9M#)lf%Cr4^;5h$qrCg zU*nJBB@e633o&^lh@U>IpSXSG3FQAp%r06SUQxZu9;SOmZ7!4^3l3k45ULpl^f4Mv zTBA{XZZ@}0g)}MTFoY6tfefNmQ_Ob~raSDTnj=z|om_x|{(2zl8b=zb|32^2jC%XM=_Ga%L>0B!ySWDdFUT5!W}TM^r9zD~(cUAHA1Cs0Z6@?8 zljdD)(6~j?6$E#eGP|4T+pxJ;lAL?{8CR*j13+^(S}|GFOt>aFj8`rJeE3*cPKpun z;XBERq0hfjq+4G|UhzhAc|f2fFR7T4>hq{d@FzdxfW}QuSN^4cjGaB@B{q^C{Dmtc~!i9|t~E;rw2=WO?}G%013^M(!_u71WWhlhYk=iQ=&g#ZJ@FqpOe_T7c%=r-4`cdlIsVuTM&wM@=bOYTCeZ*`RI zv?0vmybarnt;;%_THSdR$GtOybu}VSO?=HiE7qZsuPeb{IM5a(K3l!XXHmlr3!ZLqo+u(`41gY3R`Zw)yx((W-8CQa`uD=s79fx4lQS&wScFSZEcGh~4n+o!`KSY5$r`Yo)L`0rvaUUOoX`Nhqiu|g8 z5C-#W$~psu_937%jxaH*9?fmtuZ=B;7@MUncQkV7;0+^Pw}^ zx4`~W4OavZF9xB)QIlkB78oc>U3)PUXzt_#I}s1x>rc9(;IVNDNJKQ^ z?}P+v+@#5CKAIoHGGA*=#JUY2ZsY^_YMNtq;CQB?8K`kdMQ*LG zHQmLx&sHP`EaIusJpOuva9>L6d^~zBTKvd_hqX-xv*K_??KxaCJ z<8Se|dPl+9usD9LD6+XS-PZ|C1!61E{{dP)KmfXZ(U-oKM3{Q!zLx3X_PvuYZdvUa zBV9T~WV;40CxLB=VKgqrka$j44y)a<4{~b8Ix4mOB?fYIo0m&8v?W zbbn3m9W}PJH$y3!4WJHBoqT$cJh$FOX^zv_Oi$|`)pj!#behw{Cl*?-QsO?<+Ib>e zc7gM@k6IRPwAx$>jmZC0(V0VBm>#B1$b&M5EWqqKmsE!&==BNA?{Uc)ly%*o=6&+k zR^E?rFs0$api+3EH#U@fFrwrbgK;iUl4j z2+TZ1ZHW8nc)gcex(jnF{v3#$7!g!B%Ya_bp8@n2bQXH%*@?GZk}{gYKkebO zgM2n|gY?(Ros-9M@-q8O>zVtjUv3ejya6XTV2zIh@FKw#ulm~?And|2n6+*})n+1I z`lDi&h}fg8X38$(q=s|t2o8pGalc0V-mAMg*H--4AG7B-uYPY&i{3wxB^pz$QylP;PqfuQdl^XJ~-x}R>_W?!f4^HdHKCXg@Z-WN=Nh;H$Em-2XpewZ`$ z=VVv)_0tjvP7wY z=6{_8TJCLNSAlGFK9G%O-+vc<+yEP4amVttcerLH!$r0k?a#r88jW`({`n;Es$-AoC#PxjiqBfrd877?%%?^ z#~#|-UtBBBLgvIC??S)4#5u6-2BIvioLFVWX8pPGOtq2e8!IZH|uqZzg)UE0O@X0_!4R~^nWlV zwXkNhS@Ic^yXG*s&>4L+Pk-LeXk=_(F`_>+tEv$8UXfwWJXFWJ_PBH4=A%}de(K5q zOem3x(0di+^E_a)Ah?O)(*`8ba`}Pbv1YKU`5)|~+pM9JH^L(dZn)4Bl+Q+=G=HkK z=R_R&F%c0)PtI8=H|bP_QMfYnc3;*#ul=Z-@?71b&d<+~7!RZ$MC2CptCe=Es9Whu z-Eeb&q(ICg!k<`8hy#xjJy-h%o?kNieI3n`G0$HWkzti(p z7PF-(XOU*h$i}@Cem3`aCyZ|{J(?WcZT zDe+Ky)rpD}=lcs=Fr}isMr8SMCi_u@T)C|88*dTd5Zirq&wyy!Hg^SX`Xmc%@AOrP zL&zpBsP~10QK;|J$@-UwSY(h=^qg+*`S z2hm28VLg}X16Bv4hwsZyu3my&xy=8GjWL>VMV0#?9Kq~tZ@n5i(-#R`S`Z4PN+<%g8ge<+T|QJurtI=AORSI z5|QsgiyC>J@{C=%jQ{-uN438b(w2xoR!6%$2Y!F)XYj~QmMzdSQ_iy%nLqC$j;ZKV zK)7u0sV|LMBkvc>#d7udHf~27F>&qQ+f)IL*@e7yVgG3B;kXN_`KQRva0S4awe(3p zfB)JLV@xmk3S1zd*=rIJI7P(F+puZ0N(mj?ElO1;wC+vO1P}}oHx*d-8q*}E@m(QO zyWkMCR_u$AygU+(&S6tUkxZP)Yop=px>K2Tkp<2>?H{(*hc_-C0m;MW20n5#*Ib`e z8Bdr!SRxRkt!k!85~&xr;b$Z{<>V0*M&?OlU!Iq{=yM8sjYnXD_-)x)XY%8E`~Gc+ zHQ0|?BgnP+G0$~e3%JWQliwwFyqX;>I_c^JJc)=WwuE18Xj0RjF>kM3xoewwMvzHu zO-x?+pbr%159g8uysu&W2d&eyd@8vy@cGS|H!Kni_gsAf0WZZGJ!Z>|Vh^uVhccan zDxisCSTMH?yY_M|HayT=S&oM7+fH8XI=&WL7owysyjve(g3Hrrx1>|^sAhT#&S~%= z(GG*hMlfRFy+8NVkB)g2ZP?Ov@L+Ga6eQ|dabqEC#7VdO)p)Ufz&(0DUZvf46GkHI z9e(|F?*-wpVio}UOz6Pa;+S$gDtfD%RH@bJHh3)0fj097yh-`g3bo^+SG}-u9Tqfm z*S-kr(XB*4ya@D^0d5_v_z$?YUA-`%CE=TdHj|Ti3@6U^XuF+>5xyqncHx|ZIPD*k zH3c87CeYu{_2T*kX$1lsN-c|v)fWF2E6x0zM5I<~3DNG`fmg#F`i^!?pr#6E$wsEu!yqZ4s&V$ve=7&Ch4LKBR)Qht#T2o4RfjcVi}h1lsQETR6r zo8%ew@j>P>TMhS}B9wYHxa2n?UN=^6Tf2>HQ;j8zelD;T2!D*VM*$fIe8K(xJC_T; z_0GSWp$St=h|~-OT%p8ZA-nOh{vs;DS6QTZot*-@=0hY1FlNJfc+MWYUbkF48WsrI zUYWOxK?)v%&=ScBMOL85mH#0kUehb<#`m{8qscZ!7Q)8{m|E8LP=NOqW(G#@o43OJ zXL(HRCyE4Y@%!&i|Dv+m*mfcvq6HV^UN+&YlIYD4I>g}hCO`Hsq6C>Gu5Y%(C1!-B zTBd^I=eu^cKn-JE&$@G5Sd&%k(3dtx#zCl z_U`hO|6h$%8$laAFQ-tBrLUqUMxfQ1^-fB7+v1os9<9a8)sKrPw)zKixyMz{^4r0Y z1(8aEPQBHo6K_^vok0TdwoiM`qn$RfR##sGjUU_Vb7^fiOhnO`H=@kMoBDOpiy|EU z%?7j~5q;#s@T46LlyV!0x~Pn^+R#oUE$7v!0lKlM-*{J*ttZdA@fNR$qA=cH_wZ8a zqwfOsj&+y+AVu6z=U6gr7j^tpitlILl01cTTZEh*o-0+i{+t(8|2JWrXG>izs7`~i z{LM};Ra?#d%H&N)l^59>vG=F7E^fAiu-V(&mcZK^01RIfz$Xo_&dUK?wTqVylb0*9 zR89r_ljhfIBriRDSCjNy=UA_HJjS>in(@+BC-*rLLXe&38>O1_A84q09}FVW)`^g0dc0}%*pAh`S3iPw0*f9#W# z3$UOM*W)U6?_H;8%9*g{<}%pfrw!2{gW3XuOwxWfKJ z6I__^9k-sxLFag%M&fK@mc0%93afJLqoB&o6|0_?Os}o$mphT$93m=OwPUf?N-R@~h(bClepRVB!k!vBtxhrV(PCC3#x-aXsjBv}V zR{g}~t8JuyrR?t$Y$FI1T5Sj##UMA}Kn{ZPGH;5@?>>WnmVD18?F(sg3Basc43Uhp zfJ7CBJBbZX{0HoC-5`y6AOjcr&UIR@-_&CsPrOOAWT3Culf~n$$O!OS;+|lo+H*lg z+Dn_$t;Pyufxigpyyz`ZkpO%=SReKSbGf~SO;b%_9!c=W@BJBhxnKXu@PurXJFCg& zOjp7~D(_)k$Vp?sNS7%CZ2tj`jNwCc8QWDV=Q|#)(Mva01&oUMSOoE=q|M$6H#M8_ z=c9;9wH&9R53dNX4y;WOXhSd|$-MOyeVsEv)U9?fvKaNXd`laH_ZAiBGDOM@e$}By z1P1_ttQQR}C~0+~?AjY_oh4PYf$v?I%!z(gNv`Xs>HjI_ln~$E3WfC95~K z6W36;FP7$H_|dT3U{oZ1-)dJ>H(Jg&eep3(v*q1mK$-;?Qn79a5k=dlXDeWb*vm54 z=e1wg8bl2DEPT#W=(ZX*S|`z0SB$Xg9w+^?rjvf@x1>$`{OlNVGYTeQSym1_hSJ7E zcRa;$|BQEu(KgUlJ~&h6BnPT|olagX98$4|wEB&F{e(POtFQ?7=ZUhRZF2IHMoi2| zP%n|*2?OG>AL~7@D#e&hoyqp^HWKFZatwsDyODIw`z7dWE7PI=+V~0d6>|W7-y7*& zx1Q5Xe5w}7_wHnrdqd*lyI&?Vswe0-?})^gevWr(6jr!$bA_pcQh=8ZJky~JRHEBK zpXAyt@KYY2)XFLR1l_;(X8wn)L`v1oROTXVh@};<7vnJt-&AArrl0h(gUGS2*%(*x zUA;$3^COZj+}@w*8eYaQ)|>)efmQ6hMmGm@eg=xKkc#XB1N08YZNE%Y5ZTEp7KbQk zm&u4@g6%CLvW6WMZLm0S2fnwydrp#Y%kr7P^e{S?psMD9YAier@%tG_|8EEcRQ2pA zpzSQW9s|KP+?r$1=IcZZABnhzjKh%l`O6^ANGGE95reu&{U3I@4i5$quM_YBJM?Q; zRqpE);FV!WU+Rh6w^pJqqhi@=%0IW&9Tjr=2^sa*lu_=`@KlzcG#l0tnomrB-OUeF zt#NX9hn)V1F77^c>40GXQbHjWDxiO-K)u^Q@V^=TM5KEx{{WPm;nGu&rJP-;3+v8ZLkPsON)BRb>!E4X^;N#cyQG)c+f180I>z z3+4~V_R}!`@9;J!uhe&!Th>^v%jZo@uFv}Biz+i`fsA`HsiZxpZ99g?We)%9HkwSj zO#f3JF~_g(9R}3W9;5EQh+>k?VDEL~)9ej^s(z0M7e+)2FU>p&)Exm0PQ0(#V<0@i z@-9kw&G7&!Iv53eS|%UQj~rmu;x@s=JOZ`gyv#&C?q068{DX!{4AifGyC5-Kdbg*R zww_SQ!<;3!yD1O7|9uQ6IkIH3U5E1b;NSaFs~A*))$2#$=)u(XT~se!G(*dIvfI7+ zq#7V@F$TCv@h<>rrs_2<3E&#ik8=AMbboI+{_G37JzO0KWtF}7%~CTD;+p7SR8m~K z+dC2WP@m;rnF*}NZvP31A?BN{WhC=cZf|_9PasfPO!4-yMAM|&D^?IP#qhEwWQO5| z$ltj{gH6Op-q%3&qg#F7;dd-hybM3%VQjf4!Z5rd3YIEdwaL=#-xjNPnIrJ6by~)H zKTOfTPC7Ymr^KNh?U?T*13`4{9KQt_3VO%4`g#nEur*GE>E~;XIrI*=HW${O$=B}O zlm7;rAu_xfBx5`Gpjzw;)mxOqbfY>59T-*+m8GsZTmL{M_vcq@tMaH`6b1c}tS;1_ zu~<|HJ2PxG%#Nbz=HUtTKPaLNgkuE%30I`~Q08W6Y8{C8f)TC2eWdADnhs>!`qpcR z2Yx9y5M^JRtgx0C%`UV4%e2yfq`7*&y3T=l0|64bS-~wY5BSbY1pKA>@&1>t+0=nhd{Yey{xbyOZPzXGZuD{x zdCq$aCP(SE%|&mr2>JG6wxOd)_RZF-zfUFtoeI8KcP**6PiwCee;LVM(;K)F_2@_OKOx(^ww$q_(;BcPt?oNq8OC9@ zF6|qO?mz38RaxjJhSPhD4IFg(R$GLP(){O_yeAGIb7?a^sF01H(;TCHQI8TZIgkrc zDNx`?XYN2}dmc#Ey!<5U!@kLLn3_=h-&zVBL#y*g@L6=)gf#qZ4{x9Xc-6ZQj8}wZ zkPyDG#UrtRBjWj?oDO}>dRDH6c)bXdW^U$mcedy~`HlTIQ&pl$F_T)yT;YJ8!Dn2l6YapyMdkFCAr>xoY1*wF{I1ZY5IL??-Spp!_W zw=_3t;_|8U+xiKK?%LkDKShfyv6sGJ-cAH*`~6>va_&qz4<*)`ni z=ke-CI$ETrT0={FDWQHRp-W586>HTGt)%AISF2Z~4Uz9T-a7mh9ioqBOm2OlX=t55HCCQFj8d9&O+co52xI|s(fEkfNT zX7A5LLV(}7yAkDl9dF#nd}n!D#WR##2WVepRf>ORXy9p~za9C)%g;IxUHxWZQY z`|EVHh~zV$PO|Ja>q4BPMjUkQJz8YY_+J#{T4xwxNwfXw0eT>r7nmnZF6{$ZD-JtGJAa@r! zQD*hU+EYq8(E-d2*0EjFP1?>$rQN}$ozKmo)cl1)h@oEp6{|i? z;PX}Bwo-*3j5$O#TDwF2r)T9;x676ee*I|3mwd7B7^<98#~C)t8KIB0=$I#JYR$XO zLr4YR9K{l-h^mg)4S*foTDs4SPr)_`A_ei`kp5NH+3=O!tCQhbn<1wbc?wEGM8cnv zIUfR8xZThJ-6aca>|>oEnTrCMX#EToTwmI1hOwLa^c;!fDxZ^3mZ|<70>p4bH!l28 zToOjFl{4KV*P3a#_r602YGDwskO504=99(XX93JiDpIiTIH6bQ>e<}7P1!u*JjR7s zr>oZl3MfDFD$uJ4qIKrFJk7l>L*Zl96V(LdBRn136G>X(_Yb^3JCLsE?rlDQa$pMu zCFKVYSiYmE;H4Zu7F0iX$SvdLvX6w2vAP4B9COYyP5E?_UL+^m>E>pDBmNZHn6;0% z?nkW5OHP^8)|2(Fbql<}Sv23%u!QX|TUFt9|F3(uqi@^KH}Clyn$X1;=gTFdJ!pGI zGdWNR8{Y7=mr^Mkv2|*_3WLfQR%eYxQMSoD=vP^PsC4xKpRL4VS{tn?1&&Rhe)mIX z;*7lG;1YN3IcrzBu~uHy>9MHQ^k>!ERTNNjjDxy4rRpb1E!@~B$6YoNA*L11f5e0@ zf0N%7WfUTit4N78BiM}AR)cex=AH!U&(fOfzr6sJ*w$+C-MMKWjdEC&Ck%Z~wl3W8 zDxojAjW5cb}e z;a`Il&v@gY^TTlcwu5LSm-9BG<@3MnZJ1se@q;6{U0-uov_1{UIkq+u>?J*w>Mjp3 zSU8V+$11qGFeq88kK5$ibd`z7vE5mQL0YgB-QvH0ZY6!-OMGJdCC-=g;3Go~pPj-b ztZ?aB!*z7d(22oQ(jrl{E>ogeS2QomKg%;%2B&h332Hf4}j6!;BCAp$pp zr*`#iEo!R@Ojn_KkK};`ft=JRBhKsM+yhl6Cvb5oM!rsteiPDF&WDI1SaGiIGhOGK zJ#@=N{jFp?dGjNV$}vPjx$#ox67E48xDn5-AnOHE^j*QVp6%X)<}ld6K*)B+qn4;i z$w8o!^3*5u2ojTx*-bU)_5mKb6%&&T&N1;1>3xI2>~-5(Ad8G0w)Dy=eRv=eld=4; z`?Gj^WQ%8@K#>4P6mK6kx0Jv=(;5^bH(>HGBG(b9nwodvG3k42>DHkh*nP(IPx`V> zEgQ@zE9#GkmOM`GvJ&QIrWuRyMclsjz2Fl&2Z2xh!A8nIL72 zh|?osgo5Hew?ewy+e=*zr3Mw;6Y*HFRPkfyb%RPAXew1{?usZ)d&i%+Z^;+Fr3`^) zeX&qz{kyCVdKM@%=c_3?`d|<~IJQ6HwEpJj4Pe=M;(oMEERNDGBId~6d8%f*#3->g zbKg!ta^lG~mX;>>OXH)}I)fFL-0(yx6>%L>bniPm z)03W$JU4rR)z{aez(}3RBHLT7dPx%v{vqgSukpAL)8v~0R6IRMk{QCXw?a!Tomf*qLE-E85~z^+{}tQ zSaK(e`v{!B@2M35Q7t4U)ySE1)d@*&2Du@}?keoUi#6(*MHvkQc(I9O>vYg;?%E#q z!Ex%~c>tfUYMqcJThX``7aEDhk>I$dud9^0PXJw#q5AQ*-Oe;ggTI3h>28#m+eO`* ze-`y&Xb4*035Pk<9=;j)BbfymOEVWrX7`8`M_bJMNfDds=#`_-+wQ>>=d)t=({Etm zxVf>yDbrOMJO!S|Js<>2-O3x&H-LP?Qy70SgM zO*^ISaj&4^p0mCO4qrvSk^p?ZOa?RH${W~gWy2OVdGp^IA=32|=x*(?F5K4XP@8FO zgQN@wOy;<^v}`{H(pp1m;9&;^^*F}|^U=8V1XR`#`pUf(|M<&-R^AmyxqrrUeY?hU3P-9F(wu>@rmbN8F~s5b&R5Kp8o z*(xIO$w{RkZo298?e;-?s%!FoAI7 zsIn1vlxHLS6-jJr;1e%1C6e#b_r|@kT3O~!2Xivc8XC2(8$0Fd57OA4YdCW)(+9>{ zSSGc&cmtSTbVhSr;bs#TVoj_`NomM*b3Xiz?Gj9!;B=iHp)m4T4<%y`I!HH!^hB&* z;rm;ZifJ%i-PA?a9<~!rBs*&eyICZ^_XpR?ejBeAKSUSbRu-fyVdGEwG2T=F10Zbe z&CWs0?^-iVyL2nS3vo*mgp*EI}rh;S;$D-5E9ib4KY9c~3Up&F4D?Soq zf8KW0@UO#MGgu!JR7B`!r1HLz)rYz2W0}WmoX%L62b@=h`BL*!(WV%S# zvZ=%(>2R`fPqw`S&&rqa`}?!_33X8o9v$htJ7P>cJ1)V4b^q+=Hr~B?F>!MUHo4rw za4H_GYw+|>^G=wgNjfIEHzYrEsmQVD-Gxr8>9JRP>US{qq1a_N$);)FDTf;#0y-(U z)f3gnGd6rd%A&Fj*&a9%mpO&=*KR=47%|qPmz*3SYBFAi#g)X_q}WXjI;MHao$omj zY2rc{>8&Q!I{-^>U;n7}yIH@>Lc8p8W#W9Jcx==Tb=&f3bm9n{QdE!OMQ7;V6mip4 ze)o-kYhHhHT{aH34cf8ciG=oSPh+7l;h)soKUH>j&wbWV^B9MsX>$oRTZ6%5T$wTH z?-jv0Uo60D)iXd-mp)}vDMK>o5ZRl|58tJK@=={vO8K3b!#%DR&!Hs#6atBu&!W?e z-q`F-V@C+y2yhqCr_he+AXN;J637XdQFqC(G>yQq#rjM>?`r*h=O(jj*8|bZ&cFtQ z=NVvx#1mFP$Z=M2R0`Q9n{fG~)UQo@j{(5vtY<^C7*?`lyC-CZPV;-hmnO;zE^Vmm z&Hc_-m-b*xUXE6GtI%&N`{PAAepPD=(F-$XvYnmPjhGlh^=$1DXjvODJnqJFFU*5z z^b8x0pne#_7wZ&RGtK0b>6cNm*0UD|vCd2uwcW@1w__>7xqb zwD|z9LU>XOj8a;RCZ6fiXr^nMwnh-_%|k7M^4WO)e&k4+8DN962;h8smIm2IQ4;ft zx7~nTjKhgu&^KQb>gOAt=aI>4J%{N!yqzEVhyN-nGi1&jEtRC1-%<5xmgp*6>COFw z9^rJ&e&sK~UCk<0qwv()r$Pa2J8cRMOIM5Xte1Z!IFkmO=hD!+w}f{vCSXf$!p(lj zp+-j{(#NL08M2Nb9lRi9tKrDNEFH}?9LoRLk8;3D%39~`jLY;lGI2-|*L|QN{fShK zopVr@(AfQ27*yvsepc>jA8q@kcSqQ@f@6q2g|i~mY$%u()8X)W&{P;?>)Bk8nc+5d z$TEl#;3J|*)=i4Oj<9luab3>yFEHrXR~<`s9+1R&)&mTnPZIrkMr-9lni4|rEJRG1h&afiV@#5&Axk!Rurq6 z$&6j2P-AfO1XR2>k#QnVGDU71kMBNCo;vp zeLdqTGDZx|#h&0@9$NQAto(zI*v?6&OmwWcQ&O~Fv=?h z!_JuD5I2{Cwwy|N9{7B2hovZI7h~O=n%N<3rZgi5_*@j#=$^<>15TmJDpu%R6Tg0Q zIMQETUr}e$s#Q9ja*@(p{zew_BO&P7GQY?V-5eBB#lPPCIr9N}NLoj=v!#zPiJK?k zvN_@Uy&(o;WLOm9vo$2`RwI8BaJ*IAw)u~O24%y6?3OUS&o#|V3Ij=e>N~@;%z?YL6eiCEnrDZ=5{8iNVsBy0W3rcDK#X+~V zv&-#31QmW3ad4#DSHhvV<_Dd|J11TV`LqD3guE*Sn-oe#7(=l0`hnk55DuwhZzp_o zh5m1^^quVt+`i+Cdef@OL#s0o?Lv5VyK-HsIauXX`-!*JQoq<>kow+j(ri@ZO?`%! zVLsi(>6Ba-(lv@@sf<$;f7!iU-nwYjSlm@ZF+pTLA$jBiM(Q24n`AdS5yQsXrN-0f zm-GxVM}|H5OZLIKAa}_g%1V?eH@T=#qV3Bsr+wflg1K!@d--=iizK30e&b}pyHUjM z25tmKAra4&*VVj}r5tOX?^n3w@hxTHkPZoV3`-Tb_f>=sN(+Q&@w-~EW5{Y)=?U(q zN)MRPpGUJ!o5^`l>rJ_Cf7_!!ugC4@z)}??tvXE1L7}Ot*@}tq6f)(=>i#UOy|zrs zdJ;It;DRhy8N6;&LNm0*tL``PfO92N3FIGp&UDjUfrm9B)!!)oy{ggR*SlKzvU>pS zmUE#|WlxCAWRc8ChIM@&;r$8h`rf*-`OpAL+%*^}vr-Mb>%hnHN=SsZWJ=1XSfjlK$*(*wz~ z!|a>B@xMC>bNC#nV$zU+Ep%$qB z-jeDzI(v#EUp6;=YxLPXoeWd$GL3C8U%|rZ0c8-4H{G1cH6jy6sQkhIv*?5W>`ipX z645$Y=?PSuSq|48A$eknu1M%()`p6yTi)m+ys$$Q#~D`z2_Ty$lDy@%Xjt*%KK)ksLTIQ`z03p8cMjuq;;%_2d9O33gU`ofef~#NX1c)-N5- zo=_nl?`u9ZpH~BwBI#K(TwmXp??m(hjxM7@@sE1OMsCU8`Kh>K-|YPf=S^M-PPvK2 z_aJgKnz*w!e}YG=E^T&u!gA2nsZIlApy@F#d#z>{SuhIK(tfd>3btvSvIe3eba!Ave)$oxm+QK+8yX5XA0aC7K?~HCtKJa_lfBNWQQ$0iOyJ zro5raJ7pSu6f0tXc;KzEyIO`0L57z+g@Ux^|g+2bBQME9lP>? zWuuNkun_IS0M8uGpmBr{>13u6_Ui2;P@rG?_{!)qhKb!>Ge!GqKWF=`?h|ljM$Tn! z{XM;H?3&{R7a8pWW46%Gbc?h29lxlzM2F}Px{{u3(L$bwjm@lw52H4J=c|@Ar*N%B zA4bte62ZuN2SVS^*rdUzm`{|Ou;S&tXDg-kD_#s1a?XYNdo?1+Qgr1freA`>86$Zy zb%g?I9E3V(F6*o1;_R%z7v2=W#R}YS21jPhnSMm*(Sy!7$#o5EmP30r3)ADeD~-nf3!~~gdxP&lkURwNZQD#6T4;mx4vpg~`{x1p z&tanrjDdKF^}X^VCJ|9UyJW3nyTE1&sLpU77aBqoWkQ6V1f@G2ox>;SP}1z!3QV7c zvGw~5V^IWGhG`jCXB$t34!-h^d#$0O4&r#u(bbKHat}pM)}-7scWhL%_rL8xkvVEp z(i9=;<-kGES{kf$KeQhd!Uz2Uknj&D$q^gwKqs8t;~Ly+YCIIuva|U7!_3L{b1#!N zkEGY|*+I&pQOwmWuIIF0(vYBJY~NPsRg_fsK99vpW(Sg`7w7d@79P)rQSb+zz5IZ> z%$#O0P?qU(Y@aLM_@pQs3^7NobJ-v*@6ls+_*%<;$aE`*?X*uEPsBzNa8;0B{;+CB}D9~`pL zpnnaSloQD;e=r~8HCdIz*E{e;Qgfd$A&gwT*{d*ia0OM;t=Y~qZZZR0Fnm&08n6*{ z^oT(rui!P4g(#?z&1_jYQdnpH+v%dZYYoKg{@bP1YF&LSerG)v-WUoPPR5g+ZAv40-$9?Rh2CHk!plUwjz?Vb)r-w?^z5S!mxY-UCN-NWT ztA&FL4VL^Y3G>4&r#|>2m6Z*n5T4fHSQ-yzNcpb09}?10HGfo1qgMde=Gg97^YEfn z`lkGb{mzZRYpYCJ7*6!0i%HQ!9eHXBHcr&ua7n0xxhbbP*a(-@@6#mj+>>}mX_QWv zed0~Bd_r{SSXyIt^gzllCIm>Iph9GUlFM&WXzM=nrcvV0vEZW7j@L4%=s(X0FWdS_ zeTQ*U$>DrlT;Boi^%)WYB=u@Fnz}QM1Nqz_#T$mCYs(6D#&oUw`nAz5w(DW@0TE32 z1l?y`91uz*Q_3EA1ZH!Fl63!~U%}D*$^OYHwUeS<^z9#|^8M0{o|q5BG(Wfb*79T7 z?J#JJ$ao=(OdX~)Ev~As%-+^FgErnHnzYP*+9vh){4QBUf8|!ZN6)N}44j0T%F?&T zdU#w16`b0aHd?fm_F(>r2Ozy^b?mx(Im*ue`dJ(|tfsu*yxQ_ze>EaN>`{eBh+^Ha zAuLxfsQQrRgOK#SC+}DJEo|`Y1N%!gZ%Z2f*~7nZ`J3_Vw$ohx-SI3AE0SSp)pA1i z>#FmvgJ*cI!WMqEBDWBmr>*p|hfxx7yoVYGGRTiaBt?v8(^Lte5gbAO} VlY&WEpLqlPWF!?nSAH@K`aiw_K*0b2 literal 0 HcmV?d00001 diff --git a/httpdocs/js/.eslintrc.json b/httpdocs/js/.eslintrc.json new file mode 100644 index 0000000..487f0f6 --- /dev/null +++ b/httpdocs/js/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "root": true, + "env": { + "browser": true, + "es2021": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 2021, + "sourceType": "module" + } +} diff --git a/httpdocs/js/login.js b/httpdocs/js/login.js new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/httpdocs/js/login.js @@ -0,0 +1 @@ + diff --git a/jest.config.js b/jest.config.js index 2adbbae..0a57f54 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,4 +6,5 @@ module.exports = { moduleNameMapper: { '^@src/(.*)$': '/src/$1', }, + bail: true }; \ No newline at end of file diff --git a/nodemon-static.json b/nodemon-static.json new file mode 100644 index 0000000..221cf6c --- /dev/null +++ b/nodemon-static.json @@ -0,0 +1,8 @@ +{ + "watch": [ + "httpdocs" + ], + "ext": "*", + "ignore": [], + "exec": "cp -R httpdocs/ dist/" +} \ No newline at end of file diff --git a/nodemon-ts.json b/nodemon-ts.json new file mode 100644 index 0000000..ad7eb01 --- /dev/null +++ b/nodemon-ts.json @@ -0,0 +1,9 @@ +{ + "watch": ["src"], + "ext": ".ts", + "ignore": [], + "exec": "tsc && node dist/app.js", + "events": { + "start": "clear" + } +} \ No newline at end of file diff --git a/nodemon.json b/nodemon.json deleted file mode 100644 index 92b25d2..0000000 --- a/nodemon.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "watch": ["src"], - "ext": ".ts", - "ignore": [], - "exec": "tsc && node dist/app.js" -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 808a918..2953ac7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,27 +8,42 @@ "name": "lorex", "version": "0.0.1", "dependencies": { - "express": "^4.18.2", + "bcrypt": "^5.1.1", + "chalk": "^4.1.2", + "compression": "^1.7.4", + "ejs": "^3.1.9", + "express": "^4.19.2", + "express-rate-limit": "^7.2.0", + "express-slow-down": "^2.0.1", "express-validator": "^7.0.1", + "helmet": "^7.1.0", "hpp": "^0.2.3", - "module-alias": "^2.2.3" + "jsonwebtoken": "^9.0.2", + "module-alias": "^2.2.3", + "toobusy-js": "^0.5.1" }, "devDependencies": { "@jest/globals": "^29.7.0", - "@tsconfig/node20": "^20.1.2", + "@tsconfig/node20": "^20.1.4", "@types/bcrypt": "^5.0.2", + "@types/compression": "^1.7.5", "@types/express": "^4.17.21", "@types/hpp": "^0.2.5", "@types/jest": "^29.5.11", - "@types/node": "^20.10.6", + "@types/jsonwebtoken": "^9.0.6", + "@types/node": "^20.11.30", + "@types/toobusy-js": "^0.5.4", "@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/parser": "^6.18.1", "axios": "^1.6.5", + "concurrently": "^8.2.2", "dotenv": "^16.3.1", "eslint": "^8.56.0", "eslint-plugin-jest": "^27.6.3", + "install": "^0.13.0", "jest": "^29.7.0", "nodemon": "^3.0.2", + "npm": "^10.5.0", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "typescript": "^5.3.3" @@ -647,6 +662,18 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", + "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", @@ -1361,6 +1388,61 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1445,9 +1527,9 @@ "dev": true }, "node_modules/@tsconfig/node20": { - "version": "20.1.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.2.tgz", - "integrity": "sha512-madaWq2k+LYMEhmcp0fs+OGaLFk0OenpHa4gmI4VEmCKX4PJntQ6fnnGADVFrVkBj0wIdAlQnK/MrlYTHsa1gQ==", + "version": "20.1.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.4.tgz", + "integrity": "sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==", "dev": true }, "node_modules/@types/babel__core": { @@ -1510,6 +1592,15 @@ "@types/node": "*" } }, + "node_modules/@types/compression": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.5.tgz", + "integrity": "sha512-AAQvK5pxMpaT+nDvhHrsBhLSYG5yQdtkaJE1WYieSNY2mVFKAgmU4ks65rkZD5oqnGCFLyQpUr1CqI4DmUMyDg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -1607,6 +1698,15 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz", + "integrity": "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -1614,9 +1714,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.10.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.6.tgz", - "integrity": "sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==", + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -1667,6 +1767,12 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "node_modules/@types/toobusy-js": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@types/toobusy-js/-/toobusy-js-0.5.4.tgz", + "integrity": "sha512-hsKMbYiaL3ZWx7B3FYyN0rEJexw7I1HgKbNToX3ZZJv6373to954wlA7zrXR3/XoVwZnFwWqFguBs91sNzJGKQ==", + "dev": true + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -1997,8 +2103,7 @@ "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "node_modules/accepts": { "version": "1.3.8", @@ -2042,6 +2147,38 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2089,7 +2226,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -2098,7 +2234,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -2122,6 +2257,23 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -2148,6 +2300,11 @@ "node": ">=8" } }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2259,8 +2416,20 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } }, "node_modules/binary-extensions": { "version": "2.2.0", @@ -2272,12 +2441,12 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -2285,7 +2454,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -2298,7 +2467,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2369,6 +2537,11 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2384,13 +2557,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2438,7 +2616,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2454,7 +2631,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -2463,7 +2639,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -2507,6 +2682,14 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -2562,7 +2745,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -2573,8 +2755,15 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } }, "node_modules/combined-stream": { "version": "1.0.8", @@ -2588,11 +2777,107 @@ "node": ">= 0.8" } }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, "node_modules/content-disposition": { "version": "0.5.4", @@ -2620,9 +2905,9 @@ "dev": true }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -2673,6 +2958,22 @@ "node": ">= 8" } }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2711,16 +3012,19 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/delayed-stream": { @@ -2732,6 +3036,11 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2749,6 +3058,14 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2812,11 +3129,33 @@ "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, + "node_modules/ejs": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", + "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.640", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.640.tgz", @@ -2838,8 +3177,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/encodeurl": { "version": "1.0.2", @@ -2858,6 +3196,25 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -3301,16 +3658,16 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -3341,6 +3698,34 @@ "node": ">= 0.10.0" } }, + "node_modules/express-rate-limit": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.2.0.tgz", + "integrity": "sha512-T7nul1t4TNyfZMJ7pKRKkdeVJWa2CqB8NA1P8BwYaoDI5QSBZARv5oMS43J7b7I5P+4asjVXjb7ONuwDKucahg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "4 || 5 || ^5.0.0-beta.1" + } + }, + "node_modules/express-slow-down": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/express-slow-down/-/express-slow-down-2.0.1.tgz", + "integrity": "sha512-zRogSZhNXJYKDBekhgFfFXGrOngH7Fub7Mx2g8OQ4RUBwSJP/3TVEKMgSGR/WlneT0mJ6NBUnidHhIELGVPe3w==", + "dependencies": { + "express-rate-limit": "7" + }, + "engines": { + "node": ">= 16" + }, + "peerDependencies": { + "express": ">= 4" + } + }, "node_modules/express-validator": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.0.1.tgz", @@ -3417,24 +3802,51 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" + "minimatch": "^5.0.1" } }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dependencies": { - "debug": "2.6.9", + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "2.4.1", @@ -3483,9 +3895,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true, "funding": [ { @@ -3532,11 +3944,32 @@ "node": ">= 0.6" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", @@ -3560,6 +3993,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3579,15 +4031,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3617,7 +4073,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3713,20 +4168,20 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "engines": { "node": ">= 0.4" }, @@ -3745,10 +4200,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", "dependencies": { "function-bind": "^1.1.2" }, @@ -3756,6 +4216,14 @@ "node": ">= 0.4" } }, + "node_modules/helmet": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.1.0.tgz", + "integrity": "sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/hpp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/hpp/-/hpp-0.2.3.tgz", @@ -3789,6 +4257,39 @@ "node": ">= 0.8" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -3872,7 +4373,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -3883,6 +4383,15 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/install": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/install/-/install-0.13.0.tgz", + "integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -3934,7 +4443,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -4115,6 +4623,23 @@ "node": ">=8" } }, + "node_modules/jake": { + "version": "10.8.7", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", + "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -4745,6 +5270,51 @@ "node": ">=6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4811,6 +5381,36 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -4823,11 +5423,15 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -4957,7 +5561,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4965,6 +5568,48 @@ "node": "*" } }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/module-alias": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.3.tgz", @@ -4989,6 +5634,30 @@ "node": ">= 0.6" } }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -5076,6 +5745,163 @@ "node": ">=0.10.0" } }, + "node_modules/npm": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.5.0.tgz", + "integrity": "sha512-Ejxwvfh9YnWVU2yA5FzoYLTW52vxHCz+MHrOFg9Cc8IFgF/6f5AGPAvb5WTay5DIUP1NIfN3VBZ0cLlGO0Ys+A==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "cli-table3", + "columnify", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "npmlog", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "dev": true, + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^7.2.1", + "@npmcli/config": "^8.0.2", + "@npmcli/fs": "^3.1.0", + "@npmcli/map-workspaces": "^3.0.4", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.1", + "@npmcli/run-script": "^7.0.4", + "@sigstore/tuf": "^2.3.1", + "abbrev": "^2.0.0", + "archy": "~1.0.0", + "cacache": "^18.0.2", + "chalk": "^5.3.0", + "ci-info": "^4.0.0", + "cli-columns": "^4.0.0", + "cli-table3": "^0.6.3", + "columnify": "^1.6.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^7.0.1", + "ini": "^4.1.1", + "init-package-json": "^6.0.0", + "is-cidr": "^5.0.3", + "json-parse-even-better-errors": "^3.0.1", + "libnpmaccess": "^8.0.1", + "libnpmdiff": "^6.0.3", + "libnpmexec": "^7.0.4", + "libnpmfund": "^5.0.1", + "libnpmhook": "^10.0.0", + "libnpmorg": "^6.0.1", + "libnpmpack": "^6.0.3", + "libnpmpublish": "^9.0.2", + "libnpmsearch": "^7.0.0", + "libnpmteam": "^6.0.0", + "libnpmversion": "^5.0.1", + "make-fetch-happen": "^13.0.0", + "minimatch": "^9.0.3", + "minipass": "^7.0.4", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^10.0.1", + "nopt": "^7.2.0", + "normalize-package-data": "^6.0.0", + "npm-audit-report": "^5.0.0", + "npm-install-checks": "^6.3.0", + "npm-package-arg": "^11.0.1", + "npm-pick-manifest": "^9.0.0", + "npm-profile": "^9.0.0", + "npm-registry-fetch": "^16.1.0", + "npm-user-validate": "^2.0.0", + "npmlog": "^7.0.1", + "p-map": "^4.0.0", + "pacote": "^17.0.6", + "parse-conflict-json": "^3.0.1", + "proc-log": "^3.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^2.1.0", + "semver": "^7.6.0", + "spdx-expression-parse": "^3.0.1", + "ssri": "^10.0.5", + "supports-color": "^9.4.0", + "tar": "^6.2.0", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^5.0.0", + "which": "^4.0.0", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -5088,73 +5914,2707 @@ "node": ">=8" } }, - "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node_modules/npm/node_modules/@colors/colors": { + "version": "1.5.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" } }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/npm/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "ee-first": "1.1.1" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=12" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", "dev": true, - "dependencies": { - "wrappy": "1" + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/onetime": { + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, + "inBundle": true, + "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", "dev": true, + "inBundle": true, + "license": "MIT", "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/agent": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "7.4.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^3.1.0", + "@npmcli/installed-package-contents": "^2.0.2", + "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/metavuln-calculator": "^7.0.0", + "@npmcli/name-from-folder": "^2.0.0", + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/query": "^3.1.0", + "@npmcli/run-script": "^7.0.2", + "bin-links": "^4.0.1", + "cacache": "^18.0.0", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^7.0.1", + "json-parse-even-better-errors": "^3.0.0", + "json-stringify-nice": "^1.1.4", + "minimatch": "^9.0.0", + "nopt": "^7.0.0", + "npm-install-checks": "^6.2.0", + "npm-package-arg": "^11.0.1", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^16.0.0", + "npmlog": "^7.0.1", + "pacote": "^17.0.4", + "parse-conflict-json": "^3.0.0", + "proc-log": "^3.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^10.0.5", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "8.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^3.0.2", + "ci-info": "^4.0.0", + "ini": "^4.1.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/disparity-colors": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ansi-styles": "^4.3.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/disparity-colors/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "5.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "lib/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^2.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0", + "read-package-json-fast": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^18.0.0", + "json-parse-even-better-errors": "^3.0.0", + "pacote": "^17.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "7.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/@sigstore/bundle": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/core": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.3.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/sign": { + "version": "2.2.3", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.2.0", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.0", + "make-fetch-happen": "^13.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "2.3.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.0", + "tuf-js": "^2.2.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/verify": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.2.0", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@tufjs/models": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/aggregate-error": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/are-we-there-yet": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "4.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/builtins": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "18.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "5.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ci-info": { + "version": "4.0.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "4.0.3", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^5.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/clean-stack": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/cli-table3": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/npm/node_modules/clone": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/color-support": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/npm/node_modules/columnify": { + "version": "1.6.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "strip-ansi": "^6.0.1", + "wcwidth": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/console-control-strings": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cross-spawn": { + "version": "7.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.3.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/defaults": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/diff": { + "version": "5.2.0", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/eastasianwidth": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/npm/node_modules/foreground-child": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/function-bind": { + "version": "1.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/gauge": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^4.0.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "10.3.10", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/has-unicode": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hasown": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "6.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/indent-string": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ini": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^11.0.0", + "promzard": "^1.0.0", + "read": "^2.0.0", + "read-package-json": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/ip-address": { + "version": "9.0.5", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/npm/node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "5.0.3", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "4.0.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/is-core-module": { + "version": "2.13.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/is-lambda": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/jackspeak": { + "version": "2.3.6", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/npm/node_modules/jsbn": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^11.0.1", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "6.0.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1", + "@npmcli/disparity-colors": "^3.0.0", + "@npmcli/installed-package-contents": "^2.0.2", + "binary-extensions": "^2.2.0", + "diff": "^5.1.0", + "minimatch": "^9.0.0", + "npm-package-arg": "^11.0.1", + "pacote": "^17.0.4", + "tar": "^6.2.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "7.0.8", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1", + "@npmcli/run-script": "^7.0.2", + "ci-info": "^4.0.0", + "npm-package-arg": "^11.0.1", + "npmlog": "^7.0.1", + "pacote": "^17.0.4", + "proc-log": "^3.0.0", + "read": "^2.0.0", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "5.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmhook": { + "version": "10.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "6.0.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1", + "@npmcli/run-script": "^7.0.2", + "npm-package-arg": "^11.0.1", + "pacote": "^17.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "9.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ci-info": "^4.0.0", + "normalize-package-data": "^6.0.0", + "npm-package-arg": "^11.0.1", + "npm-registry-fetch": "^16.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.7", + "sigstore": "^2.2.0", + "ssri": "^10.0.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "5.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.3", + "@npmcli/run-script": "^7.0.2", + "json-parse-even-better-errors": "^3.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "10.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "13.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "9.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "7.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-json-stream": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/npm/node_modules/minipass-json-stream/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/negotiator": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "10.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "7.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "6.3.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "11.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^16.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "16.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npmlog": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^4.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^5.0.0", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/pacote": { + "version": "17.0.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^7.0.0", + "cacache": "^18.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^16.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^7.0.0", + "read-package-json-fast": "^3.0.0", + "sigstore": "^2.2.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/path-scurry": { + "version": "1.10.1", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "6.0.15", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-inflight": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~1.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.6.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/set-blocking": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/sigstore": { + "version": "2.2.2", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.2.0", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.0", + "@sigstore/sign": "^2.2.3", + "@sigstore/tuf": "^2.3.1", + "@sigstore/verify": "^1.1.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.8.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 16.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.2.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.5.0", + "dev": true, + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.17", + "dev": true, + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/ssri": { + "version": "10.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "9.4.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "6.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/tuf-js": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "2.0.0", + "debug": "^4.3.4", + "make-fetch-happen": "^13.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/wcwidth": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/npm/node_modules/which": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/which/node_modules/isexe": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm/node_modules/wide-align": { + "version": "1.1.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/npm/node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" }, "engines": { "node": ">=10" @@ -5238,7 +8698,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5503,9 +8962,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -5522,6 +8981,19 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -5534,6 +9006,12 @@ "node": ">=8.10.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -5613,7 +9091,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -5647,6 +9124,21 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/rxjs/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -5675,7 +9167,6 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -5728,15 +9219,22 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -5768,14 +9266,27 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5784,8 +9295,7 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/simple-update-notifier": { "version": "2.0.0", @@ -5833,6 +9343,12 @@ "source-map": "^0.6.0" } }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -5868,6 +9384,14 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -5885,7 +9409,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -5899,7 +9422,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -5961,6 +9483,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -6016,6 +9554,14 @@ "node": ">=0.6" } }, + "node_modules/toobusy-js": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/toobusy-js/-/toobusy-js-0.5.1.tgz", + "integrity": "sha512-GiCux/c8G2TV0FTDgtxnXOxmSAndaI/9b1YxT14CqyeBDtTZAcJLx9KlXT3qECi8D0XCc78T4sN/7gWtjRyCaA==", + "engines": { + "node": ">=0.9.1" + } + }, "node_modules/touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -6028,6 +9574,20 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/ts-api-utils": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", @@ -6264,6 +9824,11 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -6327,6 +9892,20 @@ "makeerror": "1.0.12" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6342,6 +9921,14 @@ "node": ">= 8" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -6362,8 +9949,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "4.0.2", @@ -6390,8 +9976,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yargs": { "version": "17.7.2", diff --git a/package.json b/package.json index 43db4ea..4e3216f 100644 --- a/package.json +++ b/package.json @@ -5,40 +5,63 @@ "main": "index.js", "scripts": { "prebuild": "rm -rf dist/*", - "build": "npx tsc && cp -R httpdocs/ dist/", - "build:prod": "npx tsc -p ./tsconfig.prod.json && cp -R httpdocs/ dist/", + "build": "npx tsc", + "build:prod": "npx tsc -p ./tsconfig.prod.json", + "postbuild": "cp -R httpdocs/ dist/", "start": "node dist/app.js", - "dev": "rm -rf dist/* && cp -R httpdocs/ dist/ && nodemon src/app.ts", + "dev": "npm run prebuild && npm run postbuild && concurrently \"npm:dev:*\"", + "dev:ts": "nodemon --config nodemon-ts.json", + "dev:static": "nodemon --config nodemon-static.json", "lint": "eslint . --fix", - "test": "jest" + "lint:client": "eslint httpdocs/js/ --fix", + "test": "jest", + "test:app": "jest src/tests/app.test.ts", + "test:login": "jest src/tests/login.test.ts", + "test:unit": "jest src/tests/unit.test.ts", + "test:integration": "jest src/tests/integration.test.ts" }, "keywords": [], "author": "Type-Style", "devDependencies": { "@jest/globals": "^29.7.0", - "@tsconfig/node20": "^20.1.2", + "@tsconfig/node20": "^20.1.4", "@types/bcrypt": "^5.0.2", + "@types/compression": "^1.7.5", "@types/express": "^4.17.21", "@types/hpp": "^0.2.5", "@types/jest": "^29.5.11", - "@types/node": "^20.10.6", + "@types/jsonwebtoken": "^9.0.6", + "@types/node": "^20.11.30", + "@types/toobusy-js": "^0.5.4", "@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/parser": "^6.18.1", "axios": "^1.6.5", + "concurrently": "^8.2.2", "dotenv": "^16.3.1", "eslint": "^8.56.0", "eslint-plugin-jest": "^27.6.3", + "install": "^0.13.0", "jest": "^29.7.0", "nodemon": "^3.0.2", + "npm": "^10.5.0", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "typescript": "^5.3.3" }, "dependencies": { - "express": "^4.18.2", + "bcrypt": "^5.1.1", + "chalk": "^4.1.2", + "compression": "^1.7.4", + "ejs": "^3.1.9", + "express": "^4.19.2", + "express-rate-limit": "^7.2.0", + "express-slow-down": "^2.0.1", "express-validator": "^7.0.1", + "helmet": "^7.1.0", "hpp": "^0.2.3", - "module-alias": "^2.2.3" + "jsonwebtoken": "^9.0.2", + "module-alias": "^2.2.3", + "toobusy-js": "^0.5.1" }, "_moduleAliases": { "@src": "dist" diff --git a/src/app.test.ts b/src/app.test.ts deleted file mode 100644 index 0682f2d..0000000 --- a/src/app.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import axios from 'axios'; - -describe('Server Status', () => { - it('The server is running', async () => { - let serverStatus; - try { - const response = await axios.get('http://localhost'); - serverStatus = response.status; - } catch (error) { - console.error(error); - } - - expect(serverStatus).toBe(200); - }) -}) \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index 382c255..17afecb 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,34 +1,108 @@ require('module-alias/register'); import { config } from 'dotenv'; import express from 'express'; +import path from 'path'; +import toobusy from 'toobusy-js'; +import compression from 'compression'; +import helmet from 'helmet'; import hpp from 'hpp'; -import cache from './cache'; -import * as error from "./error"; +import cache from './middleware/cache'; +import * as error from "./middleware/error"; import writeRouter from '@src/controller/write'; -import path from 'path'; +import readRouter from '@src/controller/read'; +import loginRouter from '@src/controller/login'; import logger from '@src/scripts/logger'; - +import { baseRateLimiter, cleanup as cleanupRateLimitedIps } from './middleware/limit'; +import { cleanupCSRF } from "@src/scripts/token"; // configurations -config(); +config(); // dotenv + const app = express(); -app.use(hpp()); + +app.set('view engine', 'ejs'); + +app.use((req, res, next) => { // monitor eventloop to block requests if busy + if (toobusy()) { + res.status(503).set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Retry-After': '60' }).send("I'm busy right now, sorry."); + } else { next(); } +}); +app.use((req, res, next) => { // clean up IPv6 Addresses + if (req.ip) { + res.locals.ip = req.ip.startsWith('::ffff:') ? req.ip.substring(7) : req.ip; + next(); + } else { + const message = "No IP provided"; + logger.error(message); + res.status(400).send(message); + } +}) + +app.use(helmet({ contentSecurityPolicy: { directives: { "default-src": "'self'", "img-src": "*" } } })); app.use(cache); +app.use(compression()) +app.use(hpp()); +app.use(baseRateLimiter); +app.use((req, res, next) => { // limit body for specific http methods + if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) { + return express.urlencoded({ limit: '0.5kb', extended: true })(req, res, next); + } + next(); +}); + // routes app.get('/', (req, res) => { - res.send('Hello World, via TypeScript and Node.js!'); + logger.log(req.ip + " - " + res.locals.ip, true); + res.send('Hello World, via TypeScript and Node.js! ' + `ENV: ${process.env.NODE_ENV}`); }); + app.use('/write', writeRouter); +app.use('/read', readRouter); +app.use('/login', loginRouter); // use httpdocs as static folder -app.use('/', express.static(path.join(__dirname, 'httpdocs'))) +app.use('/', express.static(path.join(__dirname, 'httpdocs'), { + extensions: ['html', 'txt', "pdf"], + index: ["start.html", "start.txt"], +})); // error handling app.use(error.notFound); app.use(error.handler); // init server -app.listen(80, () => { - logger.log(`Server running //localhost:80`); +const server = app.listen(80, () => { + logger.log(`Server running //localhost:80, ENV: ${process.env.NODE_ENV}`, true); +}); + +// scheduled cleanup +setInterval(() => { + cleanupCSRF(); + cleanupRateLimitedIps(); +}, 1000 * 60 * 5); + +// catching shutdowns +['SIGINT', 'SIGTERM', 'exit'].forEach((signal) => { + process.on(signal, () => { + function logAndExit() { + // calling .shutdown allows your process to exit normally + toobusy.shutdown(); + logger.log(`Server shutdown on signal: ${signal} //localhost:80`, true); + process.exit(); + } + if (signal != "exit") { // give the server time to shutdown before closing + server.close(logAndExit); + } else { + logger.log(`Server shutdown immediate: ${signal} //localhost:80`, true); + } + }); +}); + +// last resort error handling +process.on('uncaughtException', function (err) { + console.error('Caught exception:', err); + logger.error(err); + server.close(); + process.exit(1); }); \ No newline at end of file diff --git a/src/controller/login.ts b/src/controller/login.ts new file mode 100644 index 0000000..6a47f4c --- /dev/null +++ b/src/controller/login.ts @@ -0,0 +1,58 @@ +import express, { Request, Response, NextFunction } from 'express'; +import { create as createError } from '@src/middleware/error'; +import { crypt, compare } from '@src/scripts/crypt'; +import { loginSlowDown, loginLimiter, baseSlowDown, baseRateLimiter } from '@src/middleware/limit'; +import { createJWT, createCSRF, validateCSRF } from '@src/scripts/token'; + + +const router = express.Router(); + +router.get("/", baseSlowDown, baseRateLimiter, async function login(req: Request, res: Response, next: NextFunction) { + loginLimiter(req, res, () => { + const csrfToken = createCSRF(res, next); + res.locals = {...res.locals, text: 'start', csrfToken: csrfToken}; + res.render("login-form"); + }); +}); + +router.post("/", loginSlowDown, async function postLogin(req: Request, res: Response, next: NextFunction) { + loginLimiter(req, res, async () => { + let validLogin = false; + const token = req.body.csrfToken; + const user = req.body.user; + const password = req.body.password; + let userFound = false; + if (!user || !password) { return createError(res, 422, "Body does not contain all expected information", next); } + if (!token || !validateCSRF(req.body.csrfToken)) { return createError(res, 403, "Invalid CSRF Token", next); } + + // Loop through all environment variables + for (const key in process.env) { + if (!key.startsWith('USER')) { continue; } + if (key.substring(5) == user) { + userFound = true; + const hash = process.env[key]; + if (hash) { + validLogin = await compare(password, hash); + } + } + } + + // only allow test user in test environment + if (user == "TEST" && validLogin && process.env.NODE_ENV == "production") { + validLogin = false; + } + + if (validLogin) { + const token = createJWT(req, res); + res.json({ "token": token }); + } else { + if (!userFound) { + await crypt(password); // If no matching user is found, perform a dummy password comparison to prevent timing attacks + } + return createError(res, 403, `Invalid credentials`, next); + } + }); +}); + + +export default router; \ No newline at end of file diff --git a/src/controller/read.ts b/src/controller/read.ts new file mode 100644 index 0000000..bd4b88c --- /dev/null +++ b/src/controller/read.ts @@ -0,0 +1,37 @@ +import express, { Request, Response, NextFunction } from 'express'; +import * as file from '@src/scripts/file'; +import { create as createError } from '@src/middleware/error'; +import { validationResult, query } from 'express-validator'; +import { isLoggedIn } from '@src/middleware/logged-in'; + +const router = express.Router(); + +router.get('/', + isLoggedIn, + [query('index').isInt().withMessage("not an integer") + .isLength({ max: 3 }).withMessage("not in range") + .toInt()], + async function getRead(req: Request, res: Response, next: NextFunction) { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return createError(res, 400, JSON.stringify({ errors: errors.array() }), next) + } + + const fileObj: File.Obj = file.getFile(res, next); + fileObj.content = await file.readAsJson(res, fileObj.path, next) + if (!fileObj.content || !Array.isArray(fileObj.content.entries)) { + return createError(res, undefined, `File corrupt: ${fileObj.path}`, next); + } + + let entries = fileObj.content.entries; + + if (req.query.index) { + entries = entries.slice(Number(req.query.index)); + } + + res.json({ entries }); + }); + + + +export default router; diff --git a/src/controller/write.test.ts b/src/controller/write.test.ts deleted file mode 100644 index f447d22..0000000 --- a/src/controller/write.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -import axios, { AxiosError } from 'axios'; - -describe('HEAD /write', () => { - it('with all parameters correctly set it should succeed', async () => { - const timestamp = new Date().getTime(); - const response = await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); - expect(response.status).toBe(200); - }); - - it('without key it sends 403', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(403); - } - }); - - it('with user length not equal to 2 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=x&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); - - - it('with lat not between -90 and 90 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=91.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); - - it('with lon not between -180 and 180 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=181.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); - - it('with timestamp to old sends 422', async () => { - try { - const timestamp = new Date().getTime() - 24 * 60 * 60 * 1000 * 2; // two days ago - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=101.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }) - - it('with hdop not between 0 and 100 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=101.0&altitude=5000.000&speed=150.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); - - it('with altitude not between 0 and 10000 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=10001.000&speed=150.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); - - it('with speed not between 0 and 300 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=301.000&heading=180.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); - - it('with heading not between 0 and 360 it sends 422', async () => { - try { - const timestamp = new Date().getTime(); - await axios.head(`http://localhost/write?user=xx&lat=45.000&lon=90.000×tamp=${timestamp}&hdop=50.0&altitude=5000.000&speed=150.000&heading=361.0&key=test`); - } catch (error) { - const axiosError = error as AxiosError; - expect(axiosError.response!.status).toBe(422); - } - }); -}); diff --git a/src/controller/write.ts b/src/controller/write.ts index b62fd57..28aa439 100644 --- a/src/controller/write.ts +++ b/src/controller/write.ts @@ -1,17 +1,25 @@ import express, { Request, Response, NextFunction } from 'express'; import { entry } from '@src/models/entry'; import { validationResult } from 'express-validator'; +import { create as createError } from '@src/middleware/error'; +import { baseSlowDown, errorRateLimiter } from '@src/middleware/limit'; // example call: /write?user=xx&lat=00.000&lon=00.000×tamp=1704063600000&hdop=0.0&altitude=0.000&speed=0.000&heading=000.0 -function errorChecking (req:Request, res:Response, next:NextFunction) { + +function errorChecking(req: Request, res: Response, next: NextFunction) { const errors = validationResult(req); if (!errors.isEmpty()) { - const errorAsJson = { errors: errors.array()}; - const errorAsString = new Error(JSON.stringify(errorAsJson)); - const hasKeyErrors = errors.array().some(error => error.msg.includes("Key")); - - res.status(hasKeyErrors ? 403 : 422); // send forbidden or unprocessable content - return next(errorAsString); + // if errors happend, then rateLimit to prevent key bruteforcing + errorRateLimiter(req, res, () => { + const errorAsJson = { errors: errors.array() }; + const errorAsString = JSON.stringify(errorAsJson); + const hasKeyErrors = errors.array().some(error => error.msg.includes("Key")); + + // send forbidden or unprocessable content + return createError(res, hasKeyErrors ? 403 : 422, errorAsString, next) + }); + + return; } if (req.method == "HEAD") { @@ -19,19 +27,23 @@ function errorChecking (req:Request, res:Response, next:NextFunction) { return; } - // Regular Save logic from here - - //entry.create(req, res); - //const test = process.env.TEST; - // res.send(req.query); - + next(); } +async function writeData(req: Request, res: Response, next: NextFunction) { + // Regular Save logic from here + await entry.create(req, res, next); -const router = express.Router(); -router.use(entry.validate); + if (!res.locals.error) { + res.send(req.query); + } else { + next(); + } +} -router.get('/', errorChecking); -router.head('/', errorChecking); + +const router = express.Router(); +router.get('/', baseSlowDown, entry.validate, errorChecking, writeData); +router.head('/', baseSlowDown, entry.validate, errorChecking); export default router; \ No newline at end of file diff --git a/src/middleware/cache.ts b/src/middleware/cache.ts new file mode 100644 index 0000000..c64ae4a --- /dev/null +++ b/src/middleware/cache.ts @@ -0,0 +1,15 @@ +import {Request, Response, NextFunction } from 'express'; + +const setCache = function (req: Request, res: Response, next: NextFunction) { + const seconds = 60 * 5; // 5 minuits + + // cache get requests but nothing else + if (req.method == "GET") { + res.set("Cache-control", `public, max-age=${seconds}`); + } else { + res.set("Cache-control", 'no-store'); + } + + next(); +} +export default setCache; \ No newline at end of file diff --git a/src/middleware/error.ts b/src/middleware/error.ts new file mode 100644 index 0000000..8db272e --- /dev/null +++ b/src/middleware/error.ts @@ -0,0 +1,45 @@ +import { Request, Response, NextFunction } from "express"; +import logger from '@src/scripts/logger'; + +export function create(res:Response, status:number = 500, message:string, next:NextFunction) { + /** + * takes httpStatusCode and Message and forwards to error Handling + */ + const error = new Error(message); + res.status(status); + res.locals.error = true; // to let other middleware know that an error was called + next(error) +} + +export function notFound(req: Request, res: Response, next: NextFunction) { + res.status(404); + const error = new Error(`🔍 - Not Found - ${req.originalUrl}`); + next(error); +} + +export function handler(err: HttpError, req: Request, res: Response, next: NextFunction) { + let statusCode = res.statusCode; + if (statusCode == 200) { + statusCode = err.statusCode || err.status || 500 + } + res.status(statusCode); + + let message; + try { + const jsonMessage = JSON.parse(err.message); + message = jsonMessage; + } catch (e) { + message = err.message; + } + + const responseBody:Response.Error = { + status: statusCode, + name: err.name, + message: message, + stack: process.env.NODE_ENV === "development" ? err.stack : "---" + }; + + logger.error(responseBody); + res.json(responseBody); + next(); +} diff --git a/src/middleware/limit.ts b/src/middleware/limit.ts new file mode 100644 index 0000000..1301071 --- /dev/null +++ b/src/middleware/limit.ts @@ -0,0 +1,71 @@ +import { Request, Response, NextFunction } from 'express'; +import { rateLimit, Options as rateLimiterOptions } from 'express-rate-limit'; +import { slowDown, Options as slowDownOptions } from 'express-slow-down'; +import logger from '@src/scripts/logger'; + +const ipsThatReachedLimit: RateLimit.obj = {}; // prevent logs from flooding + +/* +** configurations +*/ +const baseOptions: Partial = { + windowMs: 3 * 60 * 1000, + skip: (req, res) => (res.locals.ip == "127.0.0.1" || res.locals.ip == "::1") +} + +const baseSlowDownOptions: Partial = { + ...baseOptions, + delayAfter: 3, // no delay for amount of attempts + delayMs: (used: number) => (used - 3) * 125, // Add delay after delayAfter is reached +} + +const baseRateLimitOptions: Partial = { + ...baseOptions, + limit: 50, // Limit each IP per window + standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers + legacyHeaders: false, // Disable the `X-RateLimit-*` headers + handler: function rateHandler(req: Request, res: Response, next: NextFunction, options: rateLimiterOptions) { + if (!Object.prototype.hasOwnProperty.call(ipsThatReachedLimit, res.locals.ip)) { + logger.error(`[RateLimit] reached ${req.originalUrl}, ${res.locals.ip}, ${req.get('User-Agent')}`); + ipsThatReachedLimit[res.locals.ip] = { limitReachedOnError: true, time: Date.now() }; + } + res.status(options.statusCode).send(options.message); + }, + message: "Too many requests" +} + + + +/* +** exported section +*/ +export const baseSlowDown = slowDown(baseSlowDownOptions); + +export const loginSlowDown = slowDown({ + ...baseSlowDownOptions, + delayAfter: 1, // no delay for amount of attempts + delayMs: (used: number) => (used - 1) * 250, // Add delay after delayAfter is reached +}); + +export const baseRateLimiter = rateLimit(baseRateLimitOptions); + +export const errorRateLimiter = rateLimit({ + ...baseRateLimitOptions, + message: 'Too many requests with errors', +}); + +export const loginLimiter = rateLimit({ + ...baseRateLimitOptions, + limit: 3, + message: 'Too many attempts without valid login', +}); + + +export function cleanup() { + const oneHourAgo = Date.now() - 60 * 60 * 1000; + for (const ip in ipsThatReachedLimit) { + if (ipsThatReachedLimit[ip].time < oneHourAgo) { + delete ipsThatReachedLimit[ip]; + } + } +} \ No newline at end of file diff --git a/src/middleware/logged-in.ts b/src/middleware/logged-in.ts new file mode 100644 index 0000000..f6546ba --- /dev/null +++ b/src/middleware/logged-in.ts @@ -0,0 +1,13 @@ +import { Request, Response, NextFunction } from 'express'; +import { validateJWT } from '@src/scripts/token'; +import { create as createError } from '@src/middleware/error'; + + +export function isLoggedIn(req: Request, res: Response, next: NextFunction) { + const result = validateJWT(req); + if (!result.success) { + createError(res, result.status, result.message || "", next) + } else { + next(); + } +} diff --git a/src/models/entry.ts b/src/models/entry.ts index 73b7d29..e7fc107 100644 --- a/src/models/entry.ts +++ b/src/models/entry.ts @@ -1,47 +1,96 @@ -import { Request, Response} from 'express'; +import { NextFunction, Request, Response } from 'express'; import { checkExact, query } from 'express-validator'; -import { crypt } from '@src/scripts/crypt'; +import { compare } from '@src/scripts/crypt'; +import { create as createError } from '@src/middleware/error'; +import * as file from '@src/scripts/file'; +import { getTime } from '@src/scripts/time'; +import { getSpeed } from '@src/scripts/speed'; +import { getDistance } from '@src/scripts/distance'; +import { getAngle } from '@src/scripts/angle'; +import { getIgnore } from '@src/scripts/ignore'; +import logger from '@src/scripts/logger'; + export const entry = { - create: (req:Request, res:Response) => { - console.log(req.query); - console.log(res); - }, - validate: [ - query('user').isLength({ min: 2, max: 2 }), - query('lat').custom(checkNumber(-90, 90)), - query('lon').custom(checkNumber(-180, 180)), - query('timestamp').custom(checkTime), - query('hdop').custom(checkNumber(0, 100)), - query('altitude').custom(checkNumber(0, 10000)), - query('speed').custom(checkNumber(0, 300)), - query('heading').custom(checkNumber(0, 360)), + create: async (req: Request, res: Response, next: NextFunction) => { + const fileObj: File.Obj = file.getFile(res, next); + fileObj.content = await file.readAsJson(res, fileObj.path, next); + + if (!fileObj.content?.entries) { + return createError(res, 500, "File Content unavailable: " + fileObj.path, next); + } + const entries = fileObj.content.entries; + const lastEntry = fileObj.content.entries.at(-1); + const entry = {} as Models.IEntry; + + entry.altitude = Number(req.query.altitude); + entry.hdop = Number(req.query.hdop); + entry.heading = Number(req.query.heading); + entry.index = entries.length; + entry.lat = Number(req.query.lat); + entry.lon = Number(req.query.lon); + entry.user = req.query.user as string; + entry.ignore = false; + + if (lastEntry) { // so there is a previous entry + entry.time = getTime(Number(req.query.timestamp), lastEntry); + lastEntry.ignore = getIgnore(lastEntry, entry); + entry.angle = getAngle(lastEntry, entry); + entry.distance = getDistance(entry, lastEntry) + entry.speed = getSpeed(Number(req.query.speed), entry); + } else { + entry.angle = undefined; + entry.time = getTime(Number(req.query.timestamp)); + entry.speed = getSpeed(Number(req.query.speed)) + } + + if (entries.length >= 1000) { + logger.log(`File over 1000 lines: ${fileObj.path}`); + if (entry.hdop < 12 || (lastEntry && entry.hdop < lastEntry.hdop)) { + entries[entries.length - 1] = entry; // replace last entry + } + } else { + entries.push(entry); + } + + file.write(res, fileObj, next); + + }, + validate: [ + query('user').isLength({ min: 2, max: 2 }), + query('lat').custom(checkNumber(-90, 90)), + query('lon').custom(checkNumber(-180, 180)), + query('timestamp').custom(checkTime), + query('hdop').custom(checkNumber(0, 100)), + query('altitude').custom(checkNumber(0, 10000)), + query('speed').custom(checkNumber(0, 300)), + query('heading').custom(checkNumber(0, 360, "integer")), query("key").custom(checkKey), - checkExact() + checkExact() // INFO: if message or any string gets added remember to escape - ] + ] } -export function checkNumber(min:number, max:number) { - return (value:string) => { +export function checkNumber(min: number, max: number, type: string = "float") { + return (value: string) => { if (!value) { throw new Error('is required'); } - if (value.length > 12) { - throw new Error('Should have a maximum of 11 digits'); - } - - const number = parseFloat(value); + if (value.length > 12) { + throw new Error('Should have a maximum of 11 digits'); + } + + const number = type == "float" ? parseFloat(value) : parseInt(value); if (isNaN(number) || number < min || number > max) { throw new Error(`Value should be between ${min} and ${max}`); } return true; - }; + }; } -export function checkTime(value:string) { +export function checkTime(value: string) { const timestamp = parseFloat(value); - + // Check if it's a number if (isNaN(timestamp)) { throw new Error('Timestamp should be a number'); @@ -53,39 +102,29 @@ export function checkTime(value:string) { throw new Error('Timestamp should represent a valid date'); } - if (process.env.NODE_ENV == "development") { - return true; // dev testing convenience - } - const now = new Date(); const difference = now.getTime() - date.getTime(); const oneDayInMilliseconds = 24 * 60 * 60 * 1000; if (Math.abs(difference) >= oneDayInMilliseconds) { throw new Error('Timestamp should represent a date not further from server time than 1 day'); } - + return true } -function checkKey(value:string) { - if (process.env.NODE_ENV != "production" && value == "test") { - return true; // dev testing convenience - } - if (!value) { - throw new Error('Key required'); +async function checkKey(value: string) { + if (!value) { throw new Error('Key required'); } + if (!process.env.KEYB) { throw new Error('Configuration wrong'); } + if (process.env.NODE_ENV != "production" && value == "test") { + return true; // dev testing convenience } - value = decodeURIComponent(value); - - const hash = crypt(value); + const result = await compare(decodeURIComponent(value), process.env.KEYB); - if (process.env.KEYB != hash) { - if (process.env.NODE_ENV == "development") { - console.log(hash); - } + if (!result) { throw new Error('Key does not match'); } return true; -} \ No newline at end of file +} diff --git a/src/scripts/angle.ts b/src/scripts/angle.ts new file mode 100644 index 0000000..e6ea972 --- /dev/null +++ b/src/scripts/angle.ts @@ -0,0 +1,9 @@ +export function getAngle(lastEntry: Models.IEntry, entry: Models.IEntry): number { + const dLon = (entry.lon - lastEntry.lon) * Math.PI / 180; + const lat1 = lastEntry.lat * Math.PI / 180; + const lat2 = entry.lat * Math.PI / 180; + const y = Math.sin(dLon) * Math.cos(lat2); + const x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon); + const angle = (Math.atan2(y, x) * 180 / Math.PI + 360) % 360; + return angle; +} \ No newline at end of file diff --git a/src/scripts/crypt.ts b/src/scripts/crypt.ts index b14ef42..6bef0df 100644 --- a/src/scripts/crypt.ts +++ b/src/scripts/crypt.ts @@ -1,9 +1,18 @@ -import * as crypto from 'crypto'; +import * as bcrypt from "bcrypt"; +import crypto from "crypto"; -export const crypt = function (value:string) { +export const crypt = async function (password: string, quick = false) { + const extendedPassword = pepper(password); + return await bcrypt.hash(extendedPassword, quick ? 8 : 16); +}; + +export const compare = async function (password: string, hash: string) { + const extendedPassword = pepper(password); + return await bcrypt.compare(extendedPassword, hash) +} + +function pepper(password: string) { const key = process.env.KEYA; - if (!key) { - throw new Error('KEYA is not defined in the environment variables'); - } - return crypto.createHmac('sha256', key).update(value).digest("base64"); -}; \ No newline at end of file + if (!key) { throw new Error('KEYA is not defined in the environment variables'); } + return password + crypto.createHmac('sha256', key).digest("base64"); +} diff --git a/src/scripts/distance.ts b/src/scripts/distance.ts new file mode 100644 index 0000000..36c4637 --- /dev/null +++ b/src/scripts/distance.ts @@ -0,0 +1,30 @@ +export function getDistance(entry: Models.IEntry, lastEntry: Models.IEntry): Models.IDistance { + const horizontal = calculateDistance({ lat: entry.lat, lon: entry.lon }, { lat: lastEntry.lat, lon: lastEntry.lon }); + const vertical = entry.altitude - lastEntry.altitude; + const total = Math.sqrt(horizontal * horizontal + vertical * vertical); + + return { + horizontal: horizontal, + vertical: vertical, + total: total + } +} + +function toRad(x: number): number { + return x * Math.PI / 180; +} + +function calculateDistance(coord1: { lat: number, lon: number }, coord2: { lat: number, lon: number }): number { + const R = 6371000; // radius of the Earth in meters + const dLat = toRad(coord2.lat - coord1.lat); + const dLon = toRad(coord2.lon - coord1.lon); + + const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(toRad(coord1.lat)) * Math.cos(toRad(coord2.lat)) * + Math.sin(dLon / 2) * Math.sin(dLon / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + + const distance = R * c; + + return distance; +} \ No newline at end of file diff --git a/src/scripts/file.ts b/src/scripts/file.ts new file mode 100644 index 0000000..a8b693b --- /dev/null +++ b/src/scripts/file.ts @@ -0,0 +1,62 @@ +import fs from 'fs'; +import path from 'path'; +import { promisify } from 'util'; +import { create as createError } from '@src/middleware/error'; +import { NextFunction, Response } from 'express'; +import logger from '@src/scripts/logger'; + +export const getFile = (res: Response, next: NextFunction): File.Obj => { + const date = new Date(); + const formattedDate = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; + const dirPath = path.resolve(__dirname, '../data'); + const filePath = path.resolve(dirPath, `data-${formattedDate}.json`); + + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + logger.log("data folder did not exist, but created now"); + } + + let fileExisted = true; + if (!fs.existsSync(filePath)) { // check if file exist + fileExisted = false; + try { + fs.writeFileSync(filePath, '{"entries": []}'); + logger.log(`file: ${filePath} did not exist, but created now`); + } catch (err) { + createError(res, 500, "File cannot be written to", next); + } + } + + return { path: filePath, content: fileExisted ? undefined : JSON.parse('{"entries": []}') }; // if the file did not exist before, the content is emptyString +}; + + +const readFileAsync = promisify(fs.readFile); + +export async function readAsJson(res: Response, filePath: string, next: NextFunction): Promise { + const data = await readFileAsync(filePath, 'utf-8'); + + try { + return JSON.parse(data); + } catch (err) { + createError(res, 500, "File contains wrong content: " + filePath, next); + } +} + + +export const write = (res:Response, fileObj:File.Obj, next: NextFunction) => { + + if (!fs.existsSync(fileObj.path)) { // check if file exist + createError(res, 500, "Can not write to file that does not exist: " + fileObj.path, next); + } + try { + const content = JSON.stringify(fileObj.content, undefined, 2); + fs.writeFileSync(fileObj.path, content); + fileObj.content = JSON.parse(content); + logger.log(`written to file: ${fileObj.path} ${fileObj.content ? fileObj.content?.entries.length - 1 : ''}`); + } catch (err) { + createError(res, 500, `File (${fileObj.path}) cannot be written to`, next); + } + + return fileObj; // if the file did not exist before, the content is emptyString +}; diff --git a/src/scripts/ignore.ts b/src/scripts/ignore.ts new file mode 100644 index 0000000..3e92857 --- /dev/null +++ b/src/scripts/ignore.ts @@ -0,0 +1,13 @@ +export function getIgnore(lastEntry: Models.IEntry, entry: Models.IEntry): boolean { + let threshold = 6; // hdop not allowed to be higher + const maxThreshold = 25; + + const timing = Math.max(lastEntry.time.diff, entry.time.diff) + + // Threshold increases with older previous entries or farther future entries. + if (timing > 32) { + threshold += Math.min(lastEntry.time.diff / 60, maxThreshold); + } + + return lastEntry.hdop > threshold; +} \ No newline at end of file diff --git a/src/scripts/logger.ts b/src/scripts/logger.ts index b58f366..ddd608a 100644 --- a/src/scripts/logger.ts +++ b/src/scripts/logger.ts @@ -1,17 +1,51 @@ // primitive text logger import fs from 'fs'; -import path from 'path'; +import path from 'path'; +import chalk from "chalk"; + +const dirPath = path.resolve(__dirname, '../httpdocs/log'); +const logPath = path.resolve(dirPath, 'start.txt'); + +if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); +} -const logPath = path.resolve(__dirname, '../httpdocs', 'log.txt'); const date = new Date().toLocaleString('de-DE', { hour12: false }); export default { - log: (message:string|JSON) => { + log: (message: string | JSON, showDateInConsole: boolean = false) => { + message = JSON.stringify(message); fs.appendFileSync(logPath, `${date} \t|\t ${message} \n`); - console.log(message); - }, - error: (message:string|JSON|Response.Error) => { - fs.appendFileSync(logPath, `${date} \t|\t ERROR: ${message} \n`); - console.error(message); + if (showDateInConsole) { + message = `${chalk.dim(date + ":")} ${message}`; + } + if (process.env.NODE_ENV != "production") { + console.log(message); + } }, -} \ No newline at end of file + error: (content: string | Response.Error) => { + // logfile + const applyErrorPrefix = !/^\[\w+\]/.test(typeof content == "string" ? content : content.message); + const logMessageTemplate = `${date} \t|\t${applyErrorPrefix ? ' [ERROR]' : ''} ${typeof content == "string" ? content : JSON.stringify(content.message) } \n`; + fs.appendFileSync(logPath, logMessageTemplate); + if (process.env.NODE_ENV == "production") { return; } + + // console + if (typeof content != "string" && Object.hasOwnProperty.call(content, "message")) { + const messageAsString = JSON.stringify(content.message); + if (content.stack) { // replace redundant information + content.stack = content.stack.replace(messageAsString, ""); + } + const consoleMessage = structuredClone(content); // create clone so response output is not "further" affected + consoleMessage.message = messageAsString; // gitbash output improvement (w/o objects in arrays appear as [Object]) + content = consoleMessage; + } else if (typeof content == "string") { + const prefix = content.match(/^\[\w+\]/); + if (prefix?.length) { + content = content.replace(prefix[0], chalk.red(prefix[0])); + } + } + console.error(content); + + } +} diff --git a/src/scripts/speed.ts b/src/scripts/speed.ts new file mode 100644 index 0000000..b0591c0 --- /dev/null +++ b/src/scripts/speed.ts @@ -0,0 +1,19 @@ +export function getSpeed(speed: number, entry?: Models.IEntry): Models.ISpeed { + const gps = speed; + let horizontal; + let vertical; + let total; + + if (entry) { + horizontal = entry.distance.horizontal / entry.time.diff; + vertical = entry.distance.vertical / entry.time.diff; + total = entry.distance.total / entry.time.diff; + } + + return { + gps: gps, + horizontal: horizontal, + vertical: vertical, + total: total + } +} \ No newline at end of file diff --git a/src/scripts/time.ts b/src/scripts/time.ts index e69de29..f6adbc7 100644 --- a/src/scripts/time.ts +++ b/src/scripts/time.ts @@ -0,0 +1,34 @@ +import logger from '@src/scripts/logger'; + +export function getTime(time: number, entry?: Models.IEntry): Models.ITime { + const now = new Date(); + const created = Number(time); + const recieved = now.getTime(); + const uploadDuration = (recieved - created) / 1000; + const createdString = now.toLocaleString("de-DE", { + weekday: "long", + year: "numeric", + month: "long", + day: "numeric", + hour: '2-digit', + hour12: false, + minute: '2-digit', + second: '2-digit' + }); + const diff = entry ? (created - entry.time.created) / 1000 : undefined; + + if (uploadDuration < 0) { + logger.error(`upload Duration is negative: ${createdString}, index: ${entry ? entry.index + 1 : 0}`); + } + if (entry && entry.time.created > created) { // maybe this could happend due to the async nature, but due to uncertainty logging is enabled + logger.error(`previous timestamp is more recent: ${createdString}, index: ${entry?.index + 1}`); + } + + return { + created: created, + recieved: recieved, + uploadDuration: uploadDuration, + diff: diff, + createdString: createdString + } +} \ No newline at end of file diff --git a/src/scripts/token.ts b/src/scripts/token.ts new file mode 100644 index 0000000..f9f4d7a --- /dev/null +++ b/src/scripts/token.ts @@ -0,0 +1,87 @@ +import jwt from 'jsonwebtoken'; +import { NextFunction, Request, Response } from 'express'; +import crypto from 'crypto'; +import { create as createError } from '@src/middleware/error'; + + +const csrfTokens: Set = new Set(); + +export function createCSRF(res: Response, next: NextFunction): string { + if (csrfTokens.size > 100) { // Max Number of Tokens in memory + res.set('Retry-After', '300'); // 5 minutes + createError(res, 503, "Too many tokens", next); + } + + const token = crypto.randomBytes(16).toString('hex'); + const expiry = Date.now() + (5 * 60 * 1000); // Token expires in 5 minutes + const csrfToken: CSRFToken = { token, expiry }; + csrfTokens.add(csrfToken); + + return token; +} + +export function validateCSRF(token: string): boolean { + const currentTime = Date.now(); + let valid: boolean = false; + for (const entry of csrfTokens) { + if (entry.token === token) { + valid = entry.expiry > currentTime; + csrfTokens.delete(entry); + } + } + + return valid; +} + +export function cleanupCSRF() { + const currentTime = Date.now(); + for (const entry of csrfTokens) { + if (entry.expiry < currentTime) { + csrfTokens.delete(entry); + } + } +} + +export function validateJWT(req: Request) { + const key = process.env.KEYA; + const header = req.header('Authorization'); + const [type, token] = header ? header.split(' ') : ""; + let payload: string | jwt.JwtPayload = ""; + + // Guard; aka early return for common failures before verifying authorization + if (!key) { return { success: false, status: 500, message: 'Wrong Configuration' }; } + if (!header) { return { success: false, status: 401, message: 'No Authorization header' }; } + if (type !== 'Bearer' || !token) { return { success: false, status: 400, message: 'Invalid Authorization header' }; } + + try { + payload = jwt.verify(token, key); + } catch (err) { + let message = "could not verify"; + if (err instanceof Error) { + message = `${err.name} - ${err.message}`; + } + + return { success: false, status: 403, message: message }; + } + + // don't allow test user in production environment + if (typeof payload == "object" && payload.user == "TEST" && process.env.NODE_ENV == "production") { + return { success: false, status: 403, message: 'test user not allowed on production' }; + } + + return { success: true }; +} + +export function createJWT(req: Request, res: Response) { + const key = process.env.KEYA; + if (!key) { throw new Error('Configuration is wrong'); } + const today = new Date(); + const dateString = today.toLocaleDateString("de-DE", { weekday: "short", year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' }); + const payload = { + date: dateString, + user: req.body.user + }; + const token = jwt.sign(payload, key, { expiresIn: 60 * 2 }); + res.locals.token = token; + return token; +} diff --git a/src/tests/app.test.ts b/src/tests/app.test.ts new file mode 100644 index 0000000..44ba08d --- /dev/null +++ b/src/tests/app.test.ts @@ -0,0 +1,42 @@ +import axios from 'axios'; +import qs from 'qs'; + +// random data of 0.75Kb pre GZIP +const randomData = qs.stringify({ + randomData: 'zIakHvSaXDdLtaPaL02LhGr4Fk6hzXF7tELeR733YZyyye1fnjNzrSlHgqcHU8BKqvE5Mi4B7iHIEdqjTelpoWyaqXqX8l6LzOvROAkTF4lrLXLD1oMHwDL9hnjR0P7g0BB2DqagKkoEYD4TmXeAXT9PbevbirWnOEzmIgSv65SlsNTRFYhmzWl93twXEBNclHTCTnZpf6diWoo8FsXZR49pe9v8J1paalh2LlbNF4ZUxMxNpSvSTRHxvkYo0TMpd0NqUSSLduLIWcE1jhCWnmHhsbohDZjFfMhVS8IFvCiu7rxfuWgwMPqD9FcBR79eqJBy2tjDMqA9S1k9k50AkbOQ6USVfEuqOtocqXonTvC3Jml90KYSs0gX4SSTFHofpMtbWIdkuKqZbitQjsPSBpTx27dhFZd8zT4erdE1ltHnq83pjEj9hQYqatmdzQGYnOyh9YDt8i1IJpk4DX83DLzw3QhaFPgZFq98SOj4ILytmBMIqOtD464aF8PKGq6g7dVqYOtyF2FwyY0xgA7LjGaFzaCDjnGEcPIMRc2tcorsuRPKUI0zcde1gYPsn4WKaKUp87hJd1YtorzCXPfvivfGGL5v1XaSzApc9BbZpbxcpTOi4Pgvx7hNafUcaCr6kcjp4JVYSktnnGCwEplgGEF8uCELsEBUi9LNhgsnwgoRh55TaJfcaFfGfYLokXYEgiyOwYhhdEY3kfjHZWAyFS4owCR6nMJGOGMHrQi1fBefdp28PQGwgELix5Vf8j6P' +}); + +describe('Server Status', () => { + it('The server is running', async () => { + let serverStatus; + try { + const response = await axios.get('http://localhost:80/'); + serverStatus = response.status; + } catch (error) { + console.error(error); + } + + expect(serverStatus).toBe(200); + }) + + it('server is ignoring body on GET requests', async () => { + let serverStatus; + try { + const response = await axios.request({ + url: 'http://localhost:80/', + method: 'GET', + data: randomData, + }); + serverStatus = response.status; + } catch (error) { + console.error(error); + } + + expect(serverStatus).toBe(200); + }) + +}) + + + + diff --git a/src/tests/integration.test.ts b/src/tests/integration.test.ts new file mode 100644 index 0000000..067d3ea --- /dev/null +++ b/src/tests/integration.test.ts @@ -0,0 +1,325 @@ +import axios, { AxiosError } from 'axios'; +import qs from 'qs'; +import fs from "fs"; +import path from "path"; + +async function callServer(timestamp = new Date().getTime(), query: string, expectStatus: number = 200, method: string = "HEAD") { + const url = new URL("http://localhost:80/write?"); + url.search = "?" + query; + const params = new URLSearchParams(url.search); + params.set("timestamp", timestamp.toString()); + url.search = params.toString(); + + + let response; + if (expectStatus == 200) { + if (method == "GET") { + response = await axios.get(url.toString()); + } else { + response = await axios.head(url.toString()); + } + expect(response.status).toBe(expectStatus); + } else { + try { + await axios.head(url.toString()); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + expect(axiosError.response.status).toBe(expectStatus); + } else { + console.error(axiosError); + } + } + } +} + + +function getData(filePath: string) { + const data = fs.readFileSync(filePath); + return JSON.parse(data.toString()); +} + +function isInRange(actual: string | number, expected: number, range: number) { + return Math.abs(Number(actual) - expected) <= range; +} + +async function verifiedRequest(url: string, token: string) { + const response = await axios({ + method: 'get', + url: url, + headers: { + 'Authorization': `Bearer ${token}`, + } + }); + return response; +} + + + +describe('HEAD /write', () => { + // eslint-disable-next-line jest/expect-expect + it('with all parameters correctly set it should succeed', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200); + }); + + // eslint-disable-next-line jest/expect-expect + it('without key it sends 403', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0", 403); + }); + + // eslint-disable-next-line jest/expect-expect + it('with user length not equal to 2 it sends 422', async () => { + await callServer(undefined, "user=x&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }); + + // eslint-disable-next-line jest/expect-expect + it('with lat not between -90 and 90 it sends 422', async () => { + await callServer(undefined, "user=xx&lat=91.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }); + + // eslint-disable-next-line jest/expect-expect + it('with lon not between -180 and 180 it sends 422', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=181.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }); + + // eslint-disable-next-line jest/expect-expect + it('with timestamp to old sends 422', async () => { + const timestamp = new Date().getTime() - 24 * 60 * 60 * 1000 * 2; // two days ago + await callServer(timestamp, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }) + + // eslint-disable-next-line jest/expect-expect + it('with hdop not between 0 and 100 it sends 422', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=101.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 422); + }); + + // eslint-disable-next-line jest/expect-expect + it('with altitude not between 0 and 10000 it sends 422', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=10001.000&speed=150.000&heading=180.0&key=test", 422); + }); + + // eslint-disable-next-line jest/expect-expect + it('with speed not between 0 and 300 it sends 422', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=301.000&heading=180.0&key=test", 422); + }); + + // eslint-disable-next-line jest/expect-expect + it('with heading not between 0 and 360 it sends 422', async () => { + await callServer(undefined, "user=xx&lat=45.000&lon=90.000×tamp=R3Pl4C3&hdop=50.0&altitude=5000.000&speed=150.000&heading=361.0&key=test", 422); + }); +}); + + +describe("GET /write", () => { + const date = new Date(); + const formattedDate = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; + const dirPath = path.resolve(__dirname, '../../dist/data/'); + const filePath = path.resolve(dirPath, `data-${formattedDate}.json`); + + it('there should a file of the current date', async () => { + await callServer(undefined, "user=xx&lat=52.51451&lon=13.35105×tamp=R3Pl4C3&hdop=20.0&altitude=5000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); + + fs.access(filePath, fs.constants.F_OK, (err) => { + expect(err).toBeFalsy(); + }); + }); + + it('the file contains valid JSON', async () => { + fs.readFile(filePath, 'utf8', (err, data) => { + expect(err).toBeFalsy(); + try { + JSON.parse(data); + } catch (e) { + expect(e).toBeFalsy(); + } + }); + }); + + it('after second call and the JSON entries length is 2', () => { + return new Promise(done => { + setTimeout(async () => { + await await callServer(undefined, "user=xx&lat=52.51627&lon=13.37770×tamp=R3Pl4C3&hdop=50&altitude=4000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); + const jsonData = getData(filePath); + + expect(jsonData.entries.length).toBe(2); + + done(); + }, 3500); + }) + }); + + it('the time is correct', () => { + const jsonData = getData(filePath); + const entry = jsonData.entries.at(-1) + + expect(entry.time.created).toBeGreaterThan(date.getTime()); + expect(entry.time.diff).toBeGreaterThan(3.5); + expect(entry.time.diff).toBeLessThan(4.6); + + + const germanDayPattern = "(Montag|Dienstag|Mittwoch|Donnerstag|Freitag|Samstag|Sonntag)"; + const dayOfMonthPattern = "(0?[1-9]|[12][0-9]|3[01])"; + const germanMonthPattern = "(Januar|Februar|März|April|Mai|Juni|Juli|August|September|Oktober|November|Dezember)"; + const yearPattern = "(\\d{4})"; + const timePattern = "([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]"; + const pattern = new RegExp(`^${germanDayPattern}, ${dayOfMonthPattern}. ${germanMonthPattern} ${yearPattern} um ${timePattern}$`); + const string = entry.time.createdString; + expect(pattern.test(string)).toBeTruthy(); + + }); + + it('the distance is correct', () => { + const jsonData = getData(filePath); + const entry = jsonData.entries.at(-1) + + expect(entry.distance.horizontal).toBeCloseTo(1813.926); + expect(entry.distance.vertical).toBe(-1000); + expect(entry.distance.total).toBeCloseTo(2071.311); + }); + + it('the angle is correct', () => { + const jsonData = getData(filePath); + const entry = jsonData.entries.at(-1) + + expect(entry.angle).toBeCloseTo(83.795775); + }); + + it('the speed is correct', () => { + const jsonData = getData(filePath); + const entry = jsonData.entries.at(-1) + + expect(isInRange(entry.speed.horizontal, 515, 10)).toBe(true); + expect(isInRange(entry.speed.vertical, -284, 10)).toBe(true); + expect(isInRange(entry.speed.total, 588, 15)).toBe(true); + }); + + it('check ignore', async () => { + let jsonData = getData(filePath); + let entry = jsonData.entries[1]; + const lastEntry = jsonData.entries[0]; + + expect(entry.ignore).toBe(false); // current one to be false allways + expect(lastEntry.ignore).toBe(true); // last one to high hdop to be true + + await callServer(undefined, "user=xx&lat=52.51627&lon=13.37770×tamp=R3Pl4C3&hdop=50&altitude=4000.000&speed=150.000&heading=180.0&key=test", 200, "GET"); + jsonData = getData(filePath); + entry = jsonData.entries[1]; // same data point, but not last now therefore ignore true + expect(entry.ignore).toBe(true); + }); +}); + + +describe('API calls', () => { + test(`1000 api calls`, async () => { + for (let i = 0; i < 1000; i++) { + const url = `http://localhost:80/write?user=xx&lat=${(52 + Math.random()).toFixed(3)}&lon=${(13 + Math.random()).toFixed(3)}×tamp=${new Date().getTime()}&hdop=${(25 * Math.random()).toFixed(3)}&altitude=${i}&speed=88.888&heading=${(360 * Math.random()).toFixed(3)}&key=test`; + const response = await axios.get(url); + expect(response.status).toBe(200); + } + }, 20000); // adjust this to to fit your setup + + test(`length of json should not exceed 1000`, async () => { + const date = new Date(); + const formattedDate = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; + const dirPath = path.resolve(__dirname, '../../dist/data/'); + const filePath = path.resolve(dirPath, `data-${formattedDate}.json`); + const jsonData = getData(filePath); + expect(jsonData.entries.length).toBeLessThanOrEqual(1000); + }); +}); + + +describe('read and login', () => { + let token = ""; + const testData = { + user: "TEST", + password: "test", + csrfToken: "" + } + + it('form available / get Token', async () => { + let response = {data:""}; + try { + response = await axios.get('http://localhost:80/login'); + } catch (error) { + console.error(error); + } + + const regex = /name="csrfToken" value="([^"]*)"/; + const match = response.data.match(regex); + testData.csrfToken = match ? match[1] : '-'; + }) + + test(`redirect without logged in`, async () => { + try { + await axios.get("http://localhost:80/read/"); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + expect(axiosError.response.status).toBe(401); + } else { + console.error(axiosError); + } + } + }); + + it('test user can login', async () => { + const response = await axios.post('http://localhost:80/login', qs.stringify(testData)); + + expect(response.status).toBe(200); + expect(response.headers['content-type']).toEqual(expect.stringContaining('application/json')); + expect(response).toHaveProperty('data.token'); + expect(response.data.token).not.toBeNull(); + token = response.data.token; + }) + + test('wrong token get error', async () => { + try { + await verifiedRequest("http://localhost:80/read?index=0", "justWrongValue"); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + expect(axiosError.response.status).toBe(403); + } else { + console.error(axiosError); + } + } + }); + + test('verified request returns json', async () => { + const response = await verifiedRequest("http://localhost:80/read?index=0", token); + expect(response.status).toBe(200); + expect(response.headers['content-type']).toEqual(expect.stringContaining('application/json')); + }); + + test(`index parameter to long`, async () => { + try { + await verifiedRequest("http://localhost:80/read?index=1234", token); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + expect(axiosError.response.status).toBe(400); + } else { + console.error(axiosError); + } + } + }); + + test(`index parameter to be a number`, async () => { + try { + await verifiedRequest("http://localhost:80/read?index=a9", token); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + expect(axiosError.response.status).toBe(400); + } else { + console.error(axiosError); + } + } + }); + test(`index parameter reduces length of json`, async () => { + const response = await verifiedRequest("http://localhost:80/read?index=999", token); + expect(response.status).toBe(200); + expect(response.data.entries.length).toBe(1); + }); +}); \ No newline at end of file diff --git a/src/tests/login.test.ts b/src/tests/login.test.ts new file mode 100644 index 0000000..e177826 --- /dev/null +++ b/src/tests/login.test.ts @@ -0,0 +1,94 @@ +import axios, { AxiosError } from 'axios'; +import qs from 'qs'; + +const userDataLarge = qs.stringify({ + user: "user", + password: "pass", + kilobyte: 'BPSwVu5vcvhWB17HcfIdyQK83mHJZKChv7zDihBJoifWK9EJFzK7VYf3kUgIqkc0io8DnSdewzc9U0GpzodQUFz0KLMaogsJruEbNSKvxnzUxS5UqSR64lLOmGumoPcn2InC0Ebpqfdiw90HFVZVlE3AY6Lhgbx8ILHi55RvpuGefDjBsePgow8Jh9sc8uVMCDglLmHQ0zk3PumMj0KlOszbMmX9fG0pPUsvLLc40biPBv9t97K3BFjYd3fGriRAQ3bFhGHBz2wzGbNQfHjKFDHuSvXOw8KReM7Wwd4Cl02QQ3RnDJVwH6cayh4BqFRXlP3i6uXw0l9qxdTv0q1CtV9rJho6zwo04gkGLvsS3AoYJQtHnOtUDdHPExu7l3nMKnPoRUwl7K2ePfHRuppFGqa43Q49bI04VjEhrB9k5S2uZJoxZdm63rIUrydmkZWdvBLVVZUIXwwIRnwLmoa26htKOz9FPKwWIPOM0NZj4jAoPhKqLDJwziNZn5UupzxBXoUM3BIyEk3K8GXs7eBduH9GCK2z2HPF0fJNtGiHASe7jCOC2mhSC5zGf9k0Yu1Ey63oQQZUtT7L57lp7UzPE2p6wzKDlbJZOn0Ho5OUfq3hE2C8fQRO1M6jDvRTiUIKhhxSHYd75Pvh4SG9lD8w5OHASusLDxmzKBUuG4GrGrQYpd0awJkqnKp5lk7psLD22YTtjTuDgI500tQLXSslxI1kIuB8RnN1LsxHyRQMVtXmNFOKKZV2U2frWpImIz2wSHCYrwRGygwDtiFfwtVwTapjhQqUMyb1vrWWi3EL1Y50fDCjDDHlvLI4N2tr2DULFf3a9m2SYWSoE6CYP4og5YyqjhqFQFm9urREInyZi9L0iQoMYxEqxTjGiVJfKmaSChSd0kQz6z2OdsxFbkMWJ2CAHOL1XNK8iFFSp93fIspaNMIonRVDCj4ZIP1LaPHDmIYcYTNU4k3Uz6VBHSIc1VjiG3sc2MZpKw9An0tJVlWbtVSk2RGYWIANAYyr5pQS' +}); +const userDataWithoutToken = qs.stringify({ + user: "user", + password: "pass" +}); + +let csrfToken = "-"; +const userDataWithToken = { + user: "user", + password: "pass", + csrfToken: "" +}; + +describe('Login', () => { + it('form available', async () => { + let serverStatus = {}; + let response = { data: "", status: "" }; + try { + response = await axios.get('http://localhost:80/login'); + serverStatus = response.status; + } catch (error) { + console.error(error); + } + + expect(serverStatus).toBe(200); + expect(response.data).toContain(' { + try { + await axios.post('http://localhost:80/login', userDataLarge); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + expect(axiosError.response.status).toBe(413); + } else { + console.error(axiosError); + } + } + }) + + it('invalid csrf shows correct error', async () => { + try { + await axios.post('http://localhost:80/login', userDataWithoutToken); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + expect(axiosError.response.status).toBe(403); + if (axiosError.response.data) { + expect(JSON.stringify(axiosError.response.data)).toContain('Invalid CSRF'); + } else { + throw Error("fail"); + } + } else { + console.error(axiosError); + } + } + }) + + + it('test invalid credentials to return error', async () => { + try { + userDataWithToken.csrfToken = csrfToken + await axios.post('http://localhost:80/login', qs.stringify(userDataWithToken)); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + expect(axiosError.response.status).toBe(403); + if (axiosError.response.data) { + expect(JSON.stringify(axiosError.response.data)).toContain('Invalid credentials'); + } else { + throw Error("fail"); + } + } else { + console.error(axiosError); + } + } + }) +}) + + + + diff --git a/src/models/entry.test.ts b/src/tests/unit.test.ts similarity index 93% rename from src/models/entry.test.ts rename to src/tests/unit.test.ts index bd2c0b4..013a1b4 100644 --- a/src/models/entry.test.ts +++ b/src/tests/unit.test.ts @@ -1,7 +1,7 @@ -import { checkNumber, checkTime } from "./entry"; +import { checkNumber, checkTime } from "../models/entry"; -describe("checkNumber", () => { +describe("entry checkNumber", () => { it("should throw error if value is not provided", () => { expect(() => checkNumber(0, 100)("")).toThrow(new Error('is required')); }); @@ -19,7 +19,7 @@ describe("checkNumber", () => { }); }); -describe("checkTime", () => { +describe("entry checkTime", () => { it("should throw error if value is not a number", () => { expect(() => checkTime("abc")).toThrow(new Error('Timestamp should be a number')); }); diff --git a/tsconfig.json b/tsconfig.json index 613bc08..5f168cd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "target": "ES6", "sourceMap": true, "baseUrl": "./src", + "esModuleInterop": true, "paths": { "@src/*": ["./*"], } diff --git a/types.d.ts b/types.d.ts index b3d3397..3cde1ce 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,14 +1,18 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ - -type NumericRange = - ARR['length'] extends END ? ACC | START | END : - NumericRange; +namespace RateLimit { + interface obj { + [key: string]: { + limitReachedOnError: boolean, + time: number + } + } +} namespace Response { interface Message { message: string; - data?: string|JSON; + data?: string | JSON; } interface Error extends Response.Message { @@ -17,7 +21,18 @@ namespace Response { status?: number } } +namespace File { + interface Obj { + path: string, + content?: Models.IEntries; + } +} + namespace Models { + interface IEntries { + entries: Models.IEntry[] + } + interface IEntry { /** * height above ground in meters, as received by gps @@ -27,25 +42,17 @@ namespace Models { /** * Direction in degrees between two coordinate pairs: 0°-360° */ - angle: NumericRange<0, 360>, + angle?: number, /** * object containing horizontal vertical and total distance, in meters */ - distance: { - horizontal: number, - vertical: number, - total: number - }, + distance: Models.IDistance, /** * object containing horizontal vertical and total speed, in km/h */ - speeed: { - horizontal: number, - vertical: number, - total: number - }, + speed: Models.ISpeed, /** * index, position of the entry point in the chain @@ -55,13 +62,13 @@ namespace Models { /** * Heading or Bearing as recieved from gps */ - heading: NumericRange<0, 360>, + heading: number, /** * lat */ lat: number, - + /** * lon @@ -82,17 +89,41 @@ namespace Models { /** * time object containing UNIX timestamps with milliseconds, gps creation time (as recieved via gps), server time (when the server recieved and computed it), differce to last entry (time between waypoints), upload time differnce */ - time: { - created: number, - recieved: number, - uploadDuration: number, - diff: number - createdString: string - }, + time: Models.time, /** * user as recieved */ user: string } + + interface ITime { + created: number, + recieved: number, + uploadDuration: number, + diff?: number + createdString: string + } + + interface ISpeed { + gps: number; + horizontal?: number, + vertical?: number, + total?: number + } + interface IDistance { + horizontal: number, + vertical: number, + total: number + } +} + +interface CSRFToken { + token: string; + expiry: number; } + +interface HttpError extends Error { + status?: number; + statusCode?: number; +} \ No newline at end of file diff --git a/views/login-form.ejs b/views/login-form.ejs new file mode 100644 index 0000000..34bf551 --- /dev/null +++ b/views/login-form.ejs @@ -0,0 +1,31 @@ + + + + + + Login Form - Lorex + + + + + + + + + + \ No newline at end of file