fix: attach CSRF token to state-changing requests
ci / docker (push) Successful in 5m20s

This commit is contained in:
2026-09-05 17:33:30 +08:00
parent 0dff033a7f
commit 799b522f9b
7 changed files with 391 additions and 5 deletions
+1 -1
View File
@@ -1 +1 @@
{"name":"dodo-frontend","private":true,"version":"0.1.0","type":"module","packageManager":"[email protected]","scripts":{"dev":"vite --host 0.0.0.0","build":"vue-tsc -b && vite build","test":"vitest run"},"dependencies":{"@fullcalendar/core":"^6.1.21","@fullcalendar/daygrid":"^6.1.21","@fullcalendar/interaction":"^6.1.21","@fullcalendar/vue3":"^6.1.21","@vitejs/plugin-vue":"latest","class-variance-authority":"latest","clsx":"latest","lucide-vue-next":"^0.468.0","reka-ui":"latest","tailwind-merge":"latest","vue":"latest","vue-router":"latest"},"devDependencies":{"@tailwindcss/vite":"latest","@types/node":"latest","tailwindcss":"latest","typescript":"^5.7.2","vite":"latest","vitest":"latest","vue-tsc":"latest"},"pnpm":{"onlyBuiltDependencies":["vue-demi"]}}
{"name":"dodo-frontend","private":true,"version":"0.1.0","type":"module","packageManager":"[email protected]","scripts":{"dev":"vite --host 0.0.0.0","build":"vue-tsc -b && vite build","test":"vitest run"},"dependencies":{"@fullcalendar/core":"^6.1.21","@fullcalendar/daygrid":"^6.1.21","@fullcalendar/interaction":"^6.1.21","@fullcalendar/vue3":"^6.1.21","@vitejs/plugin-vue":"latest","class-variance-authority":"latest","clsx":"latest","lucide-vue-next":"^0.468.0","reka-ui":"latest","tailwind-merge":"latest","vue":"latest","vue-router":"latest"},"devDependencies":{"@tailwindcss/vite":"latest","@types/node":"latest","jsdom":"^30.0.1","tailwindcss":"latest","typescript":"^5.7.2","vite":"latest","vitest":"latest","vue-tsc":"latest"},"pnpm":{"onlyBuiltDependencies":["vue-demi"]}}
+329 -2
View File
@@ -51,6 +51,9 @@ importers:
'@types/node':
specifier: latest
version: 26.4.1
jsdom:
specifier: ^30.0.1
version: 30.0.1
tailwindcss:
specifier: latest
version: 4.3.3
@@ -62,13 +65,21 @@ importers:
version: 8.2.2(@types/[email protected])([email protected])
vitest:
specifier: latest
version: 5.0.0(@types/[email protected])([email protected](@types/[email protected])([email protected]))
version: 5.0.0(@types/[email protected])([email protected])([email protected](@types/[email protected])([email protected]))
vue-tsc:
specifier: latest
version: 3.3.11([email protected])
packages:
'@asamuzakjp/[email protected]':
resolution: {integrity: sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==}
engines: {node: ^22.13.0 || >=24.0.0}
'@asamuzakjp/[email protected]':
resolution: {integrity: sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==}
engines: {node: ^22.13.0 || >=24.0.0}
'@babel/[email protected]':
resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
engines: {node: '>=6.9.0'}
@@ -86,6 +97,55 @@ packages:
resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==}
engines: {node: '>=6.9.0'}
'@bramus/[email protected]':
resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==}
hasBin: true
'@csstools/[email protected]':
resolution: {integrity: sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==}
engines: {node: '>=20.19.0'}
'@csstools/[email protected]':
resolution: {integrity: sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==}
engines: {node: '>=20.19.0'}
peerDependencies:
'@csstools/css-parser-algorithms': ^4.0.0
'@csstools/css-tokenizer': ^4.0.0
'@csstools/[email protected]':
resolution: {integrity: sha512-3QKjR/vxyjcSXBLgb6lP0S3MGdvwbmqSsvLPbYdVORqPDc8FX1HAJ0Spk38bxaRXgvENTA47tlhhbb5Z2e8hEg==}
engines: {node: '>=20.19.0'}
peerDependencies:
'@csstools/css-parser-algorithms': ^4.0.0
'@csstools/css-tokenizer': ^4.0.0
'@csstools/[email protected]':
resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==}
engines: {node: '>=20.19.0'}
peerDependencies:
'@csstools/css-tokenizer': ^4.0.0
'@csstools/[email protected]':
resolution: {integrity: sha512-3vLQK+dXxhBMR2Wx99PTCifE+vHtW2ndZWyla8yK813ev6oGhyn8Lja8jCyGAWTJ+LEYZK7EVtJxrDj8ztevJw==}
peerDependencies:
css-tree: ^3.2.1
peerDependenciesMeta:
css-tree:
optional: true
'@csstools/[email protected]':
resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==}
engines: {node: '>=20.19.0'}
'@exodus/[email protected]':
resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
peerDependencies:
'@noble/hashes': ^1.8.0 || ^2.0.0
peerDependenciesMeta:
'@noble/hashes':
optional: true
'@floating-ui/[email protected]':
resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==}
@@ -476,6 +536,9 @@ packages:
resolution: {integrity: sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==}
engines: {node: '>=20.19.0'}
[email protected]:
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
[email protected]:
resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}
@@ -503,9 +566,20 @@ packages:
[email protected]:
resolution: {integrity: sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==}
[email protected]:
resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
[email protected]:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
[email protected]:
resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
[email protected]:
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
[email protected]:
resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==}
@@ -521,6 +595,10 @@ packages:
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
engines: {node: '>=0.12'}
[email protected]:
resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==}
engines: {node: '>=20.19.0'}
[email protected]:
resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==}
@@ -557,10 +635,26 @@ packages:
[email protected]:
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
[email protected]:
resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
[email protected]:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
[email protected]:
resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
hasBin: true
[email protected]:
resolution: {integrity: sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==}
engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0}
peerDependencies:
canvas: ^3.2.3
peerDependenciesMeta:
canvas:
optional: true
[email protected]:
resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
engines: {node: '>= 12.0.0'}
@@ -713,6 +807,10 @@ packages:
resolution: {integrity: sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==}
engines: {node: '>=14'}
[email protected]:
resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==}
engines: {node: 20 || >=22}
[email protected]:
resolution: {integrity: sha512-quV/6T8YB1XK0VOEnebg3Byd8Rsan5/m95cvjnuHV4vcS3qEnLAybkrSh0hk3ppavx+V7R1PjNW+mGDvcBdz4A==}
peerDependencies:
@@ -728,6 +826,9 @@ packages:
[email protected]:
resolution: {integrity: sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g==}
[email protected]:
resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
[email protected]:
resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
@@ -749,6 +850,9 @@ packages:
[email protected]:
resolution: {integrity: sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==}
[email protected]:
resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==}
[email protected]:
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
@@ -778,6 +882,10 @@ packages:
[email protected]:
resolution: {integrity: sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==}
[email protected]:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
[email protected]:
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
@@ -790,11 +898,19 @@ packages:
peerDependencies:
vue: '>= 3.4.0'
[email protected]:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
[email protected]:
resolution: {integrity: sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
[email protected]:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'}
[email protected]:
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
@@ -811,6 +927,9 @@ packages:
[email protected]:
resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==}
[email protected]:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
[email protected]:
resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==}
@@ -833,6 +952,21 @@ packages:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'}
[email protected]:
resolution: {integrity: sha512-CW3WN2rIIE/Of21mulhgnGOwoDyEFNygyIBOONSdyAuSATgMMUCpLeUlB+E8sAwA5xRV9hYPl+kyZ9citHCaKg==}
[email protected]:
resolution: {integrity: sha512-aBiNayCfTQxuIJBm06M+xR14cYaYlDlSXZbgsnKzKNxDKUVq7KFwTjwBSsb7m9Y5xO8WfPnBc63WaYFMTGlvqw==}
hasBin: true
[email protected]:
resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==}
engines: {node: '>=16'}
[email protected]:
resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==}
engines: {node: '>=20'}
[email protected]:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
@@ -847,6 +981,10 @@ packages:
[email protected]:
resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==}
[email protected]:
resolution: {integrity: sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==}
engines: {node: '>=22.19.0'}
[email protected]:
resolution: {integrity: sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==}
engines: {node: '>=20.19.0'}
@@ -1014,16 +1152,58 @@ packages:
typescript:
optional: true
[email protected]:
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
engines: {node: '>=18'}
[email protected]:
resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==}
engines: {node: '>=20'}
[email protected]:
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
[email protected]:
resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==}
engines: {node: '>=20'}
[email protected]:
resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
[email protected]:
resolution: {integrity: sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==}
engines: {node: ^22.14.0 || >=24.0.0}
[email protected]:
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
engines: {node: '>=8'}
hasBin: true
[email protected]:
resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
engines: {node: '>=18'}
[email protected]:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
snapshots:
'@asamuzakjp/[email protected]':
dependencies:
'@csstools/css-calc': 3.3.0(@csstools/[email protected](@csstools/[email protected]))(@csstools/[email protected])
'@csstools/css-color-parser': 4.2.2(@csstools/[email protected](@csstools/[email protected]))(@csstools/[email protected])
'@csstools/css-parser-algorithms': 4.0.0(@csstools/[email protected])
'@csstools/css-tokenizer': 4.0.0
lru-cache: 11.5.2
'@asamuzakjp/[email protected]':
dependencies:
bidi-js: 1.0.3
css-tree: 3.2.1
is-potential-custom-element-name: 1.0.1
lru-cache: 11.5.2
'@babel/[email protected]': {}
'@babel/[email protected]': {}
@@ -1037,6 +1217,36 @@ snapshots:
'@babel/helper-string-parser': 7.29.7
'@babel/helper-validator-identifier': 7.29.7
'@bramus/[email protected]':
dependencies:
css-tree: 3.2.1
'@csstools/[email protected]': {}
'@csstools/[email protected](@csstools/[email protected](@csstools/[email protected]))(@csstools/[email protected])':
dependencies:
'@csstools/css-parser-algorithms': 4.0.0(@csstools/[email protected])
'@csstools/css-tokenizer': 4.0.0
'@csstools/[email protected](@csstools/[email protected](@csstools/[email protected]))(@csstools/[email protected])':
dependencies:
'@csstools/color-helpers': 6.1.1
'@csstools/css-calc': 3.3.0(@csstools/[email protected](@csstools/[email protected]))(@csstools/[email protected])
'@csstools/css-parser-algorithms': 4.0.0(@csstools/[email protected])
'@csstools/css-tokenizer': 4.0.0
'@csstools/[email protected](@csstools/[email protected])':
dependencies:
'@csstools/css-tokenizer': 4.0.0
'@csstools/[email protected]([email protected])':
optionalDependencies:
css-tree: 3.2.1
'@csstools/[email protected]': {}
'@exodus/[email protected]': {}
'@floating-ui/[email protected]':
dependencies:
'@floating-ui/utils': 0.2.12
@@ -1394,6 +1604,10 @@ snapshots:
'@babel/types': 7.29.8
ast-kit: 2.2.0
[email protected]:
dependencies:
require-from-string: 2.0.2
[email protected]: {}
[email protected]: {}
@@ -1414,8 +1628,22 @@ snapshots:
[email protected]: {}
[email protected]:
dependencies:
mdn-data: 2.27.1
source-map-js: 1.2.1
[email protected]: {}
[email protected]:
dependencies:
whatwg-mimetype: 5.0.0
whatwg-url: 16.0.1
transitivePeerDependencies:
- '@noble/hashes'
[email protected]: {}
[email protected]: {}
[email protected]: {}
@@ -1427,6 +1655,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
@@ -1450,8 +1680,42 @@ snapshots:
[email protected]: {}
[email protected]:
dependencies:
'@exodus/bytes': 1.15.1
transitivePeerDependencies:
- '@noble/hashes'
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
'@asamuzakjp/css-color': 6.0.7
'@asamuzakjp/dom-selector': 8.3.2
'@bramus/specificity': 2.4.2
'@csstools/css-syntax-patches-for-csstree': 1.1.12([email protected])
'@exodus/bytes': 1.15.1
css-tree: 3.2.1
data-urls: 7.0.0
decimal.js: 10.6.0
html-encoding-sniffer: 6.0.0
is-potential-custom-element-name: 1.0.1
lru-cache: 11.5.2
parse5: 8.0.1
saxes: 6.0.0
symbol-tree: 3.2.4
tough-cookie: 6.0.2
undici: 8.10.2
w3c-xmlserializer: 5.0.0
webidl-conversions: 8.0.1
whatwg-mimetype: 5.0.0
whatwg-url: 17.1.0
xml-name-validator: 5.0.0
transitivePeerDependencies:
- '@noble/hashes'
[email protected]:
optional: true
@@ -1556,6 +1820,8 @@ snapshots:
pkg-types: 2.3.2
quansync: 0.2.11
[email protected]: {}
[email protected]([email protected]([email protected])):
dependencies:
vue: 3.5.42([email protected])
@@ -1572,6 +1838,8 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.6.0
[email protected]: {}
[email protected]:
dependencies:
acorn: 8.18.0
@@ -1589,6 +1857,10 @@ snapshots:
[email protected]: {}
[email protected]:
dependencies:
entities: 8.0.0
[email protected]: {}
[email protected]: {}
@@ -1619,6 +1891,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
@@ -1639,6 +1913,8 @@ snapshots:
transitivePeerDependencies:
- '@vue/composition-api'
[email protected]: {}
[email protected]:
dependencies:
'@oxc-project/types': 0.148.0
@@ -1660,6 +1936,10 @@ snapshots:
'@rolldown/binding-win32-arm64-msvc': 1.2.7
'@rolldown/binding-win32-x64-msvc': 1.2.7
[email protected]:
dependencies:
xmlchars: 2.2.0
[email protected]: {}
[email protected]: {}
@@ -1670,6 +1950,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
@@ -1685,6 +1967,20 @@ snapshots:
fdir: 6.5.0([email protected])
picomatch: 4.0.7
[email protected]: {}
[email protected]:
dependencies:
tldts-core: 7.4.11
[email protected]:
dependencies:
tldts: 7.4.11
[email protected]:
dependencies:
punycode: 2.3.1
[email protected]: {}
[email protected]: {}
@@ -1693,6 +1989,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
pathe: 2.0.3
@@ -1719,7 +2017,7 @@ snapshots:
fsevents: 2.3.3
jiti: 2.7.0
[email protected](@types/[email protected])([email protected](@types/[email protected])([email protected])):
[email protected](@types/[email protected])([email protected])([email protected](@types/[email protected])([email protected])):
dependencies:
'@types/chai': 5.2.3
'@vitest/mocker': 5.0.0([email protected](@types/[email protected])([email protected]))
@@ -1737,6 +2035,7 @@ snapshots:
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 26.4.1
jsdom: 30.0.1
transitivePeerDependencies:
- msw
@@ -1794,9 +2093,37 @@ snapshots:
optionalDependencies:
typescript: 5.9.3
[email protected]:
dependencies:
xml-name-validator: 5.0.0
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
'@exodus/bytes': 1.15.1
tr46: 6.0.0
webidl-conversions: 8.0.1
transitivePeerDependencies:
- '@noble/hashes'
[email protected]:
dependencies:
'@exodus/bytes': 1.15.1
tr46: 6.0.0
webidl-conversions: 8.0.1
transitivePeerDependencies:
- '@noble/hashes'
[email protected]:
dependencies:
siginfo: 2.0.0
stackback: 0.0.2
[email protected]: {}
[email protected]: {}
+8 -1
View File
@@ -6,6 +6,7 @@ import {
Settings, Trash2, X, CalendarRange, Repeat2,
} from 'lucide-vue-next'
import { filterTasks, fromDateTimeLocal, groupTaskTree, renderMarkdown, toDateTimeLocal } from './lib/task-utils'
import { csrfHeader } from './lib/csrf'
import MvpPanel from './MvpPanel.vue'
type FolderItem = { id: string; name: string }
@@ -77,9 +78,15 @@ watch(showCompleted, () => {
})
async function api(path: string, options: RequestInit = {}) {
const headers = new Headers(options.headers || {})
if (!headers.has('Content-Type') && options.body && !(options.body instanceof FormData)) {
headers.set('Content-Type', 'application/json')
}
const csrf = csrfHeader(options.method)
if (csrf['x-csrf-token']) headers.set('x-csrf-token', csrf['x-csrf-token'])
const response = await fetch('/api/v1' + path, {
credentials: 'include',
headers: { 'Content-Type': 'application/json', ...(options.headers || {}) },
headers,
...options,
})
if (!response.ok) {
+3
View File
@@ -6,6 +6,7 @@ import interactionPlugin from '@fullcalendar/interaction'
import type { CalendarOptions, EventDropArg } from '@fullcalendar/core'
import { Activity, ArchiveRestore, Download, FileJson, LogOut, Plus, RefreshCw, Trash2, Upload } from 'lucide-vue-next'
import { dateKey, habitWeek, mergePage, moveDueDate } from './lib/mvp-utils'
import { csrfHeader } from './lib/csrf'
type View = 'calendar'|'habits'|'settings'
type Task = { id:string; title:string; due_at:string|null; version:number }
@@ -21,6 +22,8 @@ const week = computed(() => habitWeek())
async function request(path:string, options:RequestInit={}) {
const headers:Record<string,string> = { ...(options.headers as Record<string,string> || {}) }
if (options.body && !(options.body instanceof FormData)) headers['Content-Type']='application/json'
const csrf = csrfHeader(options.method)
if (csrf['x-csrf-token']) headers['x-csrf-token'] = csrf['x-csrf-token']
const response = await fetch('/api/v1'+path,{ credentials:'include',...options,headers })
if (!response.ok) throw new Error((await response.json().catch(()=>({}))).detail || `请求失败 (${response.status})`)
const type=response.headers.get('content-type')||''
+32
View File
@@ -0,0 +1,32 @@
import { describe, expect, it, beforeEach, afterEach } from 'vitest'
import { csrfHeader } from './csrf'
let originalCookie = ''
describe('csrf utilities', () => {
beforeEach(() => {
originalCookie = document.cookie
document.cookie = 'dodo_csrf=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/'
})
afterEach(() => {
document.cookie = originalCookie
})
it('adds the x-csrf-token header for write methods', () => {
document.cookie = 'dodo_csrf=token123; path=/'
expect(csrfHeader('POST')).toEqual({ 'x-csrf-token': 'token123' })
expect(csrfHeader('PATCH')).toEqual({ 'x-csrf-token': 'token123' })
expect(csrfHeader('DELETE')).toEqual({ 'x-csrf-token': 'token123' })
})
it('leaves read methods untouched', () => {
document.cookie = 'dodo_csrf=token123; path=/'
expect(csrfHeader('GET')).toEqual({})
expect(csrfHeader(undefined)).toEqual({})
})
it('does not add a header when the cookie is missing', () => {
expect(csrfHeader('POST')).toEqual({})
})
})
+13
View File
@@ -0,0 +1,13 @@
export function csrfHeader(method?: string) {
if (method && !['GET', 'HEAD', 'OPTIONS'].includes(method.toUpperCase())) {
const token = getCookie('dodo_csrf')
if (token) return { 'x-csrf-token': token }
}
return {}
}
export function getCookie(name: string) {
if (typeof document === 'undefined') return ''
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const match = document.cookie.match(new RegExp(`(?:^|; )${escaped}=([^;]*)`))
return match ? decodeURIComponent(match[1]) : ''
}
+5 -1
View File
@@ -2,4 +2,8 @@ import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({plugins:[vue(),tailwindcss()],server:{proxy:{'/api':'http://localhost:8781','/health':'http://localhost:8781'}}})
export default defineConfig({
plugins: [vue(), tailwindcss()],
server: { proxy: { '/api': 'http://localhost:8781', '/health': 'http://localhost:8781' } },
test: { environment: 'jsdom' },
})