├── .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 |

4 |
5 |
6 | [](https://npmjs.com/package/malibu) [](https://npmjs.com/package/malibu) [](https://github.com/tinyhttp/malibu/actions) [](https://coveralls.io/github/tinyhttp/malibu) [](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 |
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 |
--------------------------------------------------------------------------------