chore(config): update auth, env, i18n, proxy, and dependencies
- Update src/auth.ts auth configuration - Update src/env.mjs environment variable validation - Update src/i18n/request.ts locale handling - Update src/proxy.ts middleware - Update src/next-auth.d.ts type declarations - Update package.json and package-lock.json dependencies
This commit is contained in:
626
package-lock.json
generated
626
package-lock.json
generated
@@ -80,6 +80,7 @@
|
|||||||
"@playwright/test": "^1.58.2",
|
"@playwright/test": "^1.58.2",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
@@ -3301,7 +3302,7 @@
|
|||||||
"version": "1.58.2",
|
"version": "1.58.2",
|
||||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
|
||||||
"integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
|
"integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "1.58.2"
|
"playwright": "1.58.2"
|
||||||
@@ -6596,6 +6597,36 @@
|
|||||||
"url": "https://github.com/sponsors/tannerlinsley"
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@testing-library/dom": {
|
||||||
|
"version": "10.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
|
||||||
|
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/code-frame": "^7.10.4",
|
||||||
|
"@babel/runtime": "^7.12.5",
|
||||||
|
"@types/aria-query": "^5.0.1",
|
||||||
|
"aria-query": "5.3.0",
|
||||||
|
"dom-accessibility-api": "^0.5.9",
|
||||||
|
"lz-string": "^1.5.0",
|
||||||
|
"picocolors": "1.1.1",
|
||||||
|
"pretty-format": "^27.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@testing-library/dom/node_modules/aria-query": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"dequal": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@testing-library/jest-dom": {
|
"node_modules/@testing-library/jest-dom": {
|
||||||
"version": "6.9.1",
|
"version": "6.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz",
|
||||||
@@ -7109,6 +7140,13 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/aria-query": {
|
||||||
|
"version": "5.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||||
|
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/bcryptjs": {
|
"node_modules/@types/bcryptjs": {
|
||||||
"version": "2.4.6",
|
"version": "2.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
|
||||||
@@ -7348,7 +7386,6 @@
|
|||||||
"version": "19.2.7",
|
"version": "19.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
|
||||||
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
@@ -7358,7 +7395,6 @@
|
|||||||
"version": "19.2.3",
|
"version": "19.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.2.0"
|
"@types/react": "^19.2.0"
|
||||||
@@ -9168,7 +9204,6 @@
|
|||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/d3-array": {
|
"node_modules/d3-array": {
|
||||||
@@ -9608,6 +9643,13 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dom-accessibility-api": {
|
||||||
|
"version": "0.5.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||||
|
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "17.2.3",
|
"version": "17.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
|
||||||
@@ -12861,6 +12903,16 @@
|
|||||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lz-string": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"lz-string": "bin/bin.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.21",
|
"version": "0.30.21",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
@@ -14179,6 +14231,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/next-intl/node_modules/@swc/helpers": {
|
||||||
|
"version": "0.5.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz",
|
||||||
|
"integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/next-themes": {
|
"node_modules/next-themes": {
|
||||||
"version": "0.4.6",
|
"version": "0.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
|
||||||
@@ -14760,7 +14823,7 @@
|
|||||||
"version": "1.58.2",
|
"version": "1.58.2",
|
||||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
|
||||||
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
|
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.58.2"
|
"playwright-core": "1.58.2"
|
||||||
@@ -14779,7 +14842,7 @@
|
|||||||
"version": "1.58.2",
|
"version": "1.58.2",
|
||||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
|
||||||
"integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
|
"integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright-core": "cli.js"
|
"playwright-core": "cli.js"
|
||||||
@@ -14980,6 +15043,41 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pretty-format": {
|
||||||
|
"version": "27.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
||||||
|
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^5.0.1",
|
||||||
|
"ansi-styles": "^5.0.0",
|
||||||
|
"react-is": "^17.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pretty-format/node_modules/ansi-styles": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pretty-format/node_modules/react-is": {
|
||||||
|
"version": "17.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
|
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/process-nextick-args": {
|
"node_modules/process-nextick-args": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
@@ -15301,7 +15399,6 @@
|
|||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/react-markdown": {
|
"node_modules/react-markdown": {
|
||||||
@@ -16566,7 +16663,6 @@
|
|||||||
"version": "4.1.18",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
||||||
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss-animate": {
|
"node_modules/tailwindcss-animate": {
|
||||||
@@ -17015,7 +17111,7 @@
|
|||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@@ -17528,6 +17624,474 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/aix-ppc64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"aix"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/android-arm": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/android-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/android-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/darwin-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/darwin-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/freebsd-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/freebsd-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/linux-arm": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/linux-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/linux-ia32": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/linux-loong64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/linux-mips64el": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
|
||||||
|
"cpu": [
|
||||||
|
"mips64el"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/linux-ppc64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/linux-riscv64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/linux-s390x": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/linux-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/netbsd-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/netbsd-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/openbsd-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/openbsd-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/openharmony-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openharmony"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/sunos-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"sunos"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/win32-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/win32-ia32": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/@esbuild/win32-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vitest/node_modules/@vitest/mocker": {
|
"node_modules/vitest/node_modules/@vitest/mocker": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.0.tgz",
|
||||||
@@ -17555,6 +18119,50 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vitest/node_modules/esbuild": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"bin": {
|
||||||
|
"esbuild": "bin/esbuild"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@esbuild/aix-ppc64": "0.27.7",
|
||||||
|
"@esbuild/android-arm": "0.27.7",
|
||||||
|
"@esbuild/android-arm64": "0.27.7",
|
||||||
|
"@esbuild/android-x64": "0.27.7",
|
||||||
|
"@esbuild/darwin-arm64": "0.27.7",
|
||||||
|
"@esbuild/darwin-x64": "0.27.7",
|
||||||
|
"@esbuild/freebsd-arm64": "0.27.7",
|
||||||
|
"@esbuild/freebsd-x64": "0.27.7",
|
||||||
|
"@esbuild/linux-arm": "0.27.7",
|
||||||
|
"@esbuild/linux-arm64": "0.27.7",
|
||||||
|
"@esbuild/linux-ia32": "0.27.7",
|
||||||
|
"@esbuild/linux-loong64": "0.27.7",
|
||||||
|
"@esbuild/linux-mips64el": "0.27.7",
|
||||||
|
"@esbuild/linux-ppc64": "0.27.7",
|
||||||
|
"@esbuild/linux-riscv64": "0.27.7",
|
||||||
|
"@esbuild/linux-s390x": "0.27.7",
|
||||||
|
"@esbuild/linux-x64": "0.27.7",
|
||||||
|
"@esbuild/netbsd-arm64": "0.27.7",
|
||||||
|
"@esbuild/netbsd-x64": "0.27.7",
|
||||||
|
"@esbuild/openbsd-arm64": "0.27.7",
|
||||||
|
"@esbuild/openbsd-x64": "0.27.7",
|
||||||
|
"@esbuild/openharmony-arm64": "0.27.7",
|
||||||
|
"@esbuild/sunos-x64": "0.27.7",
|
||||||
|
"@esbuild/win32-arm64": "0.27.7",
|
||||||
|
"@esbuild/win32-ia32": "0.27.7",
|
||||||
|
"@esbuild/win32-x64": "0.27.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vitest/node_modules/fsevents": {
|
"node_modules/vitest/node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
|
|||||||
@@ -112,6 +112,7 @@
|
|||||||
"@playwright/test": "^1.58.2",
|
"@playwright/test": "^1.58.2",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
|
|||||||
201
src/auth.ts
201
src/auth.ts
@@ -1,23 +1,14 @@
|
|||||||
import { compare } from "bcryptjs"
|
|
||||||
import NextAuth from "next-auth"
|
import NextAuth from "next-auth"
|
||||||
import Credentials from "next-auth/providers/credentials"
|
import Credentials from "next-auth/providers/credentials"
|
||||||
import { eq } from "drizzle-orm"
|
|
||||||
import { resolvePermissions } from "@/shared/lib/permissions"
|
import { resolvePermissions } from "@/shared/lib/permissions"
|
||||||
import { isRole } from "@/shared/types/permissions"
|
import { isRole } from "@/shared/types/permissions"
|
||||||
import { logLoginEvent } from "@/shared/lib/login-logger"
|
import { logLoginEvent } from "@/shared/lib/login-logger"
|
||||||
import {
|
|
||||||
PASSWORD_RULES,
|
|
||||||
isAccountLocked,
|
|
||||||
} from "@/shared/lib/password-policy"
|
|
||||||
import { RATE_LIMIT_RULES, rateLimit, rateLimitKey, resetRateLimit } from "@/shared/lib/rate-limit"
|
|
||||||
import { normalizeBcryptHash } from "@/shared/lib/bcrypt-utils"
|
|
||||||
import { resolveClientIp } from "@/shared/lib/http-utils"
|
|
||||||
import {
|
|
||||||
getOrCreatePasswordSecurity,
|
|
||||||
recordFailedLogin,
|
|
||||||
resetFailedLogin,
|
|
||||||
} from "@/shared/lib/password-security-service"
|
|
||||||
import { normalizeRole, resolvePrimaryRole } from "@/shared/lib/role-utils"
|
import { normalizeRole, resolvePrimaryRole } from "@/shared/lib/role-utils"
|
||||||
|
import {
|
||||||
|
encodePermissionsBitmap,
|
||||||
|
decodePermissionsBitmap,
|
||||||
|
} from "@/shared/lib/permission-bitmap"
|
||||||
|
import { trackAuthEvent } from "@/shared/lib/track-event"
|
||||||
|
|
||||||
export const { handlers, auth, signIn, signOut } = NextAuth({
|
export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||||
trustHost: true,
|
trustHost: true,
|
||||||
@@ -31,126 +22,32 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
|||||||
password: { label: "Password", type: "password" },
|
password: { label: "Password", type: "password" },
|
||||||
totpCode: { label: "2FA Code", type: "text" },
|
totpCode: { label: "2FA Code", type: "text" },
|
||||||
},
|
},
|
||||||
|
// audit-P1-3 重构:authorize 回调原混合 7 类职责(107 行),
|
||||||
|
// 已抽取至 modules/auth/services/login-service.ts 的 authenticateUser 函数。
|
||||||
|
// 此处为薄包装,仅负责动态 import(避免 server-only 模块在模块评估期加载)
|
||||||
|
// 与委托调用。所有登录逻辑(速率限制/账户锁定/密码校验/2FA/角色加载)均在
|
||||||
|
// login-service 中实现,可独立测试。
|
||||||
authorize: async (credentials) => {
|
authorize: async (credentials) => {
|
||||||
const email = String(credentials?.email ?? "").trim().toLowerCase()
|
const { authenticateUser } = await import("@/modules/auth/services/login-service")
|
||||||
const password = String(credentials?.password ?? "")
|
return authenticateUser(credentials)
|
||||||
const totpCode = String(credentials?.totpCode ?? "").trim()
|
|
||||||
if (!email || !password) return null
|
|
||||||
|
|
||||||
// Rate limit by IP + email to slow brute-force attempts
|
|
||||||
const clientIp = await resolveClientIp()
|
|
||||||
const loginLimitKey = rateLimitKey("login", `${clientIp}:${email}`)
|
|
||||||
const limit = rateLimit({
|
|
||||||
key: loginLimitKey,
|
|
||||||
...RATE_LIMIT_RULES.LOGIN,
|
|
||||||
})
|
|
||||||
if (!limit.success) {
|
|
||||||
await logLoginEvent({
|
|
||||||
userEmail: email,
|
|
||||||
action: "signin",
|
|
||||||
status: "failure",
|
|
||||||
errorMessage: "Rate limit exceeded",
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const [{ db }, { users, roles, usersToRoles, passwordSecurity }] = await Promise.all([
|
|
||||||
import("@/shared/db"),
|
|
||||||
import("@/shared/db/schema"),
|
|
||||||
])
|
|
||||||
|
|
||||||
const user = await db.query.users.findFirst({
|
|
||||||
where: eq(users.email, email),
|
|
||||||
})
|
|
||||||
if (!user) return null
|
|
||||||
|
|
||||||
// Account lockout check
|
|
||||||
const security = await getOrCreatePasswordSecurity(db, passwordSecurity, user.id)
|
|
||||||
const lastFailedAt = security.lockedUntil
|
|
||||||
? new Date(security.lockedUntil.getTime() - PASSWORD_RULES.lockoutDurationMinutes * 60 * 1000)
|
|
||||||
: null
|
|
||||||
if (isAccountLocked(security.failedLoginAttempts, lastFailedAt)) {
|
|
||||||
await logLoginEvent({
|
|
||||||
userId: user.id,
|
|
||||||
userEmail: email,
|
|
||||||
action: "signin",
|
|
||||||
status: "failure",
|
|
||||||
errorMessage: "Account locked",
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const storedPassword = user.password ?? null
|
|
||||||
if (!storedPassword) return null
|
|
||||||
const normalizedPassword = normalizeBcryptHash(storedPassword)
|
|
||||||
if (!normalizedPassword.startsWith("$2")) return null
|
|
||||||
const ok = await compare(password, normalizedPassword)
|
|
||||||
|
|
||||||
if (!ok) {
|
|
||||||
await recordFailedLogin(db, passwordSecurity, user.id)
|
|
||||||
await logLoginEvent({
|
|
||||||
userId: user.id,
|
|
||||||
userEmail: email,
|
|
||||||
action: "signin",
|
|
||||||
status: "failure",
|
|
||||||
errorMessage: "Invalid credentials",
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Successful login: reset counters and rate limit
|
|
||||||
await resetFailedLogin(db, passwordSecurity, user.id)
|
|
||||||
resetRateLimit(loginLimitKey)
|
|
||||||
|
|
||||||
// 2FA verification (if user has enabled it)
|
|
||||||
const { verifyTwoFactorForLogin } = await import("@/modules/settings/actions-security")
|
|
||||||
const twoFactorResult = await verifyTwoFactorForLogin({
|
|
||||||
userId: user.id,
|
|
||||||
token: totpCode || undefined,
|
|
||||||
})
|
|
||||||
if (twoFactorResult.required && !twoFactorResult.valid) {
|
|
||||||
await logLoginEvent({
|
|
||||||
userId: user.id,
|
|
||||||
userEmail: email,
|
|
||||||
action: "signin",
|
|
||||||
status: "failure",
|
|
||||||
errorMessage: totpCode
|
|
||||||
? "Invalid 2FA code"
|
|
||||||
: "2FA required but not provided",
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const roleRows = await db
|
|
||||||
.select({ name: roles.name })
|
|
||||||
.from(usersToRoles)
|
|
||||||
.innerJoin(roles, eq(usersToRoles.roleId, roles.id))
|
|
||||||
.where(eq(usersToRoles.userId, user.id))
|
|
||||||
|
|
||||||
const roleNames = roleRows.map((r) => r.name)
|
|
||||||
const resolvedRole = resolvePrimaryRole(roleNames)
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: user.id,
|
|
||||||
name: user.name ?? undefined,
|
|
||||||
email: user.email,
|
|
||||||
role: resolvedRole,
|
|
||||||
roles: roleNames,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
callbacks: {
|
callbacks: {
|
||||||
jwt: async ({ token, user }) => {
|
jwt: async ({ token, user }) => {
|
||||||
if (user) {
|
// P0-5 修复:User 接口已在 next-auth.d.ts 中扩展了 role/roles 字段,
|
||||||
const u = user as { id: string; role?: string; roles?: string[]; name?: string }
|
// 无需 `as` 断言即可安全访问。
|
||||||
token.id = u.id
|
if (user && user.id) {
|
||||||
token.role = normalizeRole(u.role)
|
token.id = user.id
|
||||||
token.name = u.name ?? undefined
|
token.role = normalizeRole(user.role)
|
||||||
|
token.name = user.name ?? undefined
|
||||||
// Store all roles (not just primary) and resolved permissions
|
// Store all roles (not just primary) and resolved permissions
|
||||||
const allRoles = (u.roles ?? [u.role ?? "student"]).filter(isRole)
|
const allRoles = (user.roles ?? [user.role ?? "student"]).filter(isRole)
|
||||||
token.roles = allRoles
|
token.roles = allRoles
|
||||||
token.permissions = resolvePermissions(allRoles)
|
// audit-P1-7:JWT 中仅存位图(~14 字符),不存数组(~1.1KB)。
|
||||||
|
// Session callback 解码还原为 Permission[] 供业务使用。
|
||||||
|
const permissions = await resolvePermissions(allRoles)
|
||||||
|
token.permissionsBitmap = encodePermissionsBitmap(permissions)
|
||||||
// Onboarding status is resolved from DB below; default to false on first login
|
// Onboarding status is resolved from DB below; default to false on first login
|
||||||
token.onboarded = false
|
token.onboarded = false
|
||||||
}
|
}
|
||||||
@@ -181,7 +78,9 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
|||||||
token.role = resolvePrimaryRole(allRoles)
|
token.role = resolvePrimaryRole(allRoles)
|
||||||
token.name = fresh.name ?? token.name
|
token.name = fresh.name ?? token.name
|
||||||
token.roles = allRoles
|
token.roles = allRoles
|
||||||
token.permissions = resolvePermissions(allRoles)
|
// audit-P1-7:刷新位图,不再写 token.permissions 数组
|
||||||
|
const permissions = await resolvePermissions(allRoles)
|
||||||
|
token.permissionsBitmap = encodePermissionsBitmap(permissions)
|
||||||
token.onboarded = Boolean(fresh.onboardedAt)
|
token.onboarded = Boolean(fresh.onboardedAt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,11 +88,15 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
|||||||
return token
|
return token
|
||||||
},
|
},
|
||||||
session: async ({ session, token }) => {
|
session: async ({ session, token }) => {
|
||||||
|
// audit-P1-7:session.user.permissions 由位图解码得到,
|
||||||
|
// 业务代码(getAuthContext / usePermission)无需感知位图存在。
|
||||||
if (session.user) {
|
if (session.user) {
|
||||||
session.user.id = String(token.id ?? "")
|
session.user.id = String(token.id ?? "")
|
||||||
session.user.role = normalizeRole(token.role)
|
session.user.role = normalizeRole(token.role)
|
||||||
session.user.roles = (token.roles ?? []).filter(isRole)
|
session.user.roles = (token.roles ?? []).filter(isRole)
|
||||||
session.user.permissions = (token.permissions ?? []) as typeof token.permissions
|
session.user.permissions = decodePermissionsBitmap(
|
||||||
|
token.permissionsBitmap ?? "",
|
||||||
|
)
|
||||||
session.user.onboarded = Boolean(token.onboarded)
|
session.user.onboarded = Boolean(token.onboarded)
|
||||||
if (typeof token.name === "string") {
|
if (typeof token.name === "string") {
|
||||||
session.user.name = token.name
|
session.user.name = token.name
|
||||||
@@ -210,17 +113,34 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
|||||||
action: "signin",
|
action: "signin",
|
||||||
status: "success",
|
status: "success",
|
||||||
})
|
})
|
||||||
|
// audit-P1-9:登录成功埋点(用于登录成功率、异常登录地理/设备告警)
|
||||||
|
// 非阻塞:trackEvent 内部已吞掉异常
|
||||||
|
await trackAuthEvent("auth.signin_success", {
|
||||||
|
userId: user.id,
|
||||||
|
properties: {
|
||||||
|
// user.role 是单一主角色,user.roles 是全部角色(可能未填充)
|
||||||
|
roles: (user.roles ?? (user.role ? [user.role] : [])),
|
||||||
|
},
|
||||||
|
})
|
||||||
},
|
},
|
||||||
async signOut(message) {
|
async signOut(message) {
|
||||||
// NextAuth v5 signOut event receives the session/token info
|
// P0-5 修复:使用 `in` 操作符对联合类型进行类型收窄,替代 `as` 断言。
|
||||||
const userId =
|
// NextAuth v5 signOut 事件 message 为联合类型:
|
||||||
(message as { userId?: string })?.userId ??
|
// { session: ... } | { token: JWT }
|
||||||
(message as { token?: { id?: string } })?.token?.id ??
|
// JWT 策略下实际收到 { token: JWT },其中 JWT 已在 next-auth.d.ts 扩展。
|
||||||
""
|
let userId = ""
|
||||||
const userEmail =
|
let userEmail = ""
|
||||||
(message as { token?: { email?: string } })?.token?.email ??
|
let roles: string[] = []
|
||||||
(message as { session?: { user?: { email?: string } } })?.session?.user?.email ??
|
|
||||||
""
|
if ("token" in message) {
|
||||||
|
const token = message.token
|
||||||
|
if (token) {
|
||||||
|
userId = String(token.id ?? "")
|
||||||
|
userEmail = String(token.email ?? "")
|
||||||
|
roles = (token.roles ?? []).filter(isRole)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (userEmail) {
|
if (userEmail) {
|
||||||
await logLoginEvent({
|
await logLoginEvent({
|
||||||
userId: userId || undefined,
|
userId: userId || undefined,
|
||||||
@@ -229,6 +149,13 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
|||||||
status: "success",
|
status: "success",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// audit-P1-9:登出埋点
|
||||||
|
if (userId) {
|
||||||
|
await trackAuthEvent("auth.signout", {
|
||||||
|
userId,
|
||||||
|
properties: { roles },
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
11
src/env.mjs
11
src/env.mjs
@@ -10,6 +10,13 @@ export const env = createEnv({
|
|||||||
AI_API_KEY: z.string().min(1).optional(),
|
AI_API_KEY: z.string().min(1).optional(),
|
||||||
AI_BASE_URL: z.string().url().optional(),
|
AI_BASE_URL: z.string().url().optional(),
|
||||||
AI_MODEL: z.string().min(1).optional(),
|
AI_MODEL: z.string().min(1).optional(),
|
||||||
|
// Rate limit driver: "memory" (default, single-instance) or "redis" (distributed).
|
||||||
|
RATE_LIMIT_DRIVER: z.enum(["memory", "redis"]).default("memory"),
|
||||||
|
// Upstash Redis REST credentials (only required when RATE_LIMIT_DRIVER=redis).
|
||||||
|
UPSTASH_REDIS_REST_URL: z.string().url().optional(),
|
||||||
|
UPSTASH_REDIS_REST_TOKEN: z.string().min(1).optional(),
|
||||||
|
// audit-P2-5: Cron job bearer token for /api/cron/* endpoints.
|
||||||
|
CRON_SECRET: z.string().min(1).optional(),
|
||||||
},
|
},
|
||||||
client: {
|
client: {
|
||||||
NEXT_PUBLIC_APP_URL: z.string().url().optional(),
|
NEXT_PUBLIC_APP_URL: z.string().url().optional(),
|
||||||
@@ -23,6 +30,10 @@ export const env = createEnv({
|
|||||||
AI_API_KEY: process.env.AI_API_KEY,
|
AI_API_KEY: process.env.AI_API_KEY,
|
||||||
AI_BASE_URL: process.env.AI_BASE_URL,
|
AI_BASE_URL: process.env.AI_BASE_URL,
|
||||||
AI_MODEL: process.env.AI_MODEL,
|
AI_MODEL: process.env.AI_MODEL,
|
||||||
|
RATE_LIMIT_DRIVER: process.env.RATE_LIMIT_DRIVER,
|
||||||
|
UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL,
|
||||||
|
UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN,
|
||||||
|
CRON_SECRET: process.env.CRON_SECRET,
|
||||||
},
|
},
|
||||||
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
|
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
|
||||||
emptyStringAsUndefined: true,
|
emptyStringAsUndefined: true,
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ export default getRequestConfig(async () => {
|
|||||||
users,
|
users,
|
||||||
leave,
|
leave,
|
||||||
nav,
|
nav,
|
||||||
|
rbac,
|
||||||
|
questions,
|
||||||
|
parent,
|
||||||
|
invitationCodes,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
import(`@/shared/i18n/messages/${locale}/common.json`),
|
import(`@/shared/i18n/messages/${locale}/common.json`),
|
||||||
import(`@/shared/i18n/messages/${locale}/auth.json`),
|
import(`@/shared/i18n/messages/${locale}/auth.json`),
|
||||||
@@ -81,6 +85,10 @@ export default getRequestConfig(async () => {
|
|||||||
import(`@/shared/i18n/messages/${locale}/users.json`),
|
import(`@/shared/i18n/messages/${locale}/users.json`),
|
||||||
import(`@/shared/i18n/messages/${locale}/leave.json`),
|
import(`@/shared/i18n/messages/${locale}/leave.json`),
|
||||||
import(`@/shared/i18n/messages/${locale}/nav.json`),
|
import(`@/shared/i18n/messages/${locale}/nav.json`),
|
||||||
|
import(`@/shared/i18n/messages/${locale}/rbac.json`),
|
||||||
|
import(`@/shared/i18n/messages/${locale}/questions.json`),
|
||||||
|
import(`@/shared/i18n/messages/${locale}/parent.json`),
|
||||||
|
import(`@/shared/i18n/messages/${locale}/invitation-codes.json`),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -116,6 +124,10 @@ export default getRequestConfig(async () => {
|
|||||||
users: users.default,
|
users: users.default,
|
||||||
leave: leave.default,
|
leave: leave.default,
|
||||||
nav: nav.default,
|
nav: nav.default,
|
||||||
|
rbac: rbac.default,
|
||||||
|
questions: questions.default,
|
||||||
|
parent: parent.default,
|
||||||
|
invitationCodes: invitationCodes.default,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
27
src/next-auth.d.ts
vendored
27
src/next-auth.d.ts
vendored
@@ -2,6 +2,12 @@ import type { DefaultSession } from "next-auth"
|
|||||||
import type { Permission, Role } from "@/shared/types/permissions"
|
import type { Permission, Role } from "@/shared/types/permissions"
|
||||||
|
|
||||||
declare module "next-auth" {
|
declare module "next-auth" {
|
||||||
|
interface User {
|
||||||
|
/** 主要角色(向后兼容) */
|
||||||
|
role?: string
|
||||||
|
/** 用户拥有的全部角色名 */
|
||||||
|
roles?: string[]
|
||||||
|
}
|
||||||
interface Session {
|
interface Session {
|
||||||
user: DefaultSession["user"] & {
|
user: DefaultSession["user"] & {
|
||||||
id: string
|
id: string
|
||||||
@@ -18,7 +24,26 @@ declare module "next-auth/jwt" {
|
|||||||
id: string
|
id: string
|
||||||
role: string // kept for backward compatibility
|
role: string // kept for backward compatibility
|
||||||
roles: Role[]
|
roles: Role[]
|
||||||
permissions: Permission[]
|
/**
|
||||||
|
* 权限位图(base36 字符串,audit-P1-7)。
|
||||||
|
*
|
||||||
|
* 由 `encodePermissionsBitmap()` 编码生成,约 14 字符,
|
||||||
|
* 相比 `permissions: Permission[]` JSON 数组(~1.1KB)显著减小 JWT 体积。
|
||||||
|
*
|
||||||
|
* proxy.ts 边缘运行时直接通过 `hasPermissionInBitmap()` 检查权限,
|
||||||
|
* 无需解码为完整数组。
|
||||||
|
*
|
||||||
|
* Session callback 中通过 `decodePermissionsBitmap()` 还原为 Permission[]
|
||||||
|
* 供服务端 `getAuthContext()` 和客户端 `usePermission()` 使用。
|
||||||
|
*/
|
||||||
|
permissionsBitmap: string
|
||||||
|
/**
|
||||||
|
* @deprecated 仅用于向后兼容,新代码应使用 `permissionsBitmap`。
|
||||||
|
* Session callback 已自动从 bitmap 解码并填充到 `session.user.permissions`,
|
||||||
|
* 业务代码无需直接读取 `token.permissions`。
|
||||||
|
* 在 JWT 中保留此字段会导致 token 体积膨胀,未来版本将移除。
|
||||||
|
*/
|
||||||
|
permissions?: Permission[]
|
||||||
onboarded: boolean
|
onboarded: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
76
src/proxy.ts
76
src/proxy.ts
@@ -2,51 +2,15 @@ import { NextResponse } from "next/server"
|
|||||||
import type { NextRequest } from "next/server"
|
import type { NextRequest } from "next/server"
|
||||||
import { getToken } from "next-auth/jwt"
|
import { getToken } from "next-auth/jwt"
|
||||||
|
|
||||||
import { Permissions } from "@/shared/types/permissions"
|
import { type Permission } from "@/shared/types/permissions"
|
||||||
|
import { resolveDefaultPath } from "@/shared/lib/route-resolver"
|
||||||
// Route prefix → minimum required permission
|
import { hasPermissionInBitmap } from "@/shared/lib/permission-bitmap"
|
||||||
// Note: /admin/announcements is covered by /admin prefix (requires school:manage)
|
import {
|
||||||
// Note: /announcements is accessible to all authenticated users (no permission entry needed)
|
SPECIFIC_ROUTE_PERMISSIONS,
|
||||||
// P0 修复:原先 /teacher 和 /parent 都使用 EXAM_READ,但 student/parent 也有 EXAM_READ,
|
ROUTE_PREFIX_PERMISSIONS,
|
||||||
// 导致跨角色访问漏洞(学生可访问 /teacher/*,教师可访问 /parent/*)。
|
DASHBOARD_ROUTE_PERMISSIONS,
|
||||||
// 改为使用各角色独有的权限点,确保跨角色访问被拒绝。
|
API_ROUTE_PERMISSIONS,
|
||||||
const ROUTE_PERMISSIONS: Record<string, string> = {
|
} from "@/shared/lib/route-permissions"
|
||||||
"/admin": Permissions.SCHOOL_MANAGE,
|
|
||||||
"/teacher": Permissions.EXAM_CREATE,
|
|
||||||
"/student": Permissions.HOMEWORK_SUBMIT,
|
|
||||||
"/parent": Permissions.DASHBOARD_PARENT_READ,
|
|
||||||
"/management": Permissions.GRADE_MANAGE,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 仪表盘路由的细粒度权限(覆盖 ROUTE_PERMISSIONS 的前缀匹配)
|
|
||||||
// 防止拥有 EXAM_READ 的学生/家长访问 /teacher/dashboard 等
|
|
||||||
const DASHBOARD_ROUTE_PERMISSIONS: Record<string, string> = {
|
|
||||||
"/admin/dashboard": Permissions.DASHBOARD_ADMIN_READ,
|
|
||||||
"/teacher/dashboard": Permissions.DASHBOARD_TEACHER_READ,
|
|
||||||
"/student/dashboard": Permissions.DASHBOARD_STUDENT_READ,
|
|
||||||
"/parent/dashboard": Permissions.DASHBOARD_PARENT_READ,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 精确路由权限(优先级最高,覆盖 ROUTE_PERMISSIONS 的前缀匹配)
|
|
||||||
// 用于将 /admin/* 下的特定页面开放给非管理员角色
|
|
||||||
// V3.1:/admin/ai-settings 对所有 AI_CHAT 用户开放(管理自己的 private provider)
|
|
||||||
const SPECIFIC_ROUTE_PERMISSIONS: Record<string, string> = {
|
|
||||||
"/admin/ai-settings": Permissions.AI_CHAT,
|
|
||||||
}
|
|
||||||
|
|
||||||
// API route prefix → required permission
|
|
||||||
const API_PERMISSIONS: Record<string, string> = {
|
|
||||||
"/api/ai/chat": Permissions.AI_CHAT,
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveDefaultPath(roles: string[]): string {
|
|
||||||
if (roles.includes("admin")) return "/admin/dashboard"
|
|
||||||
if (roles.includes("grade_head") || roles.includes("teaching_head")) return "/teacher/dashboard"
|
|
||||||
if (roles.includes("teacher")) return "/teacher/dashboard"
|
|
||||||
if (roles.includes("student")) return "/student/dashboard"
|
|
||||||
if (roles.includes("parent")) return "/parent/dashboard"
|
|
||||||
return "/dashboard"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next.js 16 renamed `middleware` to `proxy`.
|
// Next.js 16 renamed `middleware` to `proxy`.
|
||||||
// See: https://nextjs.org/docs/messages/middleware-to-proxy
|
// See: https://nextjs.org/docs/messages/middleware-to-proxy
|
||||||
@@ -92,13 +56,21 @@ export async function proxy(request: NextRequest) {
|
|||||||
return NextResponse.redirect(new URL(defaultPath, request.url))
|
return NextResponse.redirect(new URL(defaultPath, request.url))
|
||||||
}
|
}
|
||||||
|
|
||||||
const permissions: string[] = (token.permissions as string[]) ?? []
|
const permissionsBitmap: string = (token.permissionsBitmap as string) ?? ""
|
||||||
const roles: string[] = (token.roles as string[]) ?? []
|
const roles: string[] = (token.roles as string[]) ?? []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* audit-P1-7:使用位图检查权限,避免每次路由检查都解码完整权限数组。
|
||||||
|
* proxy.ts 在 edge runtime 运行,每个请求都经过这里,性能至关重要。
|
||||||
|
*/
|
||||||
|
function hasPermission(requiredPerm: Permission): boolean {
|
||||||
|
return hasPermissionInBitmap(permissionsBitmap, requiredPerm)
|
||||||
|
}
|
||||||
|
|
||||||
// Check API route permissions
|
// Check API route permissions
|
||||||
for (const [prefix, requiredPerm] of Object.entries(API_PERMISSIONS)) {
|
for (const [prefix, requiredPerm] of Object.entries(API_ROUTE_PERMISSIONS)) {
|
||||||
if (pathname.startsWith(prefix)) {
|
if (pathname.startsWith(prefix)) {
|
||||||
if (!permissions.includes(requiredPerm)) {
|
if (!hasPermission(requiredPerm)) {
|
||||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 })
|
return NextResponse.json({ error: "Forbidden" }, { status: 403 })
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@@ -109,7 +81,7 @@ export async function proxy(request: NextRequest) {
|
|||||||
// 优先级 1:精确路由权限(覆盖前缀匹配,用于将 /admin/* 下特定页面开放给非管理员)
|
// 优先级 1:精确路由权限(覆盖前缀匹配,用于将 /admin/* 下特定页面开放给非管理员)
|
||||||
if (Object.prototype.hasOwnProperty.call(SPECIFIC_ROUTE_PERMISSIONS, pathname)) {
|
if (Object.prototype.hasOwnProperty.call(SPECIFIC_ROUTE_PERMISSIONS, pathname)) {
|
||||||
const requiredPerm = SPECIFIC_ROUTE_PERMISSIONS[pathname]
|
const requiredPerm = SPECIFIC_ROUTE_PERMISSIONS[pathname]
|
||||||
if (!permissions.includes(requiredPerm)) {
|
if (!hasPermission(requiredPerm)) {
|
||||||
const defaultPath = resolveDefaultPath(roles)
|
const defaultPath = resolveDefaultPath(roles)
|
||||||
const redirectUrl = new URL(defaultPath, request.url)
|
const redirectUrl = new URL(defaultPath, request.url)
|
||||||
redirectUrl.searchParams.set("from", pathname)
|
redirectUrl.searchParams.set("from", pathname)
|
||||||
@@ -122,7 +94,7 @@ export async function proxy(request: NextRequest) {
|
|||||||
// 优先级 2:仪表盘路由的细粒度权限(防止跨角色访问仪表盘)
|
// 优先级 2:仪表盘路由的细粒度权限(防止跨角色访问仪表盘)
|
||||||
if (Object.prototype.hasOwnProperty.call(DASHBOARD_ROUTE_PERMISSIONS, pathname)) {
|
if (Object.prototype.hasOwnProperty.call(DASHBOARD_ROUTE_PERMISSIONS, pathname)) {
|
||||||
const requiredPerm = DASHBOARD_ROUTE_PERMISSIONS[pathname]
|
const requiredPerm = DASHBOARD_ROUTE_PERMISSIONS[pathname]
|
||||||
if (!permissions.includes(requiredPerm)) {
|
if (!hasPermission(requiredPerm)) {
|
||||||
const defaultPath = resolveDefaultPath(roles)
|
const defaultPath = resolveDefaultPath(roles)
|
||||||
const redirectUrl = new URL(defaultPath, request.url)
|
const redirectUrl = new URL(defaultPath, request.url)
|
||||||
redirectUrl.searchParams.set("from", pathname)
|
redirectUrl.searchParams.set("from", pathname)
|
||||||
@@ -132,9 +104,9 @@ export async function proxy(request: NextRequest) {
|
|||||||
return NextResponse.next()
|
return NextResponse.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [prefix, requiredPerm] of Object.entries(ROUTE_PERMISSIONS)) {
|
for (const [prefix, requiredPerm] of Object.entries(ROUTE_PREFIX_PERMISSIONS)) {
|
||||||
if (pathname.startsWith(prefix)) {
|
if (pathname.startsWith(prefix)) {
|
||||||
if (!permissions.includes(requiredPerm)) {
|
if (!hasPermission(requiredPerm)) {
|
||||||
const defaultPath = resolveDefaultPath(roles)
|
const defaultPath = resolveDefaultPath(roles)
|
||||||
// Carry original path + reason in URL so the target page can explain
|
// Carry original path + reason in URL so the target page can explain
|
||||||
// why the user was redirected (Web Interface Guidelines: URL reflects state).
|
// why the user was redirected (Web Interface Guidelines: URL reflects state).
|
||||||
|
|||||||
Reference in New Issue
Block a user