├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── ci.yml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── biome.json ├── commitlint.config.mjs ├── logo.svg ├── package.json ├── pnpm-lock.yaml ├── src ├── index.ts ├── token.ts └── utils.ts ├── tests ├── cookie.test.ts ├── failing.test.ts ├── helper.ts ├── session.test.ts ├── signedCookie.test.ts └── utils.spec.ts ├── tsconfig.json └── vitest.config.ts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: aldy505 2 | github: [aldy505] 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | 15 | Steps to reproduce the behavior: 16 | 17 | 1. ... 18 | 2. ... 19 | 3. ... 20 | 21 | **Expected behavior** 22 | 23 | A clear and concise description of what you expected to happen. 24 | 25 | **Versions** 26 | 27 | - `node`: 14 28 | - `malibu`: 0.X 29 | - `@tinyhttp/app`: 0.X 30 | 31 | **Additional context** 32 | 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | What problem does it solve? 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [master] 10 | pull_request: 11 | branches: [master] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "test" 16 | test: 17 | # The type of runner that the job will run on 18 | runs-on: ubuntu-latest 19 | 20 | # Steps represent a sequence of tasks that will be executed as part of the job 21 | steps: 22 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 23 | - uses: actions/checkout@v4 24 | - uses: actions/setup-node@v3 25 | with: 26 | node-version: '20' 27 | - name: install pnpm 28 | uses: pnpm/action-setup@v2 29 | with: 30 | version: 8 31 | - run: pnpm install 32 | - run: pnpm test:coverage 33 | - name: Coveralls 34 | uses: coverallsapp/github-action@master 35 | with: 36 | github-token: ${{ secrets.GITHUB_TOKEN }} 37 | path-to-lcov: ./coverage/lcov.info 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | .env.production 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | .parcel-cache 80 | 81 | # Next.js build output 82 | .next 83 | out 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and not Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # yarn v2 114 | .yarn/cache 115 | .yarn/unplugged 116 | .yarn/build-state.yml 117 | .yarn/install-state.gz 118 | .pnp.* -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no-install commitlint --config commitlint.config.mjs --edit "$1" -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm check 2 | pnpm test 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are always welcome, here's an instruction of how to contribute. 4 | 5 | ## Local setup 6 | 7 | ### Install 8 | 9 | - Clone the repo first: 10 | 11 | ```sh 12 | # git 13 | git clone https://github.com/tinyhttp/malibu.git 14 | 15 | # (or) hub 16 | hub clone tinyhttp/malibu 17 | ``` 18 | 19 | - Install Node.js (v14 is recommended) and `pnpm`: 20 | 21 | ```sh 22 | # Install fnm 23 | curl -fsSL https://github.com/Schniz/fnm/raw/master/.ci/install.sh | bash 24 | 25 | # Install latest Node.js version 26 | fnm install latest 27 | fnm use latest 28 | 29 | # Install pnpm 30 | curl -L https://raw.githubusercontent.com/pnpm/self-installer/master/install.js | node 31 | 32 | # Or, via npm 33 | npm i -g pnpm 34 | ``` 35 | 36 | - Install the dependencies at root and in the packages: 37 | 38 | ```sh 39 | pnpm i 40 | ``` 41 | 42 | ### Formatting 43 | 44 | If you use VS Code, please install Biome plugin for proper linting and code formatting. 45 | 46 | If you use a text editor that doesn't have Prettier integration, you can run `pnpx format:fix"` 47 | 48 | ## Submitting PRs 49 | 50 | ### General rules 51 | 52 | Here's a small list of requirements for your PR: 53 | 54 | - it should be linted and formatted properly using configurations in the root 55 | - it should build without errors and warnings (except edge cases) 56 | - it should have been tested 57 | - PR must have a clear description of what it does, which part of the repo it affects 58 | - if PR is adding a new middleware, it should have an example in the description. 59 | 60 | In most other cases, additional steps aren't required. Install, write, test, lint and your code is ready to be submitted! 61 | 62 | If everything from the list is done right, feel free to submit a PR! I will look into it asap. 63 | 64 | If some further assistance before making a PR is needed, ping [aldy505](https://t.me/aldy505) or [talentlessguy](talentless_guy) on telegram. 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-present Reinaldy Rafli and Malibu contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

3 | Malibu 4 |

5 | 6 | [![npm](https://img.shields.io/npm/v/malibu?style=for-the-badge&logo=npm&label=&color=26B0A0)](https://npmjs.com/package/malibu) [![npm](https://img.shields.io/npm/dt/malibu?style=for-the-badge&color=26B0A0)](https://npmjs.com/package/malibu) [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/tinyhttp/malibu/ci.yml?branch=master&label=&logo=github&style=for-the-badge&color=26B0A0)](https://github.com/tinyhttp/malibu/actions) [![Coveralls](https://img.shields.io/coveralls/github/tinyhttp/malibu?style=for-the-badge&color=26B0A0)](https://coveralls.io/github/tinyhttp/malibu) [![Code Quality](https://img.shields.io/codefactor/grade/github/tinyhttp/malibu?style=for-the-badge&color=26B0A0)](https://www.codefactor.io/repository/github/tinyhttp/malibu) 7 | 8 |
9 | 10 | This middleware helps web developers fight [CSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery) attacks. Bear in mind, by solely using this middleware, we can't guarantee your app will be free from CSRF attacks. Refer to [CSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html) and [pillarjs/understanding-csrf](https://github.com/pillarjs/understanding-csrf) for more details. 11 | 12 | * ⚡ Framework agnostic (works with Express, Tinyhttp, Polka, and more!) 13 | * ✨ Native ESM (No CommonJS support) 14 | * 🛠 Typescript typings out of the box 15 | * 🚀 No legacy dependencies 16 | 17 | ## Install 18 | 19 | ``` 20 | pnpm i malibu 21 | ``` 22 | 23 | ## Usage 24 | 25 | Like all CSRF plugins, it depends on either Cookie Parser or Session middleware. 26 | 27 | ```js 28 | import { App } from '@tinyhttp/app' 29 | import { cookieParser } from '@tinyhttp/cookie-parser' 30 | import { csrf } from 'malibu' 31 | 32 | const app = new App() 33 | 34 | const csrfProtection = csrf() 35 | app.use(cookieParser()) 36 | 37 | // this lets you acquire CSRF token on response body 38 | // you also have CSRF token on your cookies as _csrf 39 | app.get('/', csrfProtection, (req, res) => { 40 | res.status(200).json({ token: req.csrfToken() }) 41 | }) 42 | 43 | // you may only access this if you give a previously acquired CSRF token 44 | app.post('/', csrfProtection, (req, res) => { 45 | res.status(200).json({ message: 'hello' }) 46 | }) 47 | ``` 48 | 49 | For signed cookies: 50 | 51 | ```js 52 | const app = new App() 53 | 54 | const csrfProtection = csrf({ cookie: { signed: true } }) 55 | app.use(cookieParser('secret key')) 56 | 57 | // this lets you acquire CSRF token on the response body 58 | // you also have a CSRF token on your cookies as _csrf 59 | app.get('/', csrfProtection, (req, res) => { 60 | res.status(200).json({ token: req.csrfToken() }) 61 | }) 62 | 63 | // you may only access this if you give a previously acquired CSRF token 64 | app.post('/', csrfProtection, (req, res) => { 65 | res.status(200).json({ message: 'hello' }) 66 | }) 67 | ``` 68 | 69 | With [express-session](https://github.com/expressjs/session): 70 | 71 | ```js 72 | import { App } from '@tinyhttp/app' 73 | import session from 'express-session' 74 | import { csrf } from 'malibu' 75 | 76 | const app = new App() 77 | 78 | const csrfProtection = csrf({ middleware: 'session' }) 79 | app.use(session({ secret: 'secret key', resave: false, saveUninitialized: false })) 80 | 81 | // this lets you acquire CSRF token on response body 82 | app.get('/', csrfProtection, (req, res) => { 83 | res.status(200).json({ token: req.csrfToken() }) 84 | }) 85 | 86 | // you may only access this if you give a previously acquired CSRF token 87 | app.post('/', csrfProtection, (req, res) => { 88 | res.status(200).json({ message: 'hello' }) 89 | }) 90 | ``` 91 | 92 | For detailed example, please refer to [examples](https://github.com/tinyhttp/tinyhttp/tree/master/examples/csrf) 93 | 94 | ## Options 95 | 96 | | Name | Type | Default | Description | 97 | | ------------ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 98 | | middleware | `string` | `cookie` | Specifies which middleware to look for. Available options are `cookie` and `session` | 99 | | cookie | `CookieOptions` | `{ signed: false, key: '_csrf', path: '/' }` | `signed` specifies whether the cookie is signed or unsigned, `key` specifies to the cookie key, `path` specifies the domain of the cookie. For other options please refer to [@tinyhttp/cookie serializer options](https://github.com/tinyhttp/tinyhttp/tree/master/packages/cookie#options-1) | 100 | | sessionKey | `string` | `session` | Specifies session key name | 101 | | value | `(req: Request) => any` | `req.body._csrf, req.query._csrf, req.headers["csrf-token"], req.headers["xsrf-token"], req.headers["x-csrf-token"], req.headers["x-xsrf-token"]` | Specifies where to look for the CSRF token | 102 | | ignoreMethod | `Array` | `["GET", "HEAD", "OPTIONS"]` | Specifies the HTTP Method in which CSRF protection will be disabled | 103 | | saltLength | `number` | `8` | Specifies the salt length for CSRF token | 104 | | secretLength | `number` | `18` | Specifies the secret length for CSRF Token | 105 | 106 | ## Why "malibu"? 107 | 108 | It's one variation of a longboard used in surfing. It's a 60's style longboard, made with heavy glass, long parallel 50/50 rails, and a deep single fin. Made especially for trimming, (walking the board) and for noseriding. Not to mention, it looks cool. 109 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.8.2/schema.json", 3 | "files": { 4 | "ignore": ["node_modules", "dist", "coverage", ".pnpm-store"] 5 | }, 6 | "formatter": { 7 | "enabled": true, 8 | "formatWithErrors": false, 9 | "indentStyle": "space", 10 | "indentWidth": 2, 11 | "lineEnding": "lf", 12 | "lineWidth": 120, 13 | "attributePosition": "auto" 14 | }, 15 | "organizeImports": { 16 | "enabled": true 17 | }, 18 | "linter": { 19 | "enabled": true, 20 | "rules": { 21 | "recommended": true, 22 | "correctness": { 23 | "noVoidTypeReturn": "off" 24 | }, 25 | "style": { 26 | "noParameterAssign": "off", 27 | "noNonNullAssertion": "off" 28 | }, 29 | "suspicious": { 30 | "noAssignInExpressions": "off", 31 | "noExplicitAny": "off" 32 | } 33 | } 34 | }, 35 | "javascript": { 36 | "formatter": { 37 | "jsxQuoteStyle": "double", 38 | "quoteProperties": "asNeeded", 39 | "trailingCommas": "none", 40 | "semicolons": "asNeeded", 41 | "arrowParentheses": "always", 42 | "bracketSpacing": true, 43 | "bracketSameLine": false, 44 | "quoteStyle": "single", 45 | "attributePosition": "auto" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /commitlint.config.mjs: -------------------------------------------------------------------------------- 1 | import defaultConfig from '@commitlint/config-conventional' 2 | 3 | export default { 4 | extends: ['@commitlint/config-conventional'], 5 | rules: { 6 | ...defaultConfig.rules, 7 | 'type-enum': [ 8 | 2, 9 | 'always', 10 | ['fix', 'test', 'tooling', 'refactor', 'revert', 'example', 'docs', 'format', 'feat', 'chore'] 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "malibu", 3 | "version": "1.0.6", 4 | "description": "Framework-agnostic CSRF middleware", 5 | "keywords": ["csrf", "middleware", "tokens", "framework agnostic", "express", "tinyhttp", "polka", "http"], 6 | "author": "Reinaldy Rafli ", 7 | "license": "MIT", 8 | "type": "module", 9 | "types": "./dist/index.d.ts", 10 | "exports": "./dist/index.js", 11 | "files": ["dist"], 12 | "scripts": { 13 | "build": "tsc", 14 | "test": "vitest run", 15 | "test:coverage": "vitest run --coverage", 16 | "lint": "biome lint .", 17 | "format": "biome format .", 18 | "check": "biome check .", 19 | "prepare": "husky" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/tinyhttp/malibu" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/tinyhttp/malibu/issues" 27 | }, 28 | "homepage": "https://github.com/tinyhttp/malibu#readme", 29 | "directories": { 30 | "test": "./test", 31 | "lib": "./src" 32 | }, 33 | "engines": { 34 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 35 | }, 36 | "devDependencies": { 37 | "@biomejs/biome": "1.9.1", 38 | "@commitlint/cli": "19.5.0", 39 | "@commitlint/config-conventional": "19.5.0", 40 | "@tinyhttp/app": "2.4.0", 41 | "@tinyhttp/cookie-parser": "2.0.6", 42 | "@types/express-session": "1.18.0", 43 | "@types/node": "22.5.5", 44 | "@vitest/coverage-v8": "2.1.1", 45 | "express-session": "1.18.0", 46 | "husky": "9.1.6", 47 | "milliparsec": "4.0.0", 48 | "supertest-fetch": "2.0.0", 49 | "typescript": "5.6.2", 50 | "vitest": "2.1.1" 51 | }, 52 | "dependencies": { 53 | "@tinyhttp/cookie": "2.1.1", 54 | "@tinyhttp/cookie-signature": "2.1.1" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | '@tinyhttp/cookie': 12 | specifier: 2.1.1 13 | version: 2.1.1 14 | '@tinyhttp/cookie-signature': 15 | specifier: 2.1.1 16 | version: 2.1.1 17 | devDependencies: 18 | '@biomejs/biome': 19 | specifier: 1.9.1 20 | version: 1.9.1 21 | '@commitlint/cli': 22 | specifier: 19.5.0 23 | version: 19.5.0(@types/node@22.5.5)(typescript@5.6.2) 24 | '@commitlint/config-conventional': 25 | specifier: 19.5.0 26 | version: 19.5.0 27 | '@tinyhttp/app': 28 | specifier: 2.4.0 29 | version: 2.4.0 30 | '@tinyhttp/cookie-parser': 31 | specifier: 2.0.6 32 | version: 2.0.6 33 | '@types/express-session': 34 | specifier: 1.18.0 35 | version: 1.18.0 36 | '@types/node': 37 | specifier: 22.5.5 38 | version: 22.5.5 39 | '@vitest/coverage-v8': 40 | specifier: 2.1.1 41 | version: 2.1.1(vitest@2.1.1(@types/node@22.5.5)) 42 | express-session: 43 | specifier: 1.18.0 44 | version: 1.18.0 45 | husky: 46 | specifier: 9.1.6 47 | version: 9.1.6 48 | milliparsec: 49 | specifier: 4.0.0 50 | version: 4.0.0 51 | supertest-fetch: 52 | specifier: 2.0.0 53 | version: 2.0.0 54 | typescript: 55 | specifier: 5.6.2 56 | version: 5.6.2 57 | vitest: 58 | specifier: 2.1.1 59 | version: 2.1.1(@types/node@22.5.5) 60 | 61 | packages: 62 | 63 | '@ampproject/remapping@2.3.0': 64 | resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} 65 | engines: {node: '>=6.0.0'} 66 | 67 | '@babel/code-frame@7.18.6': 68 | resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} 69 | engines: {node: '>=6.9.0'} 70 | 71 | '@babel/helper-string-parser@7.24.8': 72 | resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} 73 | engines: {node: '>=6.9.0'} 74 | 75 | '@babel/helper-validator-identifier@7.24.7': 76 | resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} 77 | engines: {node: '>=6.9.0'} 78 | 79 | '@babel/highlight@7.18.6': 80 | resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} 81 | engines: {node: '>=6.9.0'} 82 | 83 | '@babel/parser@7.25.6': 84 | resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==} 85 | engines: {node: '>=6.0.0'} 86 | hasBin: true 87 | 88 | '@babel/types@7.25.6': 89 | resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} 90 | engines: {node: '>=6.9.0'} 91 | 92 | '@bcoe/v8-coverage@0.2.3': 93 | resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} 94 | 95 | '@biomejs/biome@1.9.1': 96 | resolution: {integrity: sha512-Ps0Rg0zg3B1zpx+zQHMz5b0n0PBNCAaXttHEDTVrJD5YXR6Uj3T+abTDgeS3wsu4z5i2whqcE1lZxGyWH4bZYg==} 97 | engines: {node: '>=14.21.3'} 98 | hasBin: true 99 | 100 | '@biomejs/cli-darwin-arm64@1.9.1': 101 | resolution: {integrity: sha512-js0brHswq/BoeKgfSEUJYOjUOlML6p65Nantti+PsoQ61u9+YVGIZ7325LK7iUpDH8KVJT+Bx7K2b/6Q//W1Pw==} 102 | engines: {node: '>=14.21.3'} 103 | cpu: [arm64] 104 | os: [darwin] 105 | 106 | '@biomejs/cli-darwin-x64@1.9.1': 107 | resolution: {integrity: sha512-2zVyjUg5rN0k8XrytkubQWLbp2r/AS5wPhXs4vgVjvqbLnzo32EGX8p61gzroF2dH9DCUCfskdrigCGqNdEbpg==} 108 | engines: {node: '>=14.21.3'} 109 | cpu: [x64] 110 | os: [darwin] 111 | 112 | '@biomejs/cli-linux-arm64-musl@1.9.1': 113 | resolution: {integrity: sha512-L/JmXKvhsZ1lTgqOr3tWkzuY/NRppdIscHeC9aaiR72WjnBgJS94mawl9BWmGB3aWBc0q6oSDWnBS7617EMMmA==} 114 | engines: {node: '>=14.21.3'} 115 | cpu: [arm64] 116 | os: [linux] 117 | 118 | '@biomejs/cli-linux-arm64@1.9.1': 119 | resolution: {integrity: sha512-QgxwfnG+r2aer5RNGR67Ey91Tv7xXW8E9YckHhwuyWjdLEvKWkrSJrhVG/6ub0kVvTSNkYOuT/7/jMOFBuUbRA==} 120 | engines: {node: '>=14.21.3'} 121 | cpu: [arm64] 122 | os: [linux] 123 | 124 | '@biomejs/cli-linux-x64-musl@1.9.1': 125 | resolution: {integrity: sha512-gY+eFLIAW45v3WicQHicvjRfA0ntMZHx7h937bXwBMFNFoKmB6rMi6+fKQ6/hiS6juhsFxZdZIz20m15s49J6A==} 126 | engines: {node: '>=14.21.3'} 127 | cpu: [x64] 128 | os: [linux] 129 | 130 | '@biomejs/cli-linux-x64@1.9.1': 131 | resolution: {integrity: sha512-F0INygtzI2L2n2R1KtYHGr3YWDt9Up1zrUluwembM+iJ1dXN3qzlSb7deFUsSJm4FaIPriqs6Xa56ukdQW6UeQ==} 132 | engines: {node: '>=14.21.3'} 133 | cpu: [x64] 134 | os: [linux] 135 | 136 | '@biomejs/cli-win32-arm64@1.9.1': 137 | resolution: {integrity: sha512-7Jahxar3OB+aTPOgXisMJmMKMsjcK+UmdlG3UIOQjzN/ZFEsPV+GT3bfrVjZDQaCw/zes0Cqd7VTWFjFTC/+MQ==} 138 | engines: {node: '>=14.21.3'} 139 | cpu: [arm64] 140 | os: [win32] 141 | 142 | '@biomejs/cli-win32-x64@1.9.1': 143 | resolution: {integrity: sha512-liSRWjWzFhyG7s1jg/Bbv9FL+ha/CEd5tFO3+dFIJNplL4TnvAivtyfRVi/tu/pNjISbV1k9JwdBewtAKAgA0w==} 144 | engines: {node: '>=14.21.3'} 145 | cpu: [x64] 146 | os: [win32] 147 | 148 | '@commitlint/cli@19.5.0': 149 | resolution: {integrity: sha512-gaGqSliGwB86MDmAAKAtV9SV1SHdmN8pnGq4EJU4+hLisQ7IFfx4jvU4s+pk6tl0+9bv6yT+CaZkufOinkSJIQ==} 150 | engines: {node: '>=v18'} 151 | hasBin: true 152 | 153 | '@commitlint/config-conventional@19.5.0': 154 | resolution: {integrity: sha512-OBhdtJyHNPryZKg0fFpZNOBM1ZDbntMvqMuSmpfyP86XSfwzGw4CaoYRG4RutUPg0BTK07VMRIkNJT6wi2zthg==} 155 | engines: {node: '>=v18'} 156 | 157 | '@commitlint/config-validator@19.5.0': 158 | resolution: {integrity: sha512-CHtj92H5rdhKt17RmgALhfQt95VayrUo2tSqY9g2w+laAXyk7K/Ef6uPm9tn5qSIwSmrLjKaXK9eiNuxmQrDBw==} 159 | engines: {node: '>=v18'} 160 | 161 | '@commitlint/ensure@19.5.0': 162 | resolution: {integrity: sha512-Kv0pYZeMrdg48bHFEU5KKcccRfKmISSm9MvgIgkpI6m+ohFTB55qZlBW6eYqh/XDfRuIO0x4zSmvBjmOwWTwkg==} 163 | engines: {node: '>=v18'} 164 | 165 | '@commitlint/execute-rule@19.5.0': 166 | resolution: {integrity: sha512-aqyGgytXhl2ejlk+/rfgtwpPexYyri4t8/n4ku6rRJoRhGZpLFMqrZ+YaubeGysCP6oz4mMA34YSTaSOKEeNrg==} 167 | engines: {node: '>=v18'} 168 | 169 | '@commitlint/format@19.5.0': 170 | resolution: {integrity: sha512-yNy088miE52stCI3dhG/vvxFo9e4jFkU1Mj3xECfzp/bIS/JUay4491huAlVcffOoMK1cd296q0W92NlER6r3A==} 171 | engines: {node: '>=v18'} 172 | 173 | '@commitlint/is-ignored@19.5.0': 174 | resolution: {integrity: sha512-0XQ7Llsf9iL/ANtwyZ6G0NGp5Y3EQ8eDQSxv/SRcfJ0awlBY4tHFAvwWbw66FVUaWICH7iE5en+FD9TQsokZ5w==} 175 | engines: {node: '>=v18'} 176 | 177 | '@commitlint/lint@19.5.0': 178 | resolution: {integrity: sha512-cAAQwJcRtiBxQWO0eprrAbOurtJz8U6MgYqLz+p9kLElirzSCc0vGMcyCaA1O7AqBuxo11l1XsY3FhOFowLAAg==} 179 | engines: {node: '>=v18'} 180 | 181 | '@commitlint/load@19.5.0': 182 | resolution: {integrity: sha512-INOUhkL/qaKqwcTUvCE8iIUf5XHsEPCLY9looJ/ipzi7jtGhgmtH7OOFiNvwYgH7mA8osUWOUDV8t4E2HAi4xA==} 183 | engines: {node: '>=v18'} 184 | 185 | '@commitlint/message@19.5.0': 186 | resolution: {integrity: sha512-R7AM4YnbxN1Joj1tMfCyBryOC5aNJBdxadTZkuqtWi3Xj0kMdutq16XQwuoGbIzL2Pk62TALV1fZDCv36+JhTQ==} 187 | engines: {node: '>=v18'} 188 | 189 | '@commitlint/parse@19.5.0': 190 | resolution: {integrity: sha512-cZ/IxfAlfWYhAQV0TwcbdR1Oc0/r0Ik1GEessDJ3Lbuma/MRO8FRQX76eurcXtmhJC//rj52ZSZuXUg0oIX0Fw==} 191 | engines: {node: '>=v18'} 192 | 193 | '@commitlint/read@19.5.0': 194 | resolution: {integrity: sha512-TjS3HLPsLsxFPQj6jou8/CZFAmOP2y+6V4PGYt3ihbQKTY1Jnv0QG28WRKl/d1ha6zLODPZqsxLEov52dhR9BQ==} 195 | engines: {node: '>=v18'} 196 | 197 | '@commitlint/resolve-extends@19.5.0': 198 | resolution: {integrity: sha512-CU/GscZhCUsJwcKTJS9Ndh3AKGZTNFIOoQB2n8CmFnizE0VnEuJoum+COW+C1lNABEeqk6ssfc1Kkalm4bDklA==} 199 | engines: {node: '>=v18'} 200 | 201 | '@commitlint/rules@19.5.0': 202 | resolution: {integrity: sha512-hDW5TPyf/h1/EufSHEKSp6Hs+YVsDMHazfJ2azIk9tHPXS6UqSz1dIRs1gpqS3eMXgtkT7JH6TW4IShdqOwhAw==} 203 | engines: {node: '>=v18'} 204 | 205 | '@commitlint/to-lines@19.5.0': 206 | resolution: {integrity: sha512-R772oj3NHPkodOSRZ9bBVNq224DOxQtNef5Pl8l2M8ZnkkzQfeSTr4uxawV2Sd3ui05dUVzvLNnzenDBO1KBeQ==} 207 | engines: {node: '>=v18'} 208 | 209 | '@commitlint/top-level@19.5.0': 210 | resolution: {integrity: sha512-IP1YLmGAk0yWrImPRRc578I3dDUI5A2UBJx9FbSOjxe9sTlzFiwVJ+zeMLgAtHMtGZsC8LUnzmW1qRemkFU4ng==} 211 | engines: {node: '>=v18'} 212 | 213 | '@commitlint/types@19.5.0': 214 | resolution: {integrity: sha512-DSHae2obMSMkAtTBSOulg5X7/z+rGLxcXQIkg3OmWvY6wifojge5uVMydfhUvs7yQj+V7jNmRZ2Xzl8GJyqRgg==} 215 | engines: {node: '>=v18'} 216 | 217 | '@esbuild/aix-ppc64@0.21.5': 218 | resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} 219 | engines: {node: '>=12'} 220 | cpu: [ppc64] 221 | os: [aix] 222 | 223 | '@esbuild/android-arm64@0.21.5': 224 | resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} 225 | engines: {node: '>=12'} 226 | cpu: [arm64] 227 | os: [android] 228 | 229 | '@esbuild/android-arm@0.21.5': 230 | resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} 231 | engines: {node: '>=12'} 232 | cpu: [arm] 233 | os: [android] 234 | 235 | '@esbuild/android-x64@0.21.5': 236 | resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} 237 | engines: {node: '>=12'} 238 | cpu: [x64] 239 | os: [android] 240 | 241 | '@esbuild/darwin-arm64@0.21.5': 242 | resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} 243 | engines: {node: '>=12'} 244 | cpu: [arm64] 245 | os: [darwin] 246 | 247 | '@esbuild/darwin-x64@0.21.5': 248 | resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} 249 | engines: {node: '>=12'} 250 | cpu: [x64] 251 | os: [darwin] 252 | 253 | '@esbuild/freebsd-arm64@0.21.5': 254 | resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} 255 | engines: {node: '>=12'} 256 | cpu: [arm64] 257 | os: [freebsd] 258 | 259 | '@esbuild/freebsd-x64@0.21.5': 260 | resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} 261 | engines: {node: '>=12'} 262 | cpu: [x64] 263 | os: [freebsd] 264 | 265 | '@esbuild/linux-arm64@0.21.5': 266 | resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} 267 | engines: {node: '>=12'} 268 | cpu: [arm64] 269 | os: [linux] 270 | 271 | '@esbuild/linux-arm@0.21.5': 272 | resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} 273 | engines: {node: '>=12'} 274 | cpu: [arm] 275 | os: [linux] 276 | 277 | '@esbuild/linux-ia32@0.21.5': 278 | resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} 279 | engines: {node: '>=12'} 280 | cpu: [ia32] 281 | os: [linux] 282 | 283 | '@esbuild/linux-loong64@0.21.5': 284 | resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} 285 | engines: {node: '>=12'} 286 | cpu: [loong64] 287 | os: [linux] 288 | 289 | '@esbuild/linux-mips64el@0.21.5': 290 | resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} 291 | engines: {node: '>=12'} 292 | cpu: [mips64el] 293 | os: [linux] 294 | 295 | '@esbuild/linux-ppc64@0.21.5': 296 | resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} 297 | engines: {node: '>=12'} 298 | cpu: [ppc64] 299 | os: [linux] 300 | 301 | '@esbuild/linux-riscv64@0.21.5': 302 | resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} 303 | engines: {node: '>=12'} 304 | cpu: [riscv64] 305 | os: [linux] 306 | 307 | '@esbuild/linux-s390x@0.21.5': 308 | resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} 309 | engines: {node: '>=12'} 310 | cpu: [s390x] 311 | os: [linux] 312 | 313 | '@esbuild/linux-x64@0.21.5': 314 | resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} 315 | engines: {node: '>=12'} 316 | cpu: [x64] 317 | os: [linux] 318 | 319 | '@esbuild/netbsd-x64@0.21.5': 320 | resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} 321 | engines: {node: '>=12'} 322 | cpu: [x64] 323 | os: [netbsd] 324 | 325 | '@esbuild/openbsd-x64@0.21.5': 326 | resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} 327 | engines: {node: '>=12'} 328 | cpu: [x64] 329 | os: [openbsd] 330 | 331 | '@esbuild/sunos-x64@0.21.5': 332 | resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} 333 | engines: {node: '>=12'} 334 | cpu: [x64] 335 | os: [sunos] 336 | 337 | '@esbuild/win32-arm64@0.21.5': 338 | resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} 339 | engines: {node: '>=12'} 340 | cpu: [arm64] 341 | os: [win32] 342 | 343 | '@esbuild/win32-ia32@0.21.5': 344 | resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} 345 | engines: {node: '>=12'} 346 | cpu: [ia32] 347 | os: [win32] 348 | 349 | '@esbuild/win32-x64@0.21.5': 350 | resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} 351 | engines: {node: '>=12'} 352 | cpu: [x64] 353 | os: [win32] 354 | 355 | '@isaacs/cliui@8.0.2': 356 | resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} 357 | engines: {node: '>=12'} 358 | 359 | '@istanbuljs/schema@0.1.3': 360 | resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} 361 | engines: {node: '>=8'} 362 | 363 | '@jridgewell/gen-mapping@0.3.5': 364 | resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} 365 | engines: {node: '>=6.0.0'} 366 | 367 | '@jridgewell/resolve-uri@3.1.0': 368 | resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} 369 | engines: {node: '>=6.0.0'} 370 | 371 | '@jridgewell/set-array@1.2.1': 372 | resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} 373 | engines: {node: '>=6.0.0'} 374 | 375 | '@jridgewell/sourcemap-codec@1.4.14': 376 | resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} 377 | 378 | '@jridgewell/sourcemap-codec@1.5.0': 379 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 380 | 381 | '@jridgewell/trace-mapping@0.3.25': 382 | resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 383 | 384 | '@pkgjs/parseargs@0.11.0': 385 | resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 386 | engines: {node: '>=14'} 387 | 388 | '@rollup/rollup-android-arm-eabi@4.24.3': 389 | resolution: {integrity: sha512-ufb2CH2KfBWPJok95frEZZ82LtDl0A6QKTa8MoM+cWwDZvVGl5/jNb79pIhRvAalUu+7LD91VYR0nwRD799HkQ==} 390 | cpu: [arm] 391 | os: [android] 392 | 393 | '@rollup/rollup-android-arm64@4.24.3': 394 | resolution: {integrity: sha512-iAHpft/eQk9vkWIV5t22V77d90CRofgR2006UiCjHcHJFVI1E0oBkQIAbz+pLtthFw3hWEmVB4ilxGyBf48i2Q==} 395 | cpu: [arm64] 396 | os: [android] 397 | 398 | '@rollup/rollup-darwin-arm64@4.24.3': 399 | resolution: {integrity: sha512-QPW2YmkWLlvqmOa2OwrfqLJqkHm7kJCIMq9kOz40Zo9Ipi40kf9ONG5Sz76zszrmIZZ4hgRIkez69YnTHgEz1w==} 400 | cpu: [arm64] 401 | os: [darwin] 402 | 403 | '@rollup/rollup-darwin-x64@4.24.3': 404 | resolution: {integrity: sha512-KO0pN5x3+uZm1ZXeIfDqwcvnQ9UEGN8JX5ufhmgH5Lz4ujjZMAnxQygZAVGemFWn+ZZC0FQopruV4lqmGMshow==} 405 | cpu: [x64] 406 | os: [darwin] 407 | 408 | '@rollup/rollup-freebsd-arm64@4.24.3': 409 | resolution: {integrity: sha512-CsC+ZdIiZCZbBI+aRlWpYJMSWvVssPuWqrDy/zi9YfnatKKSLFCe6fjna1grHuo/nVaHG+kiglpRhyBQYRTK4A==} 410 | cpu: [arm64] 411 | os: [freebsd] 412 | 413 | '@rollup/rollup-freebsd-x64@4.24.3': 414 | resolution: {integrity: sha512-F0nqiLThcfKvRQhZEzMIXOQG4EeX61im61VYL1jo4eBxv4aZRmpin6crnBJQ/nWnCsjH5F6J3W6Stdm0mBNqBg==} 415 | cpu: [x64] 416 | os: [freebsd] 417 | 418 | '@rollup/rollup-linux-arm-gnueabihf@4.24.3': 419 | resolution: {integrity: sha512-KRSFHyE/RdxQ1CSeOIBVIAxStFC/hnBgVcaiCkQaVC+EYDtTe4X7z5tBkFyRoBgUGtB6Xg6t9t2kulnX6wJc6A==} 420 | cpu: [arm] 421 | os: [linux] 422 | 423 | '@rollup/rollup-linux-arm-musleabihf@4.24.3': 424 | resolution: {integrity: sha512-h6Q8MT+e05zP5BxEKz0vi0DhthLdrNEnspdLzkoFqGwnmOzakEHSlXfVyA4HJ322QtFy7biUAVFPvIDEDQa6rw==} 425 | cpu: [arm] 426 | os: [linux] 427 | 428 | '@rollup/rollup-linux-arm64-gnu@4.24.3': 429 | resolution: {integrity: sha512-fKElSyXhXIJ9pqiYRqisfirIo2Z5pTTve5K438URf08fsypXrEkVmShkSfM8GJ1aUyvjakT+fn2W7Czlpd/0FQ==} 430 | cpu: [arm64] 431 | os: [linux] 432 | 433 | '@rollup/rollup-linux-arm64-musl@4.24.3': 434 | resolution: {integrity: sha512-YlddZSUk8G0px9/+V9PVilVDC6ydMz7WquxozToozSnfFK6wa6ne1ATUjUvjin09jp34p84milxlY5ikueoenw==} 435 | cpu: [arm64] 436 | os: [linux] 437 | 438 | '@rollup/rollup-linux-powerpc64le-gnu@4.24.3': 439 | resolution: {integrity: sha512-yNaWw+GAO8JjVx3s3cMeG5Esz1cKVzz8PkTJSfYzE5u7A+NvGmbVFEHP+BikTIyYWuz0+DX9kaA3pH9Sqxp69g==} 440 | cpu: [ppc64] 441 | os: [linux] 442 | 443 | '@rollup/rollup-linux-riscv64-gnu@4.24.3': 444 | resolution: {integrity: sha512-lWKNQfsbpv14ZCtM/HkjCTm4oWTKTfxPmr7iPfp3AHSqyoTz5AgLemYkWLwOBWc+XxBbrU9SCokZP0WlBZM9lA==} 445 | cpu: [riscv64] 446 | os: [linux] 447 | 448 | '@rollup/rollup-linux-s390x-gnu@4.24.3': 449 | resolution: {integrity: sha512-HoojGXTC2CgCcq0Woc/dn12wQUlkNyfH0I1ABK4Ni9YXyFQa86Fkt2Q0nqgLfbhkyfQ6003i3qQk9pLh/SpAYw==} 450 | cpu: [s390x] 451 | os: [linux] 452 | 453 | '@rollup/rollup-linux-x64-gnu@4.24.3': 454 | resolution: {integrity: sha512-mnEOh4iE4USSccBOtcrjF5nj+5/zm6NcNhbSEfR3Ot0pxBwvEn5QVUXcuOwwPkapDtGZ6pT02xLoPaNv06w7KQ==} 455 | cpu: [x64] 456 | os: [linux] 457 | 458 | '@rollup/rollup-linux-x64-musl@4.24.3': 459 | resolution: {integrity: sha512-rMTzawBPimBQkG9NKpNHvquIUTQPzrnPxPbCY1Xt+mFkW7pshvyIS5kYgcf74goxXOQk0CP3EoOC1zcEezKXhw==} 460 | cpu: [x64] 461 | os: [linux] 462 | 463 | '@rollup/rollup-win32-arm64-msvc@4.24.3': 464 | resolution: {integrity: sha512-2lg1CE305xNvnH3SyiKwPVsTVLCg4TmNCF1z7PSHX2uZY2VbUpdkgAllVoISD7JO7zu+YynpWNSKAtOrX3AiuA==} 465 | cpu: [arm64] 466 | os: [win32] 467 | 468 | '@rollup/rollup-win32-ia32-msvc@4.24.3': 469 | resolution: {integrity: sha512-9SjYp1sPyxJsPWuhOCX6F4jUMXGbVVd5obVpoVEi8ClZqo52ViZewA6eFz85y8ezuOA+uJMP5A5zo6Oz4S5rVQ==} 470 | cpu: [ia32] 471 | os: [win32] 472 | 473 | '@rollup/rollup-win32-x64-msvc@4.24.3': 474 | resolution: {integrity: sha512-HGZgRFFYrMrP3TJlq58nR1xy8zHKId25vhmm5S9jETEfDf6xybPxsavFTJaufe2zgOGYJBskGlj49CwtEuFhWQ==} 475 | cpu: [x64] 476 | os: [win32] 477 | 478 | '@tinyhttp/accepts@2.2.3': 479 | resolution: {integrity: sha512-9pQN6pJAJOU3McmdJWTcyq7LLFW8Lj5q+DadyKcvp+sxMkEpktKX5sbfJgJuOvjk6+1xWl7pe0YL1US1vaO/1w==} 480 | engines: {node: '>=12.20.0'} 481 | 482 | '@tinyhttp/app@2.4.0': 483 | resolution: {integrity: sha512-vOPiCemQRJq5twnl06dde6XnWiNbVMdVRFJWW/yC/9G0qgvV2TvzNNTxrdlz6YmyB7vIC7Fg3qS6m6gx8RbBNQ==} 484 | engines: {node: '>=14.21.3'} 485 | 486 | '@tinyhttp/content-disposition@2.2.2': 487 | resolution: {integrity: sha512-crXw1txzrS36huQOyQGYFvhTeLeG0Si1xu+/l6kXUVYpE0TjFjEZRqTbuadQLfKGZ0jaI+jJoRyqaWwxOSHW2g==} 488 | engines: {node: '>=12.20.0'} 489 | 490 | '@tinyhttp/content-type@0.1.4': 491 | resolution: {integrity: sha512-dl6f3SHIJPYbhsW1oXdrqOmLSQF/Ctlv3JnNfXAE22kIP7FosqJHxkz/qj2gv465prG8ODKH5KEyhBkvwrueKQ==} 492 | engines: {node: '>=12.4'} 493 | 494 | '@tinyhttp/cookie-parser@2.0.6': 495 | resolution: {integrity: sha512-hLcFr3ml6MBU71URUutvzh8p0PgqGpmTMcorUrRS+Fck0/q3tzScE+ghhocgIoSx/F6GRtS/TYJkJrjL/ickBw==} 496 | engines: {node: '>=12.4 || 14.x || >=16'} 497 | 498 | '@tinyhttp/cookie-signature@2.1.1': 499 | resolution: {integrity: sha512-VDsSMY5OJfQJIAtUgeQYhqMPSZptehFSfvEEtxr+4nldPA8IImlp3QVcOVuK985g4AFR4Hl1sCbWCXoqBnVWnw==} 500 | engines: {node: '>=12.20.0'} 501 | 502 | '@tinyhttp/cookie@2.1.1': 503 | resolution: {integrity: sha512-h/kL9jY0e0Dvad+/QU3efKZww0aTvZJslaHj3JTPmIPC9Oan9+kYqmh3M6L5JUQRuTJYFK2nzgL2iJtH2S+6dA==} 504 | engines: {node: '>=12.20.0'} 505 | 506 | '@tinyhttp/encode-url@2.1.1': 507 | resolution: {integrity: sha512-AhY+JqdZ56qV77tzrBm0qThXORbsVjs/IOPgGCS7x/wWnsa/Bx30zDUU/jPAUcSzNOzt860x9fhdGpzdqbUeUw==} 508 | engines: {node: '>=12.20.0'} 509 | 510 | '@tinyhttp/etag@2.1.2': 511 | resolution: {integrity: sha512-j80fPKimGqdmMh6962y+BtQsnYPVCzZfJw0HXjyH70VaJBHLKGF+iYhcKqzI3yef6QBNa8DKIPsbEYpuwApXTw==} 512 | engines: {node: '>=12.20.0'} 513 | 514 | '@tinyhttp/forwarded@2.1.1': 515 | resolution: {integrity: sha512-nO3kq0R1LRl2+CAMlnggm22zE6sT8gfvGbNvSitV6F9eaUSurHP0A8YZFMihSkugHxK+uIegh1TKrqgD8+lyGQ==} 516 | engines: {node: '>=12.20.0'} 517 | 518 | '@tinyhttp/proxy-addr@2.2.0': 519 | resolution: {integrity: sha512-WM/PPL9xNvrs7/8Om5nhKbke5FHrP3EfjOOR+wBnjgESfibqn0K7wdUTnzSLp1lBmemr88os1XvzwymSgaibyA==} 520 | engines: {node: '>=12.20.0'} 521 | 522 | '@tinyhttp/req@2.2.4': 523 | resolution: {integrity: sha512-lQAZIAo0NOeghxFOZS57tQzxpHSPPLs9T68Krq2BncEBImKwqaDKUt7M9Y5Kb+rvC/GwIL3LeErhkg7f5iG4IQ==} 524 | engines: {node: '>=12.20.0'} 525 | 526 | '@tinyhttp/res@2.2.4': 527 | resolution: {integrity: sha512-ETBRShnO19oJyIg2XQHQoofXPWeTXPAuwnIVYkU8WaftvXd/Vz4y5+WFQDHUzKlmdGOw5fAFnrEU7pIVMeFeVA==} 528 | engines: {node: '>=12.20.0'} 529 | 530 | '@tinyhttp/router@2.2.3': 531 | resolution: {integrity: sha512-O0MQqWV3Vpg/uXsMYg19XsIgOhwjyhTYWh51Qng7bxqXixxx2PEvZWnFjP7c84K7kU/nUX41KpkEBTLnznk9/Q==} 532 | engines: {node: '>=12.20.0'} 533 | 534 | '@tinyhttp/send@2.2.3': 535 | resolution: {integrity: sha512-o4cVHHGQ8WjVBS8UT0EE/2WnjoybrfXikHwsRoNlG1pfrC/Sd01u1N4Te8cOd/9aNGLr4mGxWb5qTm2RRtEi7g==} 536 | engines: {node: '>=12.20.0'} 537 | 538 | '@tinyhttp/type-is@2.2.4': 539 | resolution: {integrity: sha512-7F328NheridwjIfefBB2j1PEcKKABpADgv7aCJaE8x8EON77ZFrAkI3Rir7pGjopV7V9MBmW88xUQigBEX2rmQ==} 540 | engines: {node: '>=12.20.0'} 541 | 542 | '@tinyhttp/url@2.1.1': 543 | resolution: {integrity: sha512-POJeq2GQ5jI7Zrdmj22JqOijB5/GeX+LEX7DUdml1hUnGbJOTWDx7zf2b5cCERj7RoXL67zTgyzVblBJC+NJWg==} 544 | engines: {node: '>=12.20.0'} 545 | 546 | '@tinyhttp/vary@0.1.3': 547 | resolution: {integrity: sha512-SoL83sQXAGiHN1jm2VwLUWQSQeDAAl1ywOm6T0b0Cg1CZhVsjoiZadmjhxF6FHCCY7OHHVaLnTgSMxTPIDLxMg==} 548 | engines: {node: '>=12.20'} 549 | 550 | '@types/body-parser@1.19.2': 551 | resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} 552 | 553 | '@types/connect@3.4.35': 554 | resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} 555 | 556 | '@types/conventional-commits-parser@5.0.0': 557 | resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==} 558 | 559 | '@types/estree@1.0.5': 560 | resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} 561 | 562 | '@types/estree@1.0.6': 563 | resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} 564 | 565 | '@types/express-serve-static-core@4.17.33': 566 | resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==} 567 | 568 | '@types/express-session@1.18.0': 569 | resolution: {integrity: sha512-27JdDRgor6PoYlURY+Y5kCakqp5ulC0kmf7y+QwaY+hv9jEFuQOThgkjyA53RP3jmKuBsH5GR6qEfFmvb8mwOA==} 570 | 571 | '@types/express@4.17.17': 572 | resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==} 573 | 574 | '@types/mime@3.0.1': 575 | resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==} 576 | 577 | '@types/node@22.5.5': 578 | resolution: {integrity: sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==} 579 | 580 | '@types/qs@6.9.7': 581 | resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} 582 | 583 | '@types/range-parser@1.2.4': 584 | resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} 585 | 586 | '@types/serve-static@1.15.1': 587 | resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==} 588 | 589 | '@vitest/coverage-v8@2.1.1': 590 | resolution: {integrity: sha512-md/A7A3c42oTT8JUHSqjP5uKTWJejzUW4jalpvs+rZ27gsURsMU8DEb+8Jf8C6Kj2gwfSHJqobDNBuoqlm0cFw==} 591 | peerDependencies: 592 | '@vitest/browser': 2.1.1 593 | vitest: 2.1.1 594 | peerDependenciesMeta: 595 | '@vitest/browser': 596 | optional: true 597 | 598 | '@vitest/expect@2.1.1': 599 | resolution: {integrity: sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==} 600 | 601 | '@vitest/mocker@2.1.1': 602 | resolution: {integrity: sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA==} 603 | peerDependencies: 604 | '@vitest/spy': 2.1.1 605 | msw: ^2.3.5 606 | vite: ^5.0.0 607 | peerDependenciesMeta: 608 | msw: 609 | optional: true 610 | vite: 611 | optional: true 612 | 613 | '@vitest/pretty-format@2.1.1': 614 | resolution: {integrity: sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==} 615 | 616 | '@vitest/runner@2.1.1': 617 | resolution: {integrity: sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA==} 618 | 619 | '@vitest/snapshot@2.1.1': 620 | resolution: {integrity: sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw==} 621 | 622 | '@vitest/spy@2.1.1': 623 | resolution: {integrity: sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g==} 624 | 625 | '@vitest/utils@2.1.1': 626 | resolution: {integrity: sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==} 627 | 628 | JSONStream@1.3.5: 629 | resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} 630 | hasBin: true 631 | 632 | ajv@8.12.0: 633 | resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} 634 | 635 | ansi-regex@5.0.1: 636 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 637 | engines: {node: '>=8'} 638 | 639 | ansi-regex@6.1.0: 640 | resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} 641 | engines: {node: '>=12'} 642 | 643 | ansi-styles@3.2.1: 644 | resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} 645 | engines: {node: '>=4'} 646 | 647 | ansi-styles@4.3.0: 648 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 649 | engines: {node: '>=8'} 650 | 651 | ansi-styles@6.2.1: 652 | resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} 653 | engines: {node: '>=12'} 654 | 655 | argparse@2.0.1: 656 | resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 657 | 658 | array-ify@1.0.0: 659 | resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} 660 | 661 | assertion-error@2.0.1: 662 | resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} 663 | engines: {node: '>=12'} 664 | 665 | balanced-match@1.0.2: 666 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 667 | 668 | brace-expansion@2.0.1: 669 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 670 | 671 | cac@6.7.14: 672 | resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 673 | engines: {node: '>=8'} 674 | 675 | callsites@3.1.0: 676 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 677 | engines: {node: '>=6'} 678 | 679 | chai@5.1.1: 680 | resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} 681 | engines: {node: '>=12'} 682 | 683 | chalk@2.4.2: 684 | resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} 685 | engines: {node: '>=4'} 686 | 687 | chalk@5.3.0: 688 | resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} 689 | engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} 690 | 691 | check-error@2.1.1: 692 | resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} 693 | engines: {node: '>= 16'} 694 | 695 | cliui@8.0.1: 696 | resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} 697 | engines: {node: '>=12'} 698 | 699 | color-convert@1.9.3: 700 | resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} 701 | 702 | color-convert@2.0.1: 703 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 704 | engines: {node: '>=7.0.0'} 705 | 706 | color-name@1.1.3: 707 | resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} 708 | 709 | color-name@1.1.4: 710 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 711 | 712 | compare-func@2.0.0: 713 | resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} 714 | 715 | conventional-changelog-angular@7.0.0: 716 | resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==} 717 | engines: {node: '>=16'} 718 | 719 | conventional-changelog-conventionalcommits@7.0.2: 720 | resolution: {integrity: sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==} 721 | engines: {node: '>=16'} 722 | 723 | conventional-commits-parser@5.0.0: 724 | resolution: {integrity: sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==} 725 | engines: {node: '>=16'} 726 | hasBin: true 727 | 728 | cookie-signature@1.0.7: 729 | resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} 730 | 731 | cookie@0.6.0: 732 | resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} 733 | engines: {node: '>= 0.6'} 734 | 735 | cosmiconfig-typescript-loader@5.0.0: 736 | resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==} 737 | engines: {node: '>=v16'} 738 | peerDependencies: 739 | '@types/node': '*' 740 | cosmiconfig: '>=8.2' 741 | typescript: '>=4' 742 | 743 | cosmiconfig@9.0.0: 744 | resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} 745 | engines: {node: '>=14'} 746 | peerDependencies: 747 | typescript: '>=4.9.5' 748 | peerDependenciesMeta: 749 | typescript: 750 | optional: true 751 | 752 | cross-spawn@7.0.6: 753 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 754 | engines: {node: '>= 8'} 755 | 756 | dargs@8.1.0: 757 | resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==} 758 | engines: {node: '>=12'} 759 | 760 | debug@2.6.9: 761 | resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} 762 | peerDependencies: 763 | supports-color: '*' 764 | peerDependenciesMeta: 765 | supports-color: 766 | optional: true 767 | 768 | debug@4.3.7: 769 | resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} 770 | engines: {node: '>=6.0'} 771 | peerDependencies: 772 | supports-color: '*' 773 | peerDependenciesMeta: 774 | supports-color: 775 | optional: true 776 | 777 | deep-eql@5.0.2: 778 | resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} 779 | engines: {node: '>=6'} 780 | 781 | depd@2.0.0: 782 | resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} 783 | engines: {node: '>= 0.8'} 784 | 785 | dot-prop@5.3.0: 786 | resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} 787 | engines: {node: '>=8'} 788 | 789 | eastasianwidth@0.2.0: 790 | resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 791 | 792 | emoji-regex@8.0.0: 793 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 794 | 795 | emoji-regex@9.2.2: 796 | resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} 797 | 798 | env-paths@2.2.1: 799 | resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} 800 | engines: {node: '>=6'} 801 | 802 | error-ex@1.3.2: 803 | resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} 804 | 805 | es-escape-html@0.1.1: 806 | resolution: {integrity: sha512-yUx1o+8RsG7UlszmYPtks+dm6Lho2m8lgHMOsLJQsFI0R8XwUJwiMhM1M4E/S8QLeGyf6MkDV/pWgjQ0tdTSyQ==} 807 | engines: {node: '>=12.x'} 808 | 809 | esbuild@0.21.5: 810 | resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} 811 | engines: {node: '>=12'} 812 | hasBin: true 813 | 814 | escalade@3.1.1: 815 | resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} 816 | engines: {node: '>=6'} 817 | 818 | escape-string-regexp@1.0.5: 819 | resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} 820 | engines: {node: '>=0.8.0'} 821 | 822 | estree-walker@3.0.3: 823 | resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} 824 | 825 | express-session@1.18.0: 826 | resolution: {integrity: sha512-m93QLWr0ju+rOwApSsyso838LQwgfs44QtOP/WBiwtAgPIo/SAh1a5c6nn2BR6mFNZehTpqKDESzP+fRHVbxwQ==} 827 | engines: {node: '>= 0.8.0'} 828 | 829 | fast-deep-equal@3.1.3: 830 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 831 | 832 | find-up@7.0.0: 833 | resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} 834 | engines: {node: '>=18'} 835 | 836 | foreground-child@3.3.0: 837 | resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} 838 | engines: {node: '>=14'} 839 | 840 | fsevents@2.3.3: 841 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 842 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 843 | os: [darwin] 844 | 845 | get-caller-file@2.0.5: 846 | resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} 847 | engines: {node: 6.* || 8.* || >= 10.*} 848 | 849 | get-func-name@2.0.2: 850 | resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} 851 | 852 | git-raw-commits@4.0.0: 853 | resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==} 854 | engines: {node: '>=16'} 855 | hasBin: true 856 | 857 | glob@10.4.5: 858 | resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} 859 | hasBin: true 860 | 861 | global-directory@4.0.1: 862 | resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} 863 | engines: {node: '>=18'} 864 | 865 | has-flag@3.0.0: 866 | resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} 867 | engines: {node: '>=4'} 868 | 869 | has-flag@4.0.0: 870 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 871 | engines: {node: '>=8'} 872 | 873 | header-range-parser@1.1.3: 874 | resolution: {integrity: sha512-B9zCFt3jH8g09LR1vHL4pcAn8yMEtlSlOUdQemzHMRKMImNIhhszdeosYFfNW0WXKQtXIlWB+O4owHJKvEJYaA==} 875 | engines: {node: '>=12.22.0'} 876 | 877 | html-escaper@2.0.2: 878 | resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} 879 | 880 | husky@9.1.6: 881 | resolution: {integrity: sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==} 882 | engines: {node: '>=18'} 883 | hasBin: true 884 | 885 | import-fresh@3.3.0: 886 | resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} 887 | engines: {node: '>=6'} 888 | 889 | import-meta-resolve@4.1.0: 890 | resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} 891 | 892 | ini@4.1.1: 893 | resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} 894 | engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} 895 | 896 | ipaddr.js@2.2.0: 897 | resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} 898 | engines: {node: '>= 10'} 899 | 900 | is-arrayish@0.2.1: 901 | resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} 902 | 903 | is-fullwidth-code-point@3.0.0: 904 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 905 | engines: {node: '>=8'} 906 | 907 | is-obj@2.0.0: 908 | resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} 909 | engines: {node: '>=8'} 910 | 911 | is-text-path@2.0.0: 912 | resolution: {integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==} 913 | engines: {node: '>=8'} 914 | 915 | isexe@2.0.0: 916 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 917 | 918 | istanbul-lib-coverage@3.2.2: 919 | resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} 920 | engines: {node: '>=8'} 921 | 922 | istanbul-lib-report@3.0.1: 923 | resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} 924 | engines: {node: '>=10'} 925 | 926 | istanbul-lib-source-maps@5.0.6: 927 | resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} 928 | engines: {node: '>=10'} 929 | 930 | istanbul-reports@3.1.7: 931 | resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} 932 | engines: {node: '>=8'} 933 | 934 | jackspeak@3.4.3: 935 | resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} 936 | 937 | jiti@1.21.6: 938 | resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} 939 | hasBin: true 940 | 941 | js-tokens@4.0.0: 942 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 943 | 944 | js-yaml@4.1.0: 945 | resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 946 | hasBin: true 947 | 948 | json-parse-even-better-errors@2.3.1: 949 | resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} 950 | 951 | json-schema-traverse@1.0.0: 952 | resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} 953 | 954 | jsonparse@1.3.1: 955 | resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} 956 | engines: {'0': node >= 0.2.0} 957 | 958 | lines-and-columns@1.2.4: 959 | resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} 960 | 961 | locate-path@7.2.0: 962 | resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} 963 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 964 | 965 | lodash.camelcase@4.3.0: 966 | resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} 967 | 968 | lodash.isplainobject@4.0.6: 969 | resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} 970 | 971 | lodash.kebabcase@4.1.1: 972 | resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} 973 | 974 | lodash.merge@4.6.2: 975 | resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 976 | 977 | lodash.mergewith@4.6.2: 978 | resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} 979 | 980 | lodash.snakecase@4.1.1: 981 | resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} 982 | 983 | lodash.startcase@4.4.0: 984 | resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} 985 | 986 | lodash.uniq@4.5.0: 987 | resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} 988 | 989 | lodash.upperfirst@4.3.1: 990 | resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} 991 | 992 | loupe@3.1.1: 993 | resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} 994 | 995 | lru-cache@10.4.3: 996 | resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} 997 | 998 | lru-cache@6.0.0: 999 | resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 1000 | engines: {node: '>=10'} 1001 | 1002 | magic-string@0.30.11: 1003 | resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} 1004 | 1005 | magicast@0.3.5: 1006 | resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} 1007 | 1008 | make-dir@4.0.0: 1009 | resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} 1010 | engines: {node: '>=10'} 1011 | 1012 | meow@12.1.1: 1013 | resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} 1014 | engines: {node: '>=16.10'} 1015 | 1016 | milliparsec@4.0.0: 1017 | resolution: {integrity: sha512-/wk9d4Z6/9ZvoEH/6BI4TrTCgmkpZPuSRN/6fI9aUHOfXdNTuj/VhLS7d+NqG26bi6L9YmGXutVYvWC8zQ0qtA==} 1018 | engines: {node: '>=20'} 1019 | 1020 | mime@4.0.4: 1021 | resolution: {integrity: sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==} 1022 | engines: {node: '>=16'} 1023 | hasBin: true 1024 | 1025 | minimatch@9.0.5: 1026 | resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 1027 | engines: {node: '>=16 || 14 >=14.17'} 1028 | 1029 | minimist@1.2.8: 1030 | resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} 1031 | 1032 | minipass@7.1.2: 1033 | resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} 1034 | engines: {node: '>=16 || 14 >=14.17'} 1035 | 1036 | ms@2.0.0: 1037 | resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} 1038 | 1039 | ms@2.1.3: 1040 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 1041 | 1042 | nanoid@3.3.8: 1043 | resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} 1044 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 1045 | hasBin: true 1046 | 1047 | negotiator@0.6.3: 1048 | resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} 1049 | engines: {node: '>= 0.6'} 1050 | 1051 | on-headers@1.0.2: 1052 | resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} 1053 | engines: {node: '>= 0.8'} 1054 | 1055 | p-limit@4.0.0: 1056 | resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} 1057 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1058 | 1059 | p-locate@6.0.0: 1060 | resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} 1061 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1062 | 1063 | package-json-from-dist@1.0.0: 1064 | resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} 1065 | 1066 | parent-module@1.0.1: 1067 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1068 | engines: {node: '>=6'} 1069 | 1070 | parse-json@5.2.0: 1071 | resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} 1072 | engines: {node: '>=8'} 1073 | 1074 | parseurl@1.3.3: 1075 | resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} 1076 | engines: {node: '>= 0.8'} 1077 | 1078 | path-exists@5.0.0: 1079 | resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} 1080 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1081 | 1082 | path-key@3.1.1: 1083 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1084 | engines: {node: '>=8'} 1085 | 1086 | path-scurry@1.11.1: 1087 | resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} 1088 | engines: {node: '>=16 || 14 >=14.18'} 1089 | 1090 | pathe@1.1.2: 1091 | resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} 1092 | 1093 | pathval@2.0.0: 1094 | resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} 1095 | engines: {node: '>= 14.16'} 1096 | 1097 | picocolors@1.1.0: 1098 | resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} 1099 | 1100 | postcss@8.4.47: 1101 | resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} 1102 | engines: {node: ^10 || ^12 || >=14} 1103 | 1104 | punycode@2.3.0: 1105 | resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} 1106 | engines: {node: '>=6'} 1107 | 1108 | random-bytes@1.0.0: 1109 | resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==} 1110 | engines: {node: '>= 0.8'} 1111 | 1112 | regexparam@2.0.2: 1113 | resolution: {integrity: sha512-A1PeDEYMrkLrfyOwv2jwihXbo9qxdGD3atBYQA9JJgreAx8/7rC6IUkWOw2NQlOxLp2wL0ifQbh1HuidDfYA6w==} 1114 | engines: {node: '>=8'} 1115 | 1116 | require-directory@2.1.1: 1117 | resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} 1118 | engines: {node: '>=0.10.0'} 1119 | 1120 | require-from-string@2.0.2: 1121 | resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} 1122 | engines: {node: '>=0.10.0'} 1123 | 1124 | resolve-from@4.0.0: 1125 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 1126 | engines: {node: '>=4'} 1127 | 1128 | resolve-from@5.0.0: 1129 | resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} 1130 | engines: {node: '>=8'} 1131 | 1132 | rollup@4.24.3: 1133 | resolution: {integrity: sha512-HBW896xR5HGmoksbi3JBDtmVzWiPAYqp7wip50hjQ67JbDz61nyoMPdqu1DvVW9asYb2M65Z20ZHsyJCMqMyDg==} 1134 | engines: {node: '>=18.0.0', npm: '>=8.0.0'} 1135 | hasBin: true 1136 | 1137 | safe-buffer@5.2.1: 1138 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1139 | 1140 | semver@7.5.4: 1141 | resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} 1142 | engines: {node: '>=10'} 1143 | hasBin: true 1144 | 1145 | semver@7.6.3: 1146 | resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} 1147 | engines: {node: '>=10'} 1148 | hasBin: true 1149 | 1150 | shebang-command@2.0.0: 1151 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1152 | engines: {node: '>=8'} 1153 | 1154 | shebang-regex@3.0.0: 1155 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1156 | engines: {node: '>=8'} 1157 | 1158 | siginfo@2.0.0: 1159 | resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} 1160 | 1161 | signal-exit@4.1.0: 1162 | resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 1163 | engines: {node: '>=14'} 1164 | 1165 | source-map-js@1.2.1: 1166 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 1167 | engines: {node: '>=0.10.0'} 1168 | 1169 | split2@4.2.0: 1170 | resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} 1171 | engines: {node: '>= 10.x'} 1172 | 1173 | stackback@0.0.2: 1174 | resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} 1175 | 1176 | std-env@3.7.0: 1177 | resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} 1178 | 1179 | string-width@4.2.3: 1180 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 1181 | engines: {node: '>=8'} 1182 | 1183 | string-width@5.1.2: 1184 | resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} 1185 | engines: {node: '>=12'} 1186 | 1187 | strip-ansi@6.0.1: 1188 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1189 | engines: {node: '>=8'} 1190 | 1191 | strip-ansi@7.1.0: 1192 | resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 1193 | engines: {node: '>=12'} 1194 | 1195 | supertest-fetch@2.0.0: 1196 | resolution: {integrity: sha512-Mx2ZszLJkrBMFt7fmyML12y+u2yHAN5FF94yOzZXHzrTtsS59fjdbqh9G4OAPDM14jnkDkcLk2AgCVGJZcVoUg==} 1197 | engines: {node: '>=18.0.0'} 1198 | 1199 | supports-color@5.5.0: 1200 | resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} 1201 | engines: {node: '>=4'} 1202 | 1203 | supports-color@7.2.0: 1204 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1205 | engines: {node: '>=8'} 1206 | 1207 | test-exclude@7.0.1: 1208 | resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} 1209 | engines: {node: '>=18'} 1210 | 1211 | text-extensions@2.4.0: 1212 | resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} 1213 | engines: {node: '>=8'} 1214 | 1215 | through@2.3.8: 1216 | resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} 1217 | 1218 | tinybench@2.9.0: 1219 | resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} 1220 | 1221 | tinyexec@0.3.0: 1222 | resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==} 1223 | 1224 | tinypool@1.0.1: 1225 | resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==} 1226 | engines: {node: ^18.0.0 || >=20.0.0} 1227 | 1228 | tinyrainbow@1.2.0: 1229 | resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} 1230 | engines: {node: '>=14.0.0'} 1231 | 1232 | tinyspy@3.0.2: 1233 | resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} 1234 | engines: {node: '>=14.0.0'} 1235 | 1236 | to-fast-properties@2.0.0: 1237 | resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} 1238 | engines: {node: '>=4'} 1239 | 1240 | typescript@5.6.2: 1241 | resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} 1242 | engines: {node: '>=14.17'} 1243 | hasBin: true 1244 | 1245 | uid-safe@2.1.5: 1246 | resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==} 1247 | engines: {node: '>= 0.8'} 1248 | 1249 | undici-types@6.19.8: 1250 | resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} 1251 | 1252 | unicorn-magic@0.1.0: 1253 | resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} 1254 | engines: {node: '>=18'} 1255 | 1256 | uri-js@4.4.1: 1257 | resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1258 | 1259 | vite-node@2.1.1: 1260 | resolution: {integrity: sha512-N/mGckI1suG/5wQI35XeR9rsMsPqKXzq1CdUndzVstBj/HvyxxGctwnK6WX43NGt5L3Z5tcRf83g4TITKJhPrA==} 1261 | engines: {node: ^18.0.0 || >=20.0.0} 1262 | hasBin: true 1263 | 1264 | vite@5.4.10: 1265 | resolution: {integrity: sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==} 1266 | engines: {node: ^18.0.0 || >=20.0.0} 1267 | hasBin: true 1268 | peerDependencies: 1269 | '@types/node': ^18.0.0 || >=20.0.0 1270 | less: '*' 1271 | lightningcss: ^1.21.0 1272 | sass: '*' 1273 | sass-embedded: '*' 1274 | stylus: '*' 1275 | sugarss: '*' 1276 | terser: ^5.4.0 1277 | peerDependenciesMeta: 1278 | '@types/node': 1279 | optional: true 1280 | less: 1281 | optional: true 1282 | lightningcss: 1283 | optional: true 1284 | sass: 1285 | optional: true 1286 | sass-embedded: 1287 | optional: true 1288 | stylus: 1289 | optional: true 1290 | sugarss: 1291 | optional: true 1292 | terser: 1293 | optional: true 1294 | 1295 | vitest@2.1.1: 1296 | resolution: {integrity: sha512-97We7/VC0e9X5zBVkvt7SGQMGrRtn3KtySFQG5fpaMlS+l62eeXRQO633AYhSTC3z7IMebnPPNjGXVGNRFlxBA==} 1297 | engines: {node: ^18.0.0 || >=20.0.0} 1298 | hasBin: true 1299 | peerDependencies: 1300 | '@edge-runtime/vm': '*' 1301 | '@types/node': ^18.0.0 || >=20.0.0 1302 | '@vitest/browser': 2.1.1 1303 | '@vitest/ui': 2.1.1 1304 | happy-dom: '*' 1305 | jsdom: '*' 1306 | peerDependenciesMeta: 1307 | '@edge-runtime/vm': 1308 | optional: true 1309 | '@types/node': 1310 | optional: true 1311 | '@vitest/browser': 1312 | optional: true 1313 | '@vitest/ui': 1314 | optional: true 1315 | happy-dom: 1316 | optional: true 1317 | jsdom: 1318 | optional: true 1319 | 1320 | which@2.0.2: 1321 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1322 | engines: {node: '>= 8'} 1323 | hasBin: true 1324 | 1325 | why-is-node-running@2.3.0: 1326 | resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} 1327 | engines: {node: '>=8'} 1328 | hasBin: true 1329 | 1330 | wrap-ansi@7.0.0: 1331 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 1332 | engines: {node: '>=10'} 1333 | 1334 | wrap-ansi@8.1.0: 1335 | resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} 1336 | engines: {node: '>=12'} 1337 | 1338 | y18n@5.0.8: 1339 | resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} 1340 | engines: {node: '>=10'} 1341 | 1342 | yallist@4.0.0: 1343 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 1344 | 1345 | yargs-parser@21.1.1: 1346 | resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} 1347 | engines: {node: '>=12'} 1348 | 1349 | yargs@17.7.2: 1350 | resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} 1351 | engines: {node: '>=12'} 1352 | 1353 | yocto-queue@1.1.1: 1354 | resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} 1355 | engines: {node: '>=12.20'} 1356 | 1357 | snapshots: 1358 | 1359 | '@ampproject/remapping@2.3.0': 1360 | dependencies: 1361 | '@jridgewell/gen-mapping': 0.3.5 1362 | '@jridgewell/trace-mapping': 0.3.25 1363 | 1364 | '@babel/code-frame@7.18.6': 1365 | dependencies: 1366 | '@babel/highlight': 7.18.6 1367 | 1368 | '@babel/helper-string-parser@7.24.8': {} 1369 | 1370 | '@babel/helper-validator-identifier@7.24.7': {} 1371 | 1372 | '@babel/highlight@7.18.6': 1373 | dependencies: 1374 | '@babel/helper-validator-identifier': 7.24.7 1375 | chalk: 2.4.2 1376 | js-tokens: 4.0.0 1377 | 1378 | '@babel/parser@7.25.6': 1379 | dependencies: 1380 | '@babel/types': 7.25.6 1381 | 1382 | '@babel/types@7.25.6': 1383 | dependencies: 1384 | '@babel/helper-string-parser': 7.24.8 1385 | '@babel/helper-validator-identifier': 7.24.7 1386 | to-fast-properties: 2.0.0 1387 | 1388 | '@bcoe/v8-coverage@0.2.3': {} 1389 | 1390 | '@biomejs/biome@1.9.1': 1391 | optionalDependencies: 1392 | '@biomejs/cli-darwin-arm64': 1.9.1 1393 | '@biomejs/cli-darwin-x64': 1.9.1 1394 | '@biomejs/cli-linux-arm64': 1.9.1 1395 | '@biomejs/cli-linux-arm64-musl': 1.9.1 1396 | '@biomejs/cli-linux-x64': 1.9.1 1397 | '@biomejs/cli-linux-x64-musl': 1.9.1 1398 | '@biomejs/cli-win32-arm64': 1.9.1 1399 | '@biomejs/cli-win32-x64': 1.9.1 1400 | 1401 | '@biomejs/cli-darwin-arm64@1.9.1': 1402 | optional: true 1403 | 1404 | '@biomejs/cli-darwin-x64@1.9.1': 1405 | optional: true 1406 | 1407 | '@biomejs/cli-linux-arm64-musl@1.9.1': 1408 | optional: true 1409 | 1410 | '@biomejs/cli-linux-arm64@1.9.1': 1411 | optional: true 1412 | 1413 | '@biomejs/cli-linux-x64-musl@1.9.1': 1414 | optional: true 1415 | 1416 | '@biomejs/cli-linux-x64@1.9.1': 1417 | optional: true 1418 | 1419 | '@biomejs/cli-win32-arm64@1.9.1': 1420 | optional: true 1421 | 1422 | '@biomejs/cli-win32-x64@1.9.1': 1423 | optional: true 1424 | 1425 | '@commitlint/cli@19.5.0(@types/node@22.5.5)(typescript@5.6.2)': 1426 | dependencies: 1427 | '@commitlint/format': 19.5.0 1428 | '@commitlint/lint': 19.5.0 1429 | '@commitlint/load': 19.5.0(@types/node@22.5.5)(typescript@5.6.2) 1430 | '@commitlint/read': 19.5.0 1431 | '@commitlint/types': 19.5.0 1432 | tinyexec: 0.3.0 1433 | yargs: 17.7.2 1434 | transitivePeerDependencies: 1435 | - '@types/node' 1436 | - typescript 1437 | 1438 | '@commitlint/config-conventional@19.5.0': 1439 | dependencies: 1440 | '@commitlint/types': 19.5.0 1441 | conventional-changelog-conventionalcommits: 7.0.2 1442 | 1443 | '@commitlint/config-validator@19.5.0': 1444 | dependencies: 1445 | '@commitlint/types': 19.5.0 1446 | ajv: 8.12.0 1447 | 1448 | '@commitlint/ensure@19.5.0': 1449 | dependencies: 1450 | '@commitlint/types': 19.5.0 1451 | lodash.camelcase: 4.3.0 1452 | lodash.kebabcase: 4.1.1 1453 | lodash.snakecase: 4.1.1 1454 | lodash.startcase: 4.4.0 1455 | lodash.upperfirst: 4.3.1 1456 | 1457 | '@commitlint/execute-rule@19.5.0': {} 1458 | 1459 | '@commitlint/format@19.5.0': 1460 | dependencies: 1461 | '@commitlint/types': 19.5.0 1462 | chalk: 5.3.0 1463 | 1464 | '@commitlint/is-ignored@19.5.0': 1465 | dependencies: 1466 | '@commitlint/types': 19.5.0 1467 | semver: 7.6.3 1468 | 1469 | '@commitlint/lint@19.5.0': 1470 | dependencies: 1471 | '@commitlint/is-ignored': 19.5.0 1472 | '@commitlint/parse': 19.5.0 1473 | '@commitlint/rules': 19.5.0 1474 | '@commitlint/types': 19.5.0 1475 | 1476 | '@commitlint/load@19.5.0(@types/node@22.5.5)(typescript@5.6.2)': 1477 | dependencies: 1478 | '@commitlint/config-validator': 19.5.0 1479 | '@commitlint/execute-rule': 19.5.0 1480 | '@commitlint/resolve-extends': 19.5.0 1481 | '@commitlint/types': 19.5.0 1482 | chalk: 5.3.0 1483 | cosmiconfig: 9.0.0(typescript@5.6.2) 1484 | cosmiconfig-typescript-loader: 5.0.0(@types/node@22.5.5)(cosmiconfig@9.0.0(typescript@5.6.2))(typescript@5.6.2) 1485 | lodash.isplainobject: 4.0.6 1486 | lodash.merge: 4.6.2 1487 | lodash.uniq: 4.5.0 1488 | transitivePeerDependencies: 1489 | - '@types/node' 1490 | - typescript 1491 | 1492 | '@commitlint/message@19.5.0': {} 1493 | 1494 | '@commitlint/parse@19.5.0': 1495 | dependencies: 1496 | '@commitlint/types': 19.5.0 1497 | conventional-changelog-angular: 7.0.0 1498 | conventional-commits-parser: 5.0.0 1499 | 1500 | '@commitlint/read@19.5.0': 1501 | dependencies: 1502 | '@commitlint/top-level': 19.5.0 1503 | '@commitlint/types': 19.5.0 1504 | git-raw-commits: 4.0.0 1505 | minimist: 1.2.8 1506 | tinyexec: 0.3.0 1507 | 1508 | '@commitlint/resolve-extends@19.5.0': 1509 | dependencies: 1510 | '@commitlint/config-validator': 19.5.0 1511 | '@commitlint/types': 19.5.0 1512 | global-directory: 4.0.1 1513 | import-meta-resolve: 4.1.0 1514 | lodash.mergewith: 4.6.2 1515 | resolve-from: 5.0.0 1516 | 1517 | '@commitlint/rules@19.5.0': 1518 | dependencies: 1519 | '@commitlint/ensure': 19.5.0 1520 | '@commitlint/message': 19.5.0 1521 | '@commitlint/to-lines': 19.5.0 1522 | '@commitlint/types': 19.5.0 1523 | 1524 | '@commitlint/to-lines@19.5.0': {} 1525 | 1526 | '@commitlint/top-level@19.5.0': 1527 | dependencies: 1528 | find-up: 7.0.0 1529 | 1530 | '@commitlint/types@19.5.0': 1531 | dependencies: 1532 | '@types/conventional-commits-parser': 5.0.0 1533 | chalk: 5.3.0 1534 | 1535 | '@esbuild/aix-ppc64@0.21.5': 1536 | optional: true 1537 | 1538 | '@esbuild/android-arm64@0.21.5': 1539 | optional: true 1540 | 1541 | '@esbuild/android-arm@0.21.5': 1542 | optional: true 1543 | 1544 | '@esbuild/android-x64@0.21.5': 1545 | optional: true 1546 | 1547 | '@esbuild/darwin-arm64@0.21.5': 1548 | optional: true 1549 | 1550 | '@esbuild/darwin-x64@0.21.5': 1551 | optional: true 1552 | 1553 | '@esbuild/freebsd-arm64@0.21.5': 1554 | optional: true 1555 | 1556 | '@esbuild/freebsd-x64@0.21.5': 1557 | optional: true 1558 | 1559 | '@esbuild/linux-arm64@0.21.5': 1560 | optional: true 1561 | 1562 | '@esbuild/linux-arm@0.21.5': 1563 | optional: true 1564 | 1565 | '@esbuild/linux-ia32@0.21.5': 1566 | optional: true 1567 | 1568 | '@esbuild/linux-loong64@0.21.5': 1569 | optional: true 1570 | 1571 | '@esbuild/linux-mips64el@0.21.5': 1572 | optional: true 1573 | 1574 | '@esbuild/linux-ppc64@0.21.5': 1575 | optional: true 1576 | 1577 | '@esbuild/linux-riscv64@0.21.5': 1578 | optional: true 1579 | 1580 | '@esbuild/linux-s390x@0.21.5': 1581 | optional: true 1582 | 1583 | '@esbuild/linux-x64@0.21.5': 1584 | optional: true 1585 | 1586 | '@esbuild/netbsd-x64@0.21.5': 1587 | optional: true 1588 | 1589 | '@esbuild/openbsd-x64@0.21.5': 1590 | optional: true 1591 | 1592 | '@esbuild/sunos-x64@0.21.5': 1593 | optional: true 1594 | 1595 | '@esbuild/win32-arm64@0.21.5': 1596 | optional: true 1597 | 1598 | '@esbuild/win32-ia32@0.21.5': 1599 | optional: true 1600 | 1601 | '@esbuild/win32-x64@0.21.5': 1602 | optional: true 1603 | 1604 | '@isaacs/cliui@8.0.2': 1605 | dependencies: 1606 | string-width: 5.1.2 1607 | string-width-cjs: string-width@4.2.3 1608 | strip-ansi: 7.1.0 1609 | strip-ansi-cjs: strip-ansi@6.0.1 1610 | wrap-ansi: 8.1.0 1611 | wrap-ansi-cjs: wrap-ansi@7.0.0 1612 | 1613 | '@istanbuljs/schema@0.1.3': {} 1614 | 1615 | '@jridgewell/gen-mapping@0.3.5': 1616 | dependencies: 1617 | '@jridgewell/set-array': 1.2.1 1618 | '@jridgewell/sourcemap-codec': 1.4.14 1619 | '@jridgewell/trace-mapping': 0.3.25 1620 | 1621 | '@jridgewell/resolve-uri@3.1.0': {} 1622 | 1623 | '@jridgewell/set-array@1.2.1': {} 1624 | 1625 | '@jridgewell/sourcemap-codec@1.4.14': {} 1626 | 1627 | '@jridgewell/sourcemap-codec@1.5.0': {} 1628 | 1629 | '@jridgewell/trace-mapping@0.3.25': 1630 | dependencies: 1631 | '@jridgewell/resolve-uri': 3.1.0 1632 | '@jridgewell/sourcemap-codec': 1.4.14 1633 | 1634 | '@pkgjs/parseargs@0.11.0': 1635 | optional: true 1636 | 1637 | '@rollup/rollup-android-arm-eabi@4.24.3': 1638 | optional: true 1639 | 1640 | '@rollup/rollup-android-arm64@4.24.3': 1641 | optional: true 1642 | 1643 | '@rollup/rollup-darwin-arm64@4.24.3': 1644 | optional: true 1645 | 1646 | '@rollup/rollup-darwin-x64@4.24.3': 1647 | optional: true 1648 | 1649 | '@rollup/rollup-freebsd-arm64@4.24.3': 1650 | optional: true 1651 | 1652 | '@rollup/rollup-freebsd-x64@4.24.3': 1653 | optional: true 1654 | 1655 | '@rollup/rollup-linux-arm-gnueabihf@4.24.3': 1656 | optional: true 1657 | 1658 | '@rollup/rollup-linux-arm-musleabihf@4.24.3': 1659 | optional: true 1660 | 1661 | '@rollup/rollup-linux-arm64-gnu@4.24.3': 1662 | optional: true 1663 | 1664 | '@rollup/rollup-linux-arm64-musl@4.24.3': 1665 | optional: true 1666 | 1667 | '@rollup/rollup-linux-powerpc64le-gnu@4.24.3': 1668 | optional: true 1669 | 1670 | '@rollup/rollup-linux-riscv64-gnu@4.24.3': 1671 | optional: true 1672 | 1673 | '@rollup/rollup-linux-s390x-gnu@4.24.3': 1674 | optional: true 1675 | 1676 | '@rollup/rollup-linux-x64-gnu@4.24.3': 1677 | optional: true 1678 | 1679 | '@rollup/rollup-linux-x64-musl@4.24.3': 1680 | optional: true 1681 | 1682 | '@rollup/rollup-win32-arm64-msvc@4.24.3': 1683 | optional: true 1684 | 1685 | '@rollup/rollup-win32-ia32-msvc@4.24.3': 1686 | optional: true 1687 | 1688 | '@rollup/rollup-win32-x64-msvc@4.24.3': 1689 | optional: true 1690 | 1691 | '@tinyhttp/accepts@2.2.3': 1692 | dependencies: 1693 | mime: 4.0.4 1694 | negotiator: 0.6.3 1695 | 1696 | '@tinyhttp/app@2.4.0': 1697 | dependencies: 1698 | '@tinyhttp/cookie': 2.1.1 1699 | '@tinyhttp/proxy-addr': 2.2.0 1700 | '@tinyhttp/req': 2.2.4 1701 | '@tinyhttp/res': 2.2.4 1702 | '@tinyhttp/router': 2.2.3 1703 | header-range-parser: 1.1.3 1704 | regexparam: 2.0.2 1705 | 1706 | '@tinyhttp/content-disposition@2.2.2': {} 1707 | 1708 | '@tinyhttp/content-type@0.1.4': {} 1709 | 1710 | '@tinyhttp/cookie-parser@2.0.6': 1711 | dependencies: 1712 | '@tinyhttp/cookie': 2.1.1 1713 | '@tinyhttp/cookie-signature': 2.1.1 1714 | 1715 | '@tinyhttp/cookie-signature@2.1.1': {} 1716 | 1717 | '@tinyhttp/cookie@2.1.1': {} 1718 | 1719 | '@tinyhttp/encode-url@2.1.1': {} 1720 | 1721 | '@tinyhttp/etag@2.1.2': {} 1722 | 1723 | '@tinyhttp/forwarded@2.1.1': {} 1724 | 1725 | '@tinyhttp/proxy-addr@2.2.0': 1726 | dependencies: 1727 | '@tinyhttp/forwarded': 2.1.1 1728 | ipaddr.js: 2.2.0 1729 | 1730 | '@tinyhttp/req@2.2.4': 1731 | dependencies: 1732 | '@tinyhttp/accepts': 2.2.3 1733 | '@tinyhttp/type-is': 2.2.4 1734 | '@tinyhttp/url': 2.1.1 1735 | header-range-parser: 1.1.3 1736 | 1737 | '@tinyhttp/res@2.2.4': 1738 | dependencies: 1739 | '@tinyhttp/content-disposition': 2.2.2 1740 | '@tinyhttp/cookie': 2.1.1 1741 | '@tinyhttp/cookie-signature': 2.1.1 1742 | '@tinyhttp/encode-url': 2.1.1 1743 | '@tinyhttp/req': 2.2.4 1744 | '@tinyhttp/send': 2.2.3 1745 | '@tinyhttp/vary': 0.1.3 1746 | es-escape-html: 0.1.1 1747 | mime: 4.0.4 1748 | 1749 | '@tinyhttp/router@2.2.3': {} 1750 | 1751 | '@tinyhttp/send@2.2.3': 1752 | dependencies: 1753 | '@tinyhttp/content-type': 0.1.4 1754 | '@tinyhttp/etag': 2.1.2 1755 | mime: 4.0.4 1756 | 1757 | '@tinyhttp/type-is@2.2.4': 1758 | dependencies: 1759 | '@tinyhttp/content-type': 0.1.4 1760 | mime: 4.0.4 1761 | 1762 | '@tinyhttp/url@2.1.1': {} 1763 | 1764 | '@tinyhttp/vary@0.1.3': {} 1765 | 1766 | '@types/body-parser@1.19.2': 1767 | dependencies: 1768 | '@types/connect': 3.4.35 1769 | '@types/node': 22.5.5 1770 | 1771 | '@types/connect@3.4.35': 1772 | dependencies: 1773 | '@types/node': 22.5.5 1774 | 1775 | '@types/conventional-commits-parser@5.0.0': 1776 | dependencies: 1777 | '@types/node': 22.5.5 1778 | 1779 | '@types/estree@1.0.5': {} 1780 | 1781 | '@types/estree@1.0.6': {} 1782 | 1783 | '@types/express-serve-static-core@4.17.33': 1784 | dependencies: 1785 | '@types/node': 22.5.5 1786 | '@types/qs': 6.9.7 1787 | '@types/range-parser': 1.2.4 1788 | 1789 | '@types/express-session@1.18.0': 1790 | dependencies: 1791 | '@types/express': 4.17.17 1792 | 1793 | '@types/express@4.17.17': 1794 | dependencies: 1795 | '@types/body-parser': 1.19.2 1796 | '@types/express-serve-static-core': 4.17.33 1797 | '@types/qs': 6.9.7 1798 | '@types/serve-static': 1.15.1 1799 | 1800 | '@types/mime@3.0.1': {} 1801 | 1802 | '@types/node@22.5.5': 1803 | dependencies: 1804 | undici-types: 6.19.8 1805 | 1806 | '@types/qs@6.9.7': {} 1807 | 1808 | '@types/range-parser@1.2.4': {} 1809 | 1810 | '@types/serve-static@1.15.1': 1811 | dependencies: 1812 | '@types/mime': 3.0.1 1813 | '@types/node': 22.5.5 1814 | 1815 | '@vitest/coverage-v8@2.1.1(vitest@2.1.1(@types/node@22.5.5))': 1816 | dependencies: 1817 | '@ampproject/remapping': 2.3.0 1818 | '@bcoe/v8-coverage': 0.2.3 1819 | debug: 4.3.7 1820 | istanbul-lib-coverage: 3.2.2 1821 | istanbul-lib-report: 3.0.1 1822 | istanbul-lib-source-maps: 5.0.6 1823 | istanbul-reports: 3.1.7 1824 | magic-string: 0.30.11 1825 | magicast: 0.3.5 1826 | std-env: 3.7.0 1827 | test-exclude: 7.0.1 1828 | tinyrainbow: 1.2.0 1829 | vitest: 2.1.1(@types/node@22.5.5) 1830 | transitivePeerDependencies: 1831 | - supports-color 1832 | 1833 | '@vitest/expect@2.1.1': 1834 | dependencies: 1835 | '@vitest/spy': 2.1.1 1836 | '@vitest/utils': 2.1.1 1837 | chai: 5.1.1 1838 | tinyrainbow: 1.2.0 1839 | 1840 | '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.4.10(@types/node@22.5.5))': 1841 | dependencies: 1842 | '@vitest/spy': 2.1.1 1843 | estree-walker: 3.0.3 1844 | magic-string: 0.30.11 1845 | optionalDependencies: 1846 | vite: 5.4.10(@types/node@22.5.5) 1847 | 1848 | '@vitest/pretty-format@2.1.1': 1849 | dependencies: 1850 | tinyrainbow: 1.2.0 1851 | 1852 | '@vitest/runner@2.1.1': 1853 | dependencies: 1854 | '@vitest/utils': 2.1.1 1855 | pathe: 1.1.2 1856 | 1857 | '@vitest/snapshot@2.1.1': 1858 | dependencies: 1859 | '@vitest/pretty-format': 2.1.1 1860 | magic-string: 0.30.11 1861 | pathe: 1.1.2 1862 | 1863 | '@vitest/spy@2.1.1': 1864 | dependencies: 1865 | tinyspy: 3.0.2 1866 | 1867 | '@vitest/utils@2.1.1': 1868 | dependencies: 1869 | '@vitest/pretty-format': 2.1.1 1870 | loupe: 3.1.1 1871 | tinyrainbow: 1.2.0 1872 | 1873 | JSONStream@1.3.5: 1874 | dependencies: 1875 | jsonparse: 1.3.1 1876 | through: 2.3.8 1877 | 1878 | ajv@8.12.0: 1879 | dependencies: 1880 | fast-deep-equal: 3.1.3 1881 | json-schema-traverse: 1.0.0 1882 | require-from-string: 2.0.2 1883 | uri-js: 4.4.1 1884 | 1885 | ansi-regex@5.0.1: {} 1886 | 1887 | ansi-regex@6.1.0: {} 1888 | 1889 | ansi-styles@3.2.1: 1890 | dependencies: 1891 | color-convert: 1.9.3 1892 | 1893 | ansi-styles@4.3.0: 1894 | dependencies: 1895 | color-convert: 2.0.1 1896 | 1897 | ansi-styles@6.2.1: {} 1898 | 1899 | argparse@2.0.1: {} 1900 | 1901 | array-ify@1.0.0: {} 1902 | 1903 | assertion-error@2.0.1: {} 1904 | 1905 | balanced-match@1.0.2: {} 1906 | 1907 | brace-expansion@2.0.1: 1908 | dependencies: 1909 | balanced-match: 1.0.2 1910 | 1911 | cac@6.7.14: {} 1912 | 1913 | callsites@3.1.0: {} 1914 | 1915 | chai@5.1.1: 1916 | dependencies: 1917 | assertion-error: 2.0.1 1918 | check-error: 2.1.1 1919 | deep-eql: 5.0.2 1920 | loupe: 3.1.1 1921 | pathval: 2.0.0 1922 | 1923 | chalk@2.4.2: 1924 | dependencies: 1925 | ansi-styles: 3.2.1 1926 | escape-string-regexp: 1.0.5 1927 | supports-color: 5.5.0 1928 | 1929 | chalk@5.3.0: {} 1930 | 1931 | check-error@2.1.1: {} 1932 | 1933 | cliui@8.0.1: 1934 | dependencies: 1935 | string-width: 4.2.3 1936 | strip-ansi: 6.0.1 1937 | wrap-ansi: 7.0.0 1938 | 1939 | color-convert@1.9.3: 1940 | dependencies: 1941 | color-name: 1.1.3 1942 | 1943 | color-convert@2.0.1: 1944 | dependencies: 1945 | color-name: 1.1.4 1946 | 1947 | color-name@1.1.3: {} 1948 | 1949 | color-name@1.1.4: {} 1950 | 1951 | compare-func@2.0.0: 1952 | dependencies: 1953 | array-ify: 1.0.0 1954 | dot-prop: 5.3.0 1955 | 1956 | conventional-changelog-angular@7.0.0: 1957 | dependencies: 1958 | compare-func: 2.0.0 1959 | 1960 | conventional-changelog-conventionalcommits@7.0.2: 1961 | dependencies: 1962 | compare-func: 2.0.0 1963 | 1964 | conventional-commits-parser@5.0.0: 1965 | dependencies: 1966 | JSONStream: 1.3.5 1967 | is-text-path: 2.0.0 1968 | meow: 12.1.1 1969 | split2: 4.2.0 1970 | 1971 | cookie-signature@1.0.7: {} 1972 | 1973 | cookie@0.6.0: {} 1974 | 1975 | cosmiconfig-typescript-loader@5.0.0(@types/node@22.5.5)(cosmiconfig@9.0.0(typescript@5.6.2))(typescript@5.6.2): 1976 | dependencies: 1977 | '@types/node': 22.5.5 1978 | cosmiconfig: 9.0.0(typescript@5.6.2) 1979 | jiti: 1.21.6 1980 | typescript: 5.6.2 1981 | 1982 | cosmiconfig@9.0.0(typescript@5.6.2): 1983 | dependencies: 1984 | env-paths: 2.2.1 1985 | import-fresh: 3.3.0 1986 | js-yaml: 4.1.0 1987 | parse-json: 5.2.0 1988 | optionalDependencies: 1989 | typescript: 5.6.2 1990 | 1991 | cross-spawn@7.0.6: 1992 | dependencies: 1993 | path-key: 3.1.1 1994 | shebang-command: 2.0.0 1995 | which: 2.0.2 1996 | 1997 | dargs@8.1.0: {} 1998 | 1999 | debug@2.6.9: 2000 | dependencies: 2001 | ms: 2.0.0 2002 | 2003 | debug@4.3.7: 2004 | dependencies: 2005 | ms: 2.1.3 2006 | 2007 | deep-eql@5.0.2: {} 2008 | 2009 | depd@2.0.0: {} 2010 | 2011 | dot-prop@5.3.0: 2012 | dependencies: 2013 | is-obj: 2.0.0 2014 | 2015 | eastasianwidth@0.2.0: {} 2016 | 2017 | emoji-regex@8.0.0: {} 2018 | 2019 | emoji-regex@9.2.2: {} 2020 | 2021 | env-paths@2.2.1: {} 2022 | 2023 | error-ex@1.3.2: 2024 | dependencies: 2025 | is-arrayish: 0.2.1 2026 | 2027 | es-escape-html@0.1.1: {} 2028 | 2029 | esbuild@0.21.5: 2030 | optionalDependencies: 2031 | '@esbuild/aix-ppc64': 0.21.5 2032 | '@esbuild/android-arm': 0.21.5 2033 | '@esbuild/android-arm64': 0.21.5 2034 | '@esbuild/android-x64': 0.21.5 2035 | '@esbuild/darwin-arm64': 0.21.5 2036 | '@esbuild/darwin-x64': 0.21.5 2037 | '@esbuild/freebsd-arm64': 0.21.5 2038 | '@esbuild/freebsd-x64': 0.21.5 2039 | '@esbuild/linux-arm': 0.21.5 2040 | '@esbuild/linux-arm64': 0.21.5 2041 | '@esbuild/linux-ia32': 0.21.5 2042 | '@esbuild/linux-loong64': 0.21.5 2043 | '@esbuild/linux-mips64el': 0.21.5 2044 | '@esbuild/linux-ppc64': 0.21.5 2045 | '@esbuild/linux-riscv64': 0.21.5 2046 | '@esbuild/linux-s390x': 0.21.5 2047 | '@esbuild/linux-x64': 0.21.5 2048 | '@esbuild/netbsd-x64': 0.21.5 2049 | '@esbuild/openbsd-x64': 0.21.5 2050 | '@esbuild/sunos-x64': 0.21.5 2051 | '@esbuild/win32-arm64': 0.21.5 2052 | '@esbuild/win32-ia32': 0.21.5 2053 | '@esbuild/win32-x64': 0.21.5 2054 | 2055 | escalade@3.1.1: {} 2056 | 2057 | escape-string-regexp@1.0.5: {} 2058 | 2059 | estree-walker@3.0.3: 2060 | dependencies: 2061 | '@types/estree': 1.0.5 2062 | 2063 | express-session@1.18.0: 2064 | dependencies: 2065 | cookie: 0.6.0 2066 | cookie-signature: 1.0.7 2067 | debug: 2.6.9 2068 | depd: 2.0.0 2069 | on-headers: 1.0.2 2070 | parseurl: 1.3.3 2071 | safe-buffer: 5.2.1 2072 | uid-safe: 2.1.5 2073 | transitivePeerDependencies: 2074 | - supports-color 2075 | 2076 | fast-deep-equal@3.1.3: {} 2077 | 2078 | find-up@7.0.0: 2079 | dependencies: 2080 | locate-path: 7.2.0 2081 | path-exists: 5.0.0 2082 | unicorn-magic: 0.1.0 2083 | 2084 | foreground-child@3.3.0: 2085 | dependencies: 2086 | cross-spawn: 7.0.6 2087 | signal-exit: 4.1.0 2088 | 2089 | fsevents@2.3.3: 2090 | optional: true 2091 | 2092 | get-caller-file@2.0.5: {} 2093 | 2094 | get-func-name@2.0.2: {} 2095 | 2096 | git-raw-commits@4.0.0: 2097 | dependencies: 2098 | dargs: 8.1.0 2099 | meow: 12.1.1 2100 | split2: 4.2.0 2101 | 2102 | glob@10.4.5: 2103 | dependencies: 2104 | foreground-child: 3.3.0 2105 | jackspeak: 3.4.3 2106 | minimatch: 9.0.5 2107 | minipass: 7.1.2 2108 | package-json-from-dist: 1.0.0 2109 | path-scurry: 1.11.1 2110 | 2111 | global-directory@4.0.1: 2112 | dependencies: 2113 | ini: 4.1.1 2114 | 2115 | has-flag@3.0.0: {} 2116 | 2117 | has-flag@4.0.0: {} 2118 | 2119 | header-range-parser@1.1.3: {} 2120 | 2121 | html-escaper@2.0.2: {} 2122 | 2123 | husky@9.1.6: {} 2124 | 2125 | import-fresh@3.3.0: 2126 | dependencies: 2127 | parent-module: 1.0.1 2128 | resolve-from: 4.0.0 2129 | 2130 | import-meta-resolve@4.1.0: {} 2131 | 2132 | ini@4.1.1: {} 2133 | 2134 | ipaddr.js@2.2.0: {} 2135 | 2136 | is-arrayish@0.2.1: {} 2137 | 2138 | is-fullwidth-code-point@3.0.0: {} 2139 | 2140 | is-obj@2.0.0: {} 2141 | 2142 | is-text-path@2.0.0: 2143 | dependencies: 2144 | text-extensions: 2.4.0 2145 | 2146 | isexe@2.0.0: {} 2147 | 2148 | istanbul-lib-coverage@3.2.2: {} 2149 | 2150 | istanbul-lib-report@3.0.1: 2151 | dependencies: 2152 | istanbul-lib-coverage: 3.2.2 2153 | make-dir: 4.0.0 2154 | supports-color: 7.2.0 2155 | 2156 | istanbul-lib-source-maps@5.0.6: 2157 | dependencies: 2158 | '@jridgewell/trace-mapping': 0.3.25 2159 | debug: 4.3.7 2160 | istanbul-lib-coverage: 3.2.2 2161 | transitivePeerDependencies: 2162 | - supports-color 2163 | 2164 | istanbul-reports@3.1.7: 2165 | dependencies: 2166 | html-escaper: 2.0.2 2167 | istanbul-lib-report: 3.0.1 2168 | 2169 | jackspeak@3.4.3: 2170 | dependencies: 2171 | '@isaacs/cliui': 8.0.2 2172 | optionalDependencies: 2173 | '@pkgjs/parseargs': 0.11.0 2174 | 2175 | jiti@1.21.6: {} 2176 | 2177 | js-tokens@4.0.0: {} 2178 | 2179 | js-yaml@4.1.0: 2180 | dependencies: 2181 | argparse: 2.0.1 2182 | 2183 | json-parse-even-better-errors@2.3.1: {} 2184 | 2185 | json-schema-traverse@1.0.0: {} 2186 | 2187 | jsonparse@1.3.1: {} 2188 | 2189 | lines-and-columns@1.2.4: {} 2190 | 2191 | locate-path@7.2.0: 2192 | dependencies: 2193 | p-locate: 6.0.0 2194 | 2195 | lodash.camelcase@4.3.0: {} 2196 | 2197 | lodash.isplainobject@4.0.6: {} 2198 | 2199 | lodash.kebabcase@4.1.1: {} 2200 | 2201 | lodash.merge@4.6.2: {} 2202 | 2203 | lodash.mergewith@4.6.2: {} 2204 | 2205 | lodash.snakecase@4.1.1: {} 2206 | 2207 | lodash.startcase@4.4.0: {} 2208 | 2209 | lodash.uniq@4.5.0: {} 2210 | 2211 | lodash.upperfirst@4.3.1: {} 2212 | 2213 | loupe@3.1.1: 2214 | dependencies: 2215 | get-func-name: 2.0.2 2216 | 2217 | lru-cache@10.4.3: {} 2218 | 2219 | lru-cache@6.0.0: 2220 | dependencies: 2221 | yallist: 4.0.0 2222 | 2223 | magic-string@0.30.11: 2224 | dependencies: 2225 | '@jridgewell/sourcemap-codec': 1.5.0 2226 | 2227 | magicast@0.3.5: 2228 | dependencies: 2229 | '@babel/parser': 7.25.6 2230 | '@babel/types': 7.25.6 2231 | source-map-js: 1.2.1 2232 | 2233 | make-dir@4.0.0: 2234 | dependencies: 2235 | semver: 7.5.4 2236 | 2237 | meow@12.1.1: {} 2238 | 2239 | milliparsec@4.0.0: {} 2240 | 2241 | mime@4.0.4: {} 2242 | 2243 | minimatch@9.0.5: 2244 | dependencies: 2245 | brace-expansion: 2.0.1 2246 | 2247 | minimist@1.2.8: {} 2248 | 2249 | minipass@7.1.2: {} 2250 | 2251 | ms@2.0.0: {} 2252 | 2253 | ms@2.1.3: {} 2254 | 2255 | nanoid@3.3.8: {} 2256 | 2257 | negotiator@0.6.3: {} 2258 | 2259 | on-headers@1.0.2: {} 2260 | 2261 | p-limit@4.0.0: 2262 | dependencies: 2263 | yocto-queue: 1.1.1 2264 | 2265 | p-locate@6.0.0: 2266 | dependencies: 2267 | p-limit: 4.0.0 2268 | 2269 | package-json-from-dist@1.0.0: {} 2270 | 2271 | parent-module@1.0.1: 2272 | dependencies: 2273 | callsites: 3.1.0 2274 | 2275 | parse-json@5.2.0: 2276 | dependencies: 2277 | '@babel/code-frame': 7.18.6 2278 | error-ex: 1.3.2 2279 | json-parse-even-better-errors: 2.3.1 2280 | lines-and-columns: 1.2.4 2281 | 2282 | parseurl@1.3.3: {} 2283 | 2284 | path-exists@5.0.0: {} 2285 | 2286 | path-key@3.1.1: {} 2287 | 2288 | path-scurry@1.11.1: 2289 | dependencies: 2290 | lru-cache: 10.4.3 2291 | minipass: 7.1.2 2292 | 2293 | pathe@1.1.2: {} 2294 | 2295 | pathval@2.0.0: {} 2296 | 2297 | picocolors@1.1.0: {} 2298 | 2299 | postcss@8.4.47: 2300 | dependencies: 2301 | nanoid: 3.3.8 2302 | picocolors: 1.1.0 2303 | source-map-js: 1.2.1 2304 | 2305 | punycode@2.3.0: {} 2306 | 2307 | random-bytes@1.0.0: {} 2308 | 2309 | regexparam@2.0.2: {} 2310 | 2311 | require-directory@2.1.1: {} 2312 | 2313 | require-from-string@2.0.2: {} 2314 | 2315 | resolve-from@4.0.0: {} 2316 | 2317 | resolve-from@5.0.0: {} 2318 | 2319 | rollup@4.24.3: 2320 | dependencies: 2321 | '@types/estree': 1.0.6 2322 | optionalDependencies: 2323 | '@rollup/rollup-android-arm-eabi': 4.24.3 2324 | '@rollup/rollup-android-arm64': 4.24.3 2325 | '@rollup/rollup-darwin-arm64': 4.24.3 2326 | '@rollup/rollup-darwin-x64': 4.24.3 2327 | '@rollup/rollup-freebsd-arm64': 4.24.3 2328 | '@rollup/rollup-freebsd-x64': 4.24.3 2329 | '@rollup/rollup-linux-arm-gnueabihf': 4.24.3 2330 | '@rollup/rollup-linux-arm-musleabihf': 4.24.3 2331 | '@rollup/rollup-linux-arm64-gnu': 4.24.3 2332 | '@rollup/rollup-linux-arm64-musl': 4.24.3 2333 | '@rollup/rollup-linux-powerpc64le-gnu': 4.24.3 2334 | '@rollup/rollup-linux-riscv64-gnu': 4.24.3 2335 | '@rollup/rollup-linux-s390x-gnu': 4.24.3 2336 | '@rollup/rollup-linux-x64-gnu': 4.24.3 2337 | '@rollup/rollup-linux-x64-musl': 4.24.3 2338 | '@rollup/rollup-win32-arm64-msvc': 4.24.3 2339 | '@rollup/rollup-win32-ia32-msvc': 4.24.3 2340 | '@rollup/rollup-win32-x64-msvc': 4.24.3 2341 | fsevents: 2.3.3 2342 | 2343 | safe-buffer@5.2.1: {} 2344 | 2345 | semver@7.5.4: 2346 | dependencies: 2347 | lru-cache: 6.0.0 2348 | 2349 | semver@7.6.3: {} 2350 | 2351 | shebang-command@2.0.0: 2352 | dependencies: 2353 | shebang-regex: 3.0.0 2354 | 2355 | shebang-regex@3.0.0: {} 2356 | 2357 | siginfo@2.0.0: {} 2358 | 2359 | signal-exit@4.1.0: {} 2360 | 2361 | source-map-js@1.2.1: {} 2362 | 2363 | split2@4.2.0: {} 2364 | 2365 | stackback@0.0.2: {} 2366 | 2367 | std-env@3.7.0: {} 2368 | 2369 | string-width@4.2.3: 2370 | dependencies: 2371 | emoji-regex: 8.0.0 2372 | is-fullwidth-code-point: 3.0.0 2373 | strip-ansi: 6.0.1 2374 | 2375 | string-width@5.1.2: 2376 | dependencies: 2377 | eastasianwidth: 0.2.0 2378 | emoji-regex: 9.2.2 2379 | strip-ansi: 7.1.0 2380 | 2381 | strip-ansi@6.0.1: 2382 | dependencies: 2383 | ansi-regex: 5.0.1 2384 | 2385 | strip-ansi@7.1.0: 2386 | dependencies: 2387 | ansi-regex: 6.1.0 2388 | 2389 | supertest-fetch@2.0.0: {} 2390 | 2391 | supports-color@5.5.0: 2392 | dependencies: 2393 | has-flag: 3.0.0 2394 | 2395 | supports-color@7.2.0: 2396 | dependencies: 2397 | has-flag: 4.0.0 2398 | 2399 | test-exclude@7.0.1: 2400 | dependencies: 2401 | '@istanbuljs/schema': 0.1.3 2402 | glob: 10.4.5 2403 | minimatch: 9.0.5 2404 | 2405 | text-extensions@2.4.0: {} 2406 | 2407 | through@2.3.8: {} 2408 | 2409 | tinybench@2.9.0: {} 2410 | 2411 | tinyexec@0.3.0: {} 2412 | 2413 | tinypool@1.0.1: {} 2414 | 2415 | tinyrainbow@1.2.0: {} 2416 | 2417 | tinyspy@3.0.2: {} 2418 | 2419 | to-fast-properties@2.0.0: {} 2420 | 2421 | typescript@5.6.2: {} 2422 | 2423 | uid-safe@2.1.5: 2424 | dependencies: 2425 | random-bytes: 1.0.0 2426 | 2427 | undici-types@6.19.8: {} 2428 | 2429 | unicorn-magic@0.1.0: {} 2430 | 2431 | uri-js@4.4.1: 2432 | dependencies: 2433 | punycode: 2.3.0 2434 | 2435 | vite-node@2.1.1(@types/node@22.5.5): 2436 | dependencies: 2437 | cac: 6.7.14 2438 | debug: 4.3.7 2439 | pathe: 1.1.2 2440 | vite: 5.4.10(@types/node@22.5.5) 2441 | transitivePeerDependencies: 2442 | - '@types/node' 2443 | - less 2444 | - lightningcss 2445 | - sass 2446 | - sass-embedded 2447 | - stylus 2448 | - sugarss 2449 | - supports-color 2450 | - terser 2451 | 2452 | vite@5.4.10(@types/node@22.5.5): 2453 | dependencies: 2454 | esbuild: 0.21.5 2455 | postcss: 8.4.47 2456 | rollup: 4.24.3 2457 | optionalDependencies: 2458 | '@types/node': 22.5.5 2459 | fsevents: 2.3.3 2460 | 2461 | vitest@2.1.1(@types/node@22.5.5): 2462 | dependencies: 2463 | '@vitest/expect': 2.1.1 2464 | '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.10(@types/node@22.5.5)) 2465 | '@vitest/pretty-format': 2.1.1 2466 | '@vitest/runner': 2.1.1 2467 | '@vitest/snapshot': 2.1.1 2468 | '@vitest/spy': 2.1.1 2469 | '@vitest/utils': 2.1.1 2470 | chai: 5.1.1 2471 | debug: 4.3.7 2472 | magic-string: 0.30.11 2473 | pathe: 1.1.2 2474 | std-env: 3.7.0 2475 | tinybench: 2.9.0 2476 | tinyexec: 0.3.0 2477 | tinypool: 1.0.1 2478 | tinyrainbow: 1.2.0 2479 | vite: 5.4.10(@types/node@22.5.5) 2480 | vite-node: 2.1.1(@types/node@22.5.5) 2481 | why-is-node-running: 2.3.0 2482 | optionalDependencies: 2483 | '@types/node': 22.5.5 2484 | transitivePeerDependencies: 2485 | - less 2486 | - lightningcss 2487 | - msw 2488 | - sass 2489 | - sass-embedded 2490 | - stylus 2491 | - sugarss 2492 | - supports-color 2493 | - terser 2494 | 2495 | which@2.0.2: 2496 | dependencies: 2497 | isexe: 2.0.0 2498 | 2499 | why-is-node-running@2.3.0: 2500 | dependencies: 2501 | siginfo: 2.0.0 2502 | stackback: 0.0.2 2503 | 2504 | wrap-ansi@7.0.0: 2505 | dependencies: 2506 | ansi-styles: 4.3.0 2507 | string-width: 4.2.3 2508 | strip-ansi: 6.0.1 2509 | 2510 | wrap-ansi@8.1.0: 2511 | dependencies: 2512 | ansi-styles: 6.2.1 2513 | string-width: 5.1.2 2514 | strip-ansi: 7.1.0 2515 | 2516 | y18n@5.0.8: {} 2517 | 2518 | yallist@4.0.0: {} 2519 | 2520 | yargs-parser@21.1.1: {} 2521 | 2522 | yargs@17.7.2: 2523 | dependencies: 2524 | cliui: 8.0.1 2525 | escalade: 3.1.1 2526 | get-caller-file: 2.0.5 2527 | require-directory: 2.1.1 2528 | string-width: 4.2.3 2529 | y18n: 5.0.8 2530 | yargs-parser: 21.1.1 2531 | 2532 | yocto-queue@1.1.1: {} 2533 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import type { IncomingMessage, ServerResponse } from 'node:http' 2 | import type { ParsedUrlQuery } from 'node:querystring' 3 | import { type SerializeOptions, serialize } from '@tinyhttp/cookie' 4 | import { sign } from '@tinyhttp/cookie-signature' 5 | import { Tokens } from './token' 6 | 7 | export interface CSRFRequest extends IncomingMessage { 8 | csrfToken(): string 9 | secret?: string | string[] 10 | signedCookies?: Record 11 | cookies?: Record 12 | query?: ParsedUrlQuery 13 | body?: Record 14 | } 15 | 16 | // HTTP Method according to MDN (https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) 17 | type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'HEAD' | 'TRACE' 18 | 19 | type MiddlewareOptions = 'session' | 'cookie' 20 | 21 | /** 22 | * Options for CSRF constructor. 23 | * Refer to README for more information. 24 | */ 25 | export interface CSRFOptions { 26 | middleware?: MiddlewareOptions 27 | cookie?: CookieOptions 28 | sessionKey?: string 29 | value?: (req: CSRFRequest | IncomingMessage) => string | string[] 30 | ignoreMethod?: HTTPMethod[] 31 | saltLength?: number 32 | secretLength?: number 33 | } 34 | 35 | /** 36 | * Options for cookie value. 37 | * Extends SerializeOptions from @tinyhttp/cookie. 38 | */ 39 | export type CookieOptions = SerializeOptions & { 40 | signed?: boolean 41 | key?: string 42 | path?: string 43 | } 44 | 45 | const defaultOptions: CSRFOptions = { 46 | middleware: 'cookie', 47 | cookie: { signed: false, key: '_csrf', path: '/' }, 48 | sessionKey: 'session', 49 | ignoreMethod: ['GET', 'HEAD', 'OPTIONS'], 50 | saltLength: 8, 51 | secretLength: 18, 52 | value: defaultValue 53 | } 54 | 55 | /** 56 | * Initiate CSRF (Cross-Site Request Forgery) Protection middleware. 57 | * @function csrf 58 | * @param {CSRFOptions} opts Given configuration options 59 | * @returns {(req: CSRFRequest, res: ServerResponse, next: () => void) => void} CSRF Protection Middleware 60 | * @example 61 | * const csrfProtection = csrf() 62 | * app.use(cookieParser()) // or a session middleware, if you prefer 63 | * 64 | * app.get("/", csrfProtection, (req, res) => { 65 | * res.status(200).json({ token: req.csrfToken() }); 66 | * }); 67 | */ 68 | export function csrf(opts: CSRFOptions = {}): (req: CSRFRequest, res: ServerResponse, next: () => void) => void { 69 | const options = Object.assign({}, defaultOptions, opts) 70 | 71 | if (!options.cookie?.key) options.cookie.key = '_csrf' 72 | if (!options.cookie?.path) options.cookie.path = '/' 73 | 74 | const tokens = new Tokens({ 75 | saltLength: options.saltLength, 76 | secretLength: options.secretLength 77 | }) 78 | 79 | return (req: CSRFRequest, res: ServerResponse, next: () => void) => { 80 | if (!verifyConfiguration(req, options.sessionKey, options.cookie, options.middleware)) { 81 | throw new Error('misconfigured csrf') 82 | } 83 | 84 | let secret: string | undefined = getSecret(req, options.sessionKey, options.cookie, options.middleware) 85 | let token: string 86 | 87 | req.csrfToken = (): string => { 88 | const newSecret = 89 | options.middleware === 'session' 90 | ? getSecret(req, options.sessionKey, options.cookie, options.middleware) 91 | : secret 92 | 93 | token = tokens.create(newSecret) 94 | return token 95 | } 96 | 97 | if (!secret) { 98 | secret = tokens.secret() 99 | setSecret(req, res, options.sessionKey, secret, options.cookie, options.middleware) 100 | } 101 | 102 | if (!options.ignoreMethod.includes(req.method as HTTPMethod) && !tokens.verify(secret, options.value(req))) { 103 | return res 104 | .writeHead(403, 'invalid csrf token', { 'Content-Type': 'text/plain' }) 105 | .end('invalid csrf token', 'utf8') 106 | } 107 | 108 | next() 109 | } 110 | } 111 | 112 | function defaultValue(req: CSRFRequest): string | string[] { 113 | return ( 114 | (req?.body?._csrf as string) || 115 | req.query?._csrf || 116 | req.headers['csrf-token'] || 117 | req.headers['xsrf-token'] || 118 | req.headers['x-csrf-token'] || 119 | req.headers['x-xsrf-token'] 120 | ) 121 | } 122 | 123 | function verifyConfiguration( 124 | req: CSRFRequest, 125 | sessionKey: string, 126 | cookie: CookieOptions, 127 | middleware: MiddlewareOptions 128 | ): boolean { 129 | if (!getSecretBag(req, sessionKey, cookie, middleware)) { 130 | return false 131 | } 132 | 133 | if (cookie?.signed && !req?.secret) { 134 | return false 135 | } 136 | 137 | return true 138 | } 139 | 140 | function getSecret( 141 | req: CSRFRequest, 142 | sessionKey: string, 143 | cookie: CookieOptions, 144 | middleware: MiddlewareOptions 145 | ): string | undefined { 146 | const bag: Record | undefined = getSecretBag(req, sessionKey, cookie, middleware) 147 | const key = middleware === 'cookie' ? cookie.key : 'csrfSecret' 148 | 149 | return bag ? bag[key] : undefined 150 | } 151 | 152 | function getSecretBag( 153 | req: CSRFRequest, 154 | sessionKey: string, 155 | cookie: CookieOptions, 156 | middleware: MiddlewareOptions 157 | ): Record | undefined { 158 | if (middleware === 'cookie' && cookie) { 159 | if (cookie.signed) { 160 | return req?.signedCookies 161 | } 162 | 163 | return req?.cookies 164 | } 165 | return req[sessionKey] 166 | } 167 | 168 | function setSecret( 169 | req: CSRFRequest, 170 | res: ServerResponse, 171 | sessionKey: string, 172 | secret: string, 173 | cookie: CookieOptions, 174 | middleware: MiddlewareOptions 175 | ): void { 176 | if (middleware === 'cookie' && cookie) { 177 | const value = cookie.signed ? `s:${sign(secret, req.secret as string)}` : secret 178 | setCookie(res, cookie.key, value, cookie) 179 | return 180 | } 181 | 182 | req[sessionKey].csrfSecret = secret 183 | } 184 | 185 | function setCookie(res: ServerResponse, name: string, secret: string, cookie: CookieOptions): void { 186 | const data = serialize(name, secret, cookie) 187 | const previousHeader = (res.getHeader('set-cookie') as string[]) ?? [] 188 | res.setHeader('set-cookie', Array.isArray(previousHeader) ? previousHeader.concat(data) : [previousHeader, data]) 189 | } 190 | -------------------------------------------------------------------------------- /src/token.ts: -------------------------------------------------------------------------------- 1 | import { hash, randomBase62, timeSafeCompare, typeSafeUID } from './utils' 2 | 3 | interface TokenOptions { 4 | saltLength: number 5 | secretLength: number 6 | } 7 | 8 | /** 9 | * Token Generation. 10 | * A rewrite of https://github.com/pillarjs/csrf. 11 | * @class Tokens 12 | * @license MIT Copyright (c) Jonathan Ong and Douglas Christopher Wilson 13 | */ 14 | export class Tokens { 15 | saltLength: number 16 | secretLength: number 17 | 18 | constructor(options: TokenOptions) { 19 | this.saltLength = options.saltLength 20 | this.secretLength = options.secretLength 21 | } 22 | 23 | create(secret: string): string { 24 | return this.tokenize(secret, randomBase62(this.saltLength)) 25 | } 26 | 27 | secret(): string { 28 | return typeSafeUID(this.secretLength) 29 | } 30 | 31 | verify(secret: string, token: string | string[]): boolean { 32 | if (Array.isArray(token)) token = token.join('') 33 | const index = token?.indexOf('-') 34 | if (index === -1 || index === undefined) { 35 | return false 36 | } 37 | 38 | const salt = token.slice(0, index) 39 | const expected = this.tokenize(secret, salt) 40 | 41 | return timeSafeCompare(token, expected) 42 | } 43 | 44 | tokenize(secret: string, salt: string): string { 45 | return `${salt}-${hash(`${salt}-${secret}`)}` 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto' 2 | 3 | /** 4 | * Generate a secure type safe UID with specified length. 5 | * Rewrite of https://github.com/crypto-utils/uid-safe 6 | * @param {Number} length UID length 7 | * @returns {String} Type safe UID 8 | */ 9 | export function typeSafeUID(length: number): string { 10 | return randomBytes(length).toString('base64').replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_') 11 | } 12 | 13 | /** 14 | * Generate a (not secure) quick salt with specified length. 15 | * Rewrite of https://github.com/crypto-utils/rndm 16 | * @param {Number} length Salt length 17 | * @returns {String} Salt 18 | */ 19 | export function randomBase62(length: number): string { 20 | const availableCharacters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' 21 | const byteLength = Buffer.byteLength(availableCharacters) 22 | let salt = '' 23 | for (let i = 0; i < length; i++) { 24 | salt += availableCharacters[Math.floor(byteLength * Math.random())] 25 | } 26 | 27 | return salt 28 | } 29 | 30 | /** 31 | * Create a SHA1 hash from specified string 32 | * Rewrite of a function from https://github.com/pillarjs/csrf 33 | * @param {String} str Dirty string 34 | * @returns {String} Hashed string 35 | */ 36 | export function hash(str: string): string { 37 | return createHash('sha256') 38 | .update(str, 'ascii') 39 | .digest('base64') 40 | .replace(/=+$/, '') 41 | .replace(/\+/g, '-') 42 | .replace(/\//g, '_') 43 | } 44 | 45 | /** 46 | * Compare two keys, to check if they have the same creation duration. 47 | * Rewrite of https://github.com/suryagh/tsscmp 48 | * @param {String} a Key #1 49 | * @param {String} b Key #2 50 | * @returns {Boolean} Whether both keys matches or not 51 | */ 52 | export function timeSafeCompare(a: string, b: string): boolean { 53 | const key = randomBytes(32) 54 | const aHMAC = createHmac('sha256', key).update(String(a)).digest() 55 | const bHMAC = createHmac('sha256', key).update(String(b)).digest() 56 | 57 | return timingSafeEqual(aHMAC, bHMAC) 58 | } 59 | -------------------------------------------------------------------------------- /tests/cookie.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, it } from 'vitest' 2 | import { onTestFinished } from 'vitest' 3 | import type { CSRFOptions } from '../src/index' 4 | import { initApp } from './helper' 5 | 6 | describe('unsigned cookie - output', () => { 7 | it('should output a csrf token', async () => { 8 | const { fetch, server } = initApp({ middleware: 'cookie' }) 9 | onTestFinished(() => { 10 | server.close() 11 | }) 12 | 13 | const response = await fetch('/') 14 | const body = await response.json() 15 | 16 | assert.equal(response.status, 200) 17 | assert.ok(response.headers.has('set-cookie')) 18 | assert.ok(response.headers.get('set-cookie')?.startsWith('_csrf=')) 19 | assert.typeOf(body.token, 'string') 20 | }) 21 | 22 | it('should output a csrf token with given options (different salt & secret length)', async () => { 23 | const options: CSRFOptions = { 24 | saltLength: 10, 25 | secretLength: 30 26 | } 27 | const { fetch, server } = initApp({ middleware: 'cookie', options }) 28 | onTestFinished(() => { 29 | server.close() 30 | }) 31 | 32 | const response = await fetch('/') 33 | const body = await response.json() 34 | 35 | const [salt, _] = body.token.split('-') 36 | assert.equal(response.status, 200) 37 | assert.equal(salt.length, 10) 38 | }) 39 | 40 | it('should output a csrf token with given options (different cookie path)', async () => { 41 | const options: CSRFOptions = { 42 | cookie: { 43 | path: '/admin', 44 | key: 'virus' 45 | } 46 | } 47 | const { fetch, server } = initApp({ middleware: 'cookie', options }) 48 | onTestFinished(() => { 49 | server.close() 50 | }) 51 | 52 | const response = await fetch('/') 53 | const body = await response.json() 54 | 55 | const [token, path] = response.headers.get('set-cookie')?.split(' ') ?? ['', ''] 56 | 57 | assert.equal(response.status, 200) 58 | assert.ok(response.headers.has('set-cookie')) 59 | assert.ok(token.startsWith('virus')) 60 | assert.equal(path.split('Path=')[1], '/admin') 61 | assert.typeOf(body.token, 'string') 62 | }) 63 | }) 64 | 65 | describe('unsigned cookie - req.body', () => { 66 | it('should be able to pass through req.body', async () => { 67 | const { fetch, server } = initApp({ middleware: 'cookie', parser: 'json' }) 68 | onTestFinished(() => { 69 | server.close() 70 | }) 71 | 72 | const request = await fetch('/') 73 | const requestBody = await request.json() 74 | 75 | const response = await fetch('/', { 76 | method: 'post', 77 | body: JSON.stringify({ _csrf: requestBody.token, hello: 'there' }), 78 | headers: { 79 | cookie: request.headers.get('set-cookie') ?? '', 80 | 'content-type': 'application/json' 81 | } 82 | }) 83 | const body = await response.json() 84 | 85 | assert.equal(response.status, 200) 86 | assert.equal(body.message, 'hello') 87 | }) 88 | 89 | it('should not be able to pass through req.body', async () => { 90 | const { fetch, server } = initApp({ middleware: 'cookie', parser: 'json' }) 91 | onTestFinished(() => { 92 | server.close() 93 | }) 94 | const request = await fetch('/') 95 | 96 | const abortController = new AbortController() 97 | const timeout = setTimeout(() => abortController.abort(), 5000) 98 | 99 | try { 100 | const response = await fetch('/', { 101 | method: 'post', 102 | body: '{}', 103 | headers: { 104 | cookie: request.headers.get('set-cookie') ?? '', 105 | 'content-type': 'application/json' 106 | }, 107 | signal: abortController.signal 108 | }) 109 | const body = await response.text() 110 | 111 | assert.equal(response.status, 403) 112 | assert.equal(body, 'invalid csrf token') 113 | } finally { 114 | clearTimeout(timeout) 115 | } 116 | }) 117 | }) 118 | 119 | describe('unsigned cookie - req.query', () => { 120 | it('should be able to pass through query', async () => { 121 | const { fetch, server } = initApp({ middleware: 'cookie' }) 122 | onTestFinished(() => { 123 | server.close() 124 | }) 125 | const request = await fetch('/') 126 | const requestBody = await request.json() 127 | 128 | const response = await fetch(`/?_csrf=${encodeURIComponent(requestBody.token)}`, { 129 | method: 'post', 130 | headers: { 131 | cookie: request.headers.get('set-cookie') ?? '', 132 | 'content-type': 'application/x-www-form-urlencoded' 133 | } 134 | }) 135 | const body = await response.json() 136 | 137 | assert.equal(response.status, 200) 138 | assert.equal(body.message, 'hello') 139 | }) 140 | }) 141 | 142 | describe('unsigned cookie - req.headers', () => { 143 | it('should be able to pass through headers csrf-token', async () => { 144 | const { fetch, server } = initApp({ middleware: 'cookie' }) 145 | onTestFinished(() => { 146 | server.close() 147 | }) 148 | const request = await fetch('/') 149 | const requestBody = await request.json() 150 | 151 | const response = await fetch('/', { 152 | method: 'post', 153 | headers: { 154 | cookie: request.headers.get('set-cookie') ?? '', 155 | 'csrf-token': requestBody.token 156 | } 157 | }) 158 | const body = await response.json() 159 | 160 | assert.equal(response.status, 200) 161 | assert.equal(body.message, 'hello') 162 | }) 163 | 164 | it('should be able to pass through headers xsrf-token', async () => { 165 | const { fetch, server } = initApp({ middleware: 'cookie' }) 166 | onTestFinished(() => { 167 | server.close() 168 | }) 169 | const request = await fetch('/') 170 | const requestBody = await request.json() 171 | 172 | const response = await fetch('/', { 173 | method: 'post', 174 | headers: { 175 | cookie: request.headers.get('set-cookie') ?? '', 176 | 'xsrf-token': requestBody.token 177 | } 178 | }) 179 | const body = await response.json() 180 | 181 | assert.equal(response.status, 200) 182 | assert.equal(body.message, 'hello') 183 | }) 184 | 185 | it('should be able to pass through headers x-csrf-token', async () => { 186 | const { fetch, server } = initApp({ middleware: 'cookie' }) 187 | onTestFinished(() => { 188 | server.close() 189 | }) 190 | const request = await fetch('/') 191 | const requestBody = await request.json() 192 | 193 | const response = await fetch('/', { 194 | method: 'post', 195 | headers: { 196 | cookie: request.headers.get('set-cookie') ?? '', 197 | 'x-csrf-token': requestBody.token 198 | } 199 | }) 200 | const body = await response.json() 201 | 202 | assert.equal(response.status, 200) 203 | assert.equal(body.message, 'hello') 204 | }) 205 | 206 | it('should be able to pass through headers x-xsrf-token', async () => { 207 | const { fetch, server } = initApp({ middleware: 'cookie' }) 208 | onTestFinished(() => { 209 | server.close() 210 | }) 211 | const request = await fetch('/') 212 | const requestBody = await request.json() 213 | 214 | const response = await fetch('/', { 215 | method: 'post', 216 | headers: { 217 | cookie: request.headers.get('set-cookie') ?? '', 218 | 'x-xsrf-token': requestBody.token 219 | } 220 | }) 221 | const body = await response.json() 222 | 223 | assert.equal(response.status, 200) 224 | assert.equal(body.message, 'hello') 225 | }) 226 | }) 227 | 228 | describe('reusable token', () => { 229 | it('a', async () => { 230 | const { fetch, server } = initApp({ middleware: 'cookie' }) 231 | onTestFinished(() => { 232 | server.close() 233 | }) 234 | const request = await fetch('/') 235 | const requestBody = await request.json() 236 | 237 | // response #1 238 | const response1 = await fetch('/', { 239 | method: 'post', 240 | headers: { 241 | cookie: request.headers.get('set-cookie') ?? '', 242 | 'x-xsrf-token': requestBody.token 243 | } 244 | }) 245 | const body1 = await response1.json() 246 | 247 | // response #2 248 | const response2 = await fetch('/', { 249 | method: 'post', 250 | headers: { 251 | cookie: request.headers.get('set-cookie') ?? '', 252 | 'x-xsrf-token': requestBody.token 253 | } 254 | }) 255 | const body2 = await response2.json() 256 | 257 | assert.equal(response1.status, 200) 258 | assert.equal(response2.status, 200) 259 | assert.equal(body1.message, 'hello') 260 | assert.equal(body2.message, 'hello') 261 | }) 262 | }) 263 | -------------------------------------------------------------------------------- /tests/failing.test.ts: -------------------------------------------------------------------------------- 1 | import { App, type Request } from '@tinyhttp/app' 2 | import { cookieParser } from '@tinyhttp/cookie-parser' 3 | import { urlencoded } from 'milliparsec' 4 | import { makeFetch } from 'supertest-fetch' 5 | import { assert, describe, it } from 'vitest' 6 | import { onTestFinished } from 'vitest' 7 | import { csrf } from '../src/index' 8 | import type { CSRFRequest } from '../src/index' 9 | 10 | describe('failing - these should return error', () => { 11 | it('without a cookie parser', async () => { 12 | const app = new App() 13 | const csrfProtection = csrf({ cookie: { signed: false } }) 14 | app.use('/', urlencoded(), csrfProtection, (req, res) => { 15 | res.status(200).json({ token: req.csrfToken() }) 16 | }) 17 | const server = app.listen() 18 | onTestFinished(() => { 19 | server.close() 20 | }) 21 | 22 | const fetch = makeFetch(server) 23 | 24 | const response = await fetch('/') 25 | const body = await response.text() 26 | 27 | assert.equal(response.status, 500) 28 | assert.equal(body, 'misconfigured csrf') 29 | }) 30 | 31 | it('signed but without a secret', async () => { 32 | const app = new App() 33 | app.use(cookieParser()) 34 | const csrfProtection = csrf({ cookie: { signed: true } }) 35 | app.use('/', urlencoded(), csrfProtection, (req, res) => { 36 | res.status(200).json({ token: req.csrfToken() }) 37 | }) 38 | const server = app.listen() 39 | onTestFinished(() => { 40 | server.close() 41 | }) 42 | 43 | const fetch = makeFetch(server) 44 | 45 | const response = await fetch('/') 46 | const body = await response.text() 47 | 48 | assert.equal(response.status, 500) 49 | assert.equal(body, 'misconfigured csrf') 50 | }) 51 | 52 | it('signed cookie without cookie parser', async () => { 53 | const app = new App() 54 | const csrfProtection = csrf({ cookie: { signed: true } }) 55 | app.use('/', urlencoded(), csrfProtection, (req, res) => { 56 | res.status(200).json({ token: req.csrfToken() }) 57 | }) 58 | const server = app.listen() 59 | onTestFinished(() => { 60 | server.close() 61 | }) 62 | 63 | const fetch = makeFetch(server) 64 | 65 | const response = await fetch('/') 66 | const body = await response.text() 67 | 68 | assert.equal(response.status, 500) 69 | assert.equal(body, 'misconfigured csrf') 70 | }) 71 | 72 | it('session without the session middleware', async () => { 73 | const app = new App() 74 | const csrfProtection = csrf({ middleware: 'session' }) 75 | app.use('/', urlencoded(), csrfProtection, (req, res) => { 76 | res.status(200).json({ token: req.csrfToken() }) 77 | }) 78 | const server = app.listen() 79 | onTestFinished(() => { 80 | server.close() 81 | }) 82 | 83 | const fetch = makeFetch(server) 84 | 85 | const response = await fetch('/') 86 | const body = await response.text() 87 | 88 | assert.equal(response.status, 500) 89 | assert.equal(body, 'misconfigured csrf') 90 | }) 91 | }) 92 | -------------------------------------------------------------------------------- /tests/helper.ts: -------------------------------------------------------------------------------- 1 | import { randomBytes } from 'node:crypto' 2 | import { App } from '@tinyhttp/app' 3 | import type { Request, Response } from '@tinyhttp/app' 4 | import { cookieParser } from '@tinyhttp/cookie-parser' 5 | import session from 'express-session' 6 | import { json, urlencoded } from 'milliparsec' 7 | import { makeFetch } from 'supertest-fetch' 8 | import { csrf } from '../src/index' 9 | import type { CSRFOptions, CSRFRequest } from '../src/index' 10 | 11 | type ParserOptions = 'urlencoded' | 'json' | '' 12 | type MiddlewareOptions = 'cookie' | 'signedCookie' | 'session' 13 | 14 | interface initAppOptions { 15 | middleware?: MiddlewareOptions 16 | parser?: ParserOptions 17 | options?: CSRFOptions 18 | } 19 | 20 | const secret = randomBytes(32).toString('base64') 21 | 22 | export function initApp({ parser, options = {}, middleware = 'cookie' }: initAppOptions) { 23 | const app = new App() 24 | const csrfProtection = csrf(parseOptions(options, middleware)) 25 | 26 | if (parser === 'urlencoded') { 27 | app.use(urlencoded()) 28 | } else if (parser === 'json') { 29 | app.use(json()) 30 | } 31 | 32 | if (middleware === 'cookie') { 33 | app.use(cookieParser()) 34 | } else if (middleware === 'signedCookie') { 35 | app.use(cookieParser(secret)) 36 | } else if (middleware === 'session') { 37 | // @ts-expect-error testing purposes 38 | app.use(session({ secret, resave: false, saveUninitialized: false, name: 'session' })) 39 | } 40 | 41 | app.get('/', csrfProtection, (req, res) => { 42 | res.status(200).json({ token: req.csrfToken() }) 43 | }) 44 | 45 | app.post('/', csrfProtection, (_, res) => { 46 | res.status(200).json({ message: 'hello' }) 47 | }) 48 | 49 | const server = app.listen() 50 | const fetch = makeFetch(server) 51 | 52 | return { fetch, app, server } 53 | } 54 | 55 | function parseOptions(options: CSRFOptions, middleware: MiddlewareOptions) { 56 | if (Object.keys(options).length === 0 && middleware === 'signedCookie') { 57 | return { cookie: { signed: true } } 58 | } 59 | return options 60 | } 61 | -------------------------------------------------------------------------------- /tests/session.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, it } from 'vitest' 2 | import { onTestFinished } from 'vitest' 3 | import type { CSRFOptions } from '../src/index' 4 | import { initApp } from './helper' 5 | 6 | const options: CSRFOptions = { 7 | middleware: 'session' 8 | } 9 | 10 | describe('session - output', () => { 11 | it('should output a csrf token', async () => { 12 | const { fetch, server } = initApp({ middleware: 'session', options }) 13 | onTestFinished(() => { 14 | server.close() 15 | }) 16 | 17 | const response = await fetch('/') 18 | const body = await response.json() 19 | 20 | assert.equal(response.status, 200) 21 | assert.ok(response.headers.has('set-cookie')) 22 | assert.typeOf(body.token, 'string') 23 | }) 24 | 25 | it('should output a csrf token with given options (different salt & secret length)', async () => { 26 | const saltySecret: CSRFOptions = { 27 | saltLength: 10, 28 | secretLength: 30 29 | } 30 | const { fetch, server } = initApp({ middleware: 'session', options: { ...options, ...saltySecret } }) 31 | onTestFinished(() => { 32 | server.close() 33 | }) 34 | 35 | const response = await fetch('/') 36 | const body = await response.json() 37 | 38 | const [salt, _] = body.token.split('-') 39 | assert.equal(response.status, 200) 40 | assert.equal(salt.length, 10) 41 | }) 42 | }) 43 | 44 | describe('session - req.body', () => { 45 | it('should be able to pass through req.body', async () => { 46 | const { fetch, server } = initApp({ middleware: 'session', parser: 'json', options }) 47 | onTestFinished(() => { 48 | server.close() 49 | }) 50 | 51 | const request = await fetch('/') 52 | const requestBody = await request.json() 53 | 54 | const response = await fetch('/', { 55 | method: 'post', 56 | body: JSON.stringify({ _csrf: requestBody.token, hello: 'there' }), 57 | headers: { 58 | cookie: request.headers.get('set-cookie') ?? '', 59 | 'content-type': 'application/json' 60 | } 61 | }) 62 | const body = await response.json() 63 | 64 | assert.equal(response.status, 200) 65 | assert.equal(body.message, 'hello') 66 | }) 67 | 68 | it('should not be able to pass through req.body', async () => { 69 | const { fetch, server } = initApp({ middleware: 'session', parser: 'json', options }) 70 | onTestFinished(() => { 71 | server.close() 72 | }) 73 | 74 | const request = await fetch('/') 75 | const abortController = new AbortController() 76 | const timeout = setTimeout(() => abortController.abort(), 5000) 77 | 78 | try { 79 | const response = await fetch('/', { 80 | method: 'post', 81 | body: '{}', 82 | headers: { 83 | cookie: request.headers.get('set-cookie') ?? '', 84 | 'content-type': 'application/json' 85 | }, 86 | signal: abortController.signal 87 | }) 88 | const body = await response.text() 89 | 90 | assert.equal(response.status, 403) 91 | assert.equal(body, 'invalid csrf token') 92 | } finally { 93 | clearTimeout(timeout) 94 | } 95 | }) 96 | }) 97 | 98 | describe('session - req.query', () => { 99 | it('should be able to pass through query', async () => { 100 | const { fetch, server } = initApp({ middleware: 'session', options }) 101 | onTestFinished(() => { 102 | server.close() 103 | }) 104 | 105 | const request = await fetch('/') 106 | const requestBody = await request.json() 107 | 108 | const response = await fetch(`/?_csrf=${encodeURIComponent(requestBody.token)}`, { 109 | method: 'post', 110 | headers: { 111 | cookie: request.headers.get('set-cookie') ?? '', 112 | 'content-type': 'application/x-www-form-urlencoded' 113 | } 114 | }) 115 | const body = await response.json() 116 | 117 | assert.equal(response.status, 200) 118 | assert.equal(body.message, 'hello') 119 | }) 120 | }) 121 | 122 | describe('session - req.headers', () => { 123 | it('should be able to pass through headers csrf-token', async () => { 124 | const { fetch, server } = initApp({ middleware: 'session', options }) 125 | onTestFinished(() => { 126 | server.close() 127 | }) 128 | 129 | const request = await fetch('/') 130 | const requestBody = await request.json() 131 | 132 | const response = await fetch('/', { 133 | method: 'post', 134 | headers: { 135 | cookie: request.headers.get('set-cookie') ?? '', 136 | 'csrf-token': requestBody.token 137 | } 138 | }) 139 | const body = await response.json() 140 | 141 | assert.equal(response.status, 200) 142 | assert.equal(body.message, 'hello') 143 | }) 144 | 145 | it('should be able to pass through headers xsrf-token', async () => { 146 | const { fetch, server } = initApp({ middleware: 'session', options }) 147 | onTestFinished(() => { 148 | server.close() 149 | }) 150 | 151 | const request = await fetch('/') 152 | const requestBody = await request.json() 153 | 154 | const response = await fetch('/', { 155 | method: 'post', 156 | headers: { 157 | cookie: request.headers.get('set-cookie') ?? '', 158 | 'xsrf-token': requestBody.token 159 | } 160 | }) 161 | const body = await response.json() 162 | 163 | assert.equal(response.status, 200) 164 | assert.equal(body.message, 'hello') 165 | }) 166 | 167 | it('should be able to pass through headers x-csrf-token', async () => { 168 | const { fetch, server } = initApp({ middleware: 'session', options }) 169 | onTestFinished(() => { 170 | server.close() 171 | }) 172 | 173 | const request = await fetch('/') 174 | const requestBody = await request.json() 175 | 176 | const response = await fetch('/', { 177 | method: 'post', 178 | headers: { 179 | cookie: request.headers.get('set-cookie') ?? '', 180 | 'x-csrf-token': requestBody.token 181 | } 182 | }) 183 | const body = await response.json() 184 | 185 | assert.equal(response.status, 200) 186 | assert.equal(body.message, 'hello') 187 | }) 188 | 189 | it('should be able to pass through headers x-xsrf-token', async () => { 190 | const { fetch, server } = initApp({ middleware: 'session', options }) 191 | onTestFinished(() => { 192 | server.close() 193 | }) 194 | 195 | const request = await fetch('/') 196 | const requestBody = await request.json() 197 | 198 | const response = await fetch('/', { 199 | method: 'post', 200 | headers: { 201 | cookie: request.headers.get('set-cookie') ?? '', 202 | 'x-xsrf-token': requestBody.token 203 | } 204 | }) 205 | const body = await response.json() 206 | 207 | assert.equal(response.status, 200) 208 | assert.equal(body.message, 'hello') 209 | }) 210 | }) 211 | 212 | describe('reusable token', () => { 213 | it('a', async () => { 214 | const { fetch, server } = initApp({ middleware: 'session', options }) 215 | onTestFinished(() => { 216 | server.close() 217 | }) 218 | 219 | const request = await fetch('/') 220 | const requestBody = await request.json() 221 | 222 | // response #1 223 | const response1 = await fetch('/', { 224 | method: 'post', 225 | headers: { 226 | cookie: request.headers.get('set-cookie') ?? '', 227 | 'x-xsrf-token': requestBody.token 228 | } 229 | }) 230 | const body1 = await response1.json() 231 | 232 | // response #2 233 | const response2 = await fetch('/', { 234 | method: 'post', 235 | headers: { 236 | cookie: request.headers.get('set-cookie') ?? '', 237 | 'x-xsrf-token': requestBody.token 238 | } 239 | }) 240 | const body2 = await response2.json() 241 | 242 | assert.equal(response1.status, 200) 243 | assert.equal(response2.status, 200) 244 | assert.equal(body1.message, 'hello') 245 | assert.equal(body2.message, 'hello') 246 | }) 247 | }) 248 | -------------------------------------------------------------------------------- /tests/signedCookie.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, it } from 'vitest' 2 | import { onTestFinished } from 'vitest' 3 | import type { CSRFOptions } from '../src/index' 4 | import { initApp } from './helper' 5 | 6 | describe('signed cookie - output', () => { 7 | it('should output a csrf token', async () => { 8 | const { fetch, server } = initApp({ middleware: 'signedCookie' }) 9 | onTestFinished(() => { 10 | server.close() 11 | }) 12 | 13 | const response = await fetch('/') 14 | const body = await response.json() 15 | 16 | assert.equal(response.status, 200) 17 | assert.ok(response.headers.has('set-cookie')) 18 | assert.ok(response.headers.get('set-cookie')?.startsWith('_csrf=s%3A')) 19 | assert.typeOf(body.token, 'string') 20 | }) 21 | 22 | it('should output a csrf token with given options (different salt & secret length)', async () => { 23 | const options: CSRFOptions = { 24 | saltLength: 10, 25 | secretLength: 30, 26 | cookie: { 27 | signed: true 28 | } 29 | } 30 | const { fetch, server } = initApp({ middleware: 'signedCookie', options }) 31 | onTestFinished(() => { 32 | server.close() 33 | }) 34 | 35 | const response = await fetch('/') 36 | const body = await response.json() 37 | 38 | const [salt, _] = body.token.split('-') 39 | assert.equal(response.status, 200) 40 | assert.equal(salt.length, 10) 41 | }) 42 | 43 | it('should output a csrf token with given options (different cookie path)', async () => { 44 | const options: CSRFOptions = { 45 | cookie: { 46 | signed: true, 47 | path: '/admin', 48 | key: 'virus' 49 | } 50 | } 51 | const { fetch, server } = initApp({ middleware: 'signedCookie', options }) 52 | onTestFinished(() => { 53 | server.close() 54 | }) 55 | 56 | const response = await fetch('/') 57 | const body = await response.json() 58 | 59 | const [token, path] = response.headers.get('set-cookie')?.split(' ') ?? ['', ''] 60 | 61 | assert.equal(response.status, 200) 62 | assert.ok(response.headers.has('set-cookie')) 63 | assert.ok(token.startsWith('virus')) 64 | assert.equal(path.split('Path=')[1], '/admin') 65 | assert.typeOf(body.token, 'string') 66 | }) 67 | }) 68 | 69 | describe('signed cookie - req.body', () => { 70 | it('should be able to pass through req.body', async () => { 71 | const { fetch, server } = initApp({ middleware: 'signedCookie', parser: 'json' }) 72 | onTestFinished(() => { 73 | server.close() 74 | }) 75 | const request = await fetch('/') 76 | const requestBody = await request.json() 77 | 78 | const response = await fetch('/', { 79 | method: 'post', 80 | body: JSON.stringify({ _csrf: requestBody.token }), 81 | headers: { 82 | cookie: request.headers.get('set-cookie') ?? '', 83 | 'content-type': 'application/json' 84 | } 85 | }) 86 | const body = await response.json() 87 | 88 | assert.equal(response.status, 200) 89 | assert.equal(body.message, 'hello') 90 | }) 91 | 92 | it('should not be able to pass through req.body', async () => { 93 | const { fetch, server } = initApp({ middleware: 'signedCookie', parser: 'json' }) 94 | onTestFinished(() => { 95 | server.close() 96 | }) 97 | const request = await fetch('/') 98 | 99 | const response = await fetch('/', { 100 | method: 'post', 101 | body: JSON.stringify({}), 102 | headers: { 103 | cookie: request.headers.get('set-cookie') ?? '', 104 | 'content-type': 'application/json' 105 | } 106 | }) 107 | const body = await response.text() 108 | 109 | assert.equal(response.status, 403) 110 | assert.equal(body, 'invalid csrf token') 111 | }) 112 | }) 113 | 114 | describe('signed cookie - req.query', () => { 115 | it('should be able to pass through query', async () => { 116 | const { fetch, server } = initApp({ middleware: 'signedCookie' }) 117 | onTestFinished(() => { 118 | server.close() 119 | }) 120 | const request = await fetch('/') 121 | const requestBody = await request.json() 122 | 123 | const response = await fetch(`/?_csrf=${encodeURIComponent(requestBody.token)}`, { 124 | method: 'post', 125 | headers: { 126 | cookie: request.headers.get('set-cookie') ?? '', 127 | 'content-type': 'application/x-www-form-urlencoded' 128 | } 129 | }) 130 | const body = await response.json() 131 | 132 | assert.equal(response.status, 200) 133 | assert.equal(body.message, 'hello') 134 | }) 135 | }) 136 | 137 | describe('signed cookie - req.headers', () => { 138 | it('should be able to pass through headers csrf-token', async () => { 139 | const { fetch, server } = initApp({ middleware: 'signedCookie' }) 140 | onTestFinished(() => { 141 | server.close() 142 | }) 143 | const request = await fetch('/') 144 | const requestBody = await request.json() 145 | 146 | const response = await fetch('/', { 147 | method: 'post', 148 | headers: { 149 | cookie: request.headers.get('set-cookie') ?? '', 150 | 'csrf-token': requestBody.token 151 | } 152 | }) 153 | const body = await response.json() 154 | 155 | assert.equal(response.status, 200) 156 | assert.equal(body.message, 'hello') 157 | }) 158 | 159 | it('should be able to pass through headers xsrf-token', async () => { 160 | const { fetch, server } = initApp({ middleware: 'signedCookie' }) 161 | onTestFinished(() => { 162 | server.close() 163 | }) 164 | const request = await fetch('/') 165 | const requestBody = await request.json() 166 | 167 | const response = await fetch('/', { 168 | method: 'post', 169 | headers: { 170 | cookie: request.headers.get('set-cookie') ?? '', 171 | 'xsrf-token': requestBody.token 172 | } 173 | }) 174 | const body = await response.json() 175 | 176 | assert.equal(response.status, 200) 177 | assert.equal(body.message, 'hello') 178 | }) 179 | 180 | it('should be able to pass through headers x-csrf-token', async () => { 181 | const { fetch, server } = initApp({ middleware: 'signedCookie' }) 182 | onTestFinished(() => { 183 | server.close() 184 | }) 185 | const request = await fetch('/') 186 | const requestBody = await request.json() 187 | 188 | const response = await fetch('/', { 189 | method: 'post', 190 | headers: { 191 | cookie: request.headers.get('set-cookie') ?? '', 192 | 'x-csrf-token': requestBody.token 193 | } 194 | }) 195 | const body = await response.json() 196 | 197 | assert.equal(response.status, 200) 198 | assert.equal(body.message, 'hello') 199 | }) 200 | 201 | it('should be able to pass through headers x-xsrf-token', async () => { 202 | const { fetch, server } = initApp({ middleware: 'signedCookie' }) 203 | onTestFinished(() => { 204 | server.close() 205 | }) 206 | const request = await fetch('/') 207 | const requestBody = await request.json() 208 | 209 | const response = await fetch('/', { 210 | method: 'post', 211 | headers: { 212 | cookie: request.headers.get('set-cookie') ?? '', 213 | 'x-xsrf-token': requestBody.token 214 | } 215 | }) 216 | const body = await response.json() 217 | 218 | assert.equal(response.status, 200) 219 | assert.equal(body.message, 'hello') 220 | }) 221 | }) 222 | 223 | describe('reusable token', () => { 224 | it('a', async () => { 225 | const { fetch, server } = initApp({ middleware: 'signedCookie' }) 226 | onTestFinished(() => { 227 | server.close() 228 | }) 229 | const request = await fetch('/') 230 | const requestBody = await request.json() 231 | 232 | // response #1 233 | const response1 = await fetch('/', { 234 | method: 'post', 235 | headers: { 236 | cookie: request.headers.get('set-cookie') ?? '', 237 | 'x-xsrf-token': requestBody.token 238 | } 239 | }) 240 | const body1 = await response1.json() 241 | 242 | // response #2 243 | const response2 = await fetch('/', { 244 | method: 'post', 245 | headers: { 246 | cookie: request.headers.get('set-cookie') ?? '', 247 | 'x-xsrf-token': requestBody.token 248 | } 249 | }) 250 | const body2 = await response2.json() 251 | 252 | assert.equal(response1.status, 200) 253 | assert.equal(response2.status, 200) 254 | assert.equal(body1.message, 'hello') 255 | assert.equal(body2.message, 'hello') 256 | }) 257 | }) 258 | -------------------------------------------------------------------------------- /tests/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, it } from 'vitest' 2 | import { timeSafeCompare } from '../src/utils' 3 | 4 | describe('timeSafeCompare() test', () => { 5 | it('should return true for the same value', () => { 6 | const value = 'qsLPe4XsiY2nxXvaD9fWsUXT65psYCoE' 7 | const result = timeSafeCompare(value, value) 8 | assert.equal(result, true) 9 | }) 10 | 11 | it('should return false without failing', () => { 12 | const valueA = 'qsLPe4XsiY2nxXvaD9fWsUXT65psYCoE' 13 | const valueB = 'R9qyjzGA8b6xz25kGQTBph65Na3WW57j' 14 | const result = timeSafeCompare(valueA, valueB) 15 | assert.equal(result, false) 16 | }) 17 | 18 | it('should return false without failing', () => { 19 | const valueA = 'R9qyjzGA8b6xz25kG' 20 | const valueB = 'XsiY2nxXvaD9fWsUXT65psYCoE' 21 | const result = timeSafeCompare(valueA, valueB) 22 | assert.equal(result, false) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "src", 4 | "outDir": "dist", 5 | "declarationDir": "dist", 6 | "target": "ES2019", 7 | "module": "ESNext", 8 | "declaration": true, 9 | "preserveSymlinks": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "downlevelIteration": true, 13 | "noUnusedLocals": true, 14 | "noImplicitAny": false, 15 | "noUnusedParameters": true, 16 | "preserveConstEnums": true, 17 | "skipLibCheck": true, 18 | "moduleResolution": "Node", 19 | "forceConsistentCasingInFileNames": true, 20 | "allowSyntheticDefaultImports": true 21 | }, 22 | "exclude": ["tests", "examples", "dist", "vitest.config.ts"] 23 | } 24 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | environment: 'node', 6 | coverage: { 7 | provider: 'v8', 8 | reporter: ['lcov', 'text', 'json'], 9 | include: ['src'] 10 | } 11 | } 12 | }) 13 | --------------------------------------------------------------------------------