From 045eb694871e367afcbefd65e1c94bc0e650bcc9 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 9 Jan 2025 20:29:49 +0900 Subject: [PATCH 01/17] =?UTF-8?q?Node.js,=20pnpm=20=EB=B0=8F=20=EA=B8=B0?= =?UTF-8?q?=ED=83=80=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EB=B2=84=EC=A0=84=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Node.js 18에서 Node.js 22로 업데이트하였습니다. - pnpm 8에서 pnpm 9으로 업데이트하였습니다. - 기타 의존성은 메이저 버전이 바뀌지 않는 선에서 최신 버전으로 업데이트하였습니다. --- Dockerfile | 4 +- package.json | 42 +- pnpm-lock.yaml | 4609 ++++++++++++++++++++++++++---------------------- 3 files changed, 2521 insertions(+), 2134 deletions(-) diff --git a/Dockerfile b/Dockerfile index 691038e..87e0df0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ # Base Image -FROM node:18-alpine AS base +FROM node:22-alpine AS base WORKDIR /app -RUN npm install -g pnpm@8 +RUN npm install -g pnpm@9 COPY pnpm-lock.yaml . # Build Stage diff --git a/package.json b/package.json index 05b20cc..c15ca72 100644 --- a/package.json +++ b/package.json @@ -17,46 +17,50 @@ "db:studio": "drizzle-kit studio" }, "devDependencies": { - "@eslint/compat": "^1.2.3", + "@eslint/compat": "^1.2.4", "@iconify-json/material-symbols": "^1.2.12", - "@sveltejs/adapter-node": "^5.2.9", - "@sveltejs/kit": "^2.0.0", - "@sveltejs/vite-plugin-svelte": "^4.0.0", - "@types/better-sqlite3": "^7.6.11", + "@sveltejs/adapter-node": "^5.2.11", + "@sveltejs/kit": "^2.15.2", + "@sveltejs/vite-plugin-svelte": "^4.0.4", + "@types/better-sqlite3": "^7.6.12", "@types/file-saver": "^2.0.7", "@types/jsonwebtoken": "^9.0.7", "@types/ms": "^0.7.34", "@types/node-schedule": "^2.1.7", "autoprefixer": "^10.4.20", "dexie": "^4.0.10", - "drizzle-kit": "^0.22.0", - "eslint": "^9.7.0", + "drizzle-kit": "^0.22.8", + "eslint": "^9.17.0", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-svelte": "^2.36.0", + "eslint-plugin-svelte": "^2.46.1", "eslint-plugin-tailwindcss": "^3.17.5", "file-saver": "^2.0.5", - "globals": "^15.0.0", + "globals": "^15.14.0", "heic2any": "^0.0.4", "mime": "^4.0.6", - "prettier": "^3.3.2", - "prettier-plugin-svelte": "^3.2.6", - "prettier-plugin-tailwindcss": "^0.6.5", - "svelte": "^5.0.0", - "svelte-check": "^4.0.0", - "tailwindcss": "^3.4.9", - "typescript": "^5.0.0", - "typescript-eslint": "^8.0.0", + "prettier": "^3.4.2", + "prettier-plugin-svelte": "^3.3.2", + "prettier-plugin-tailwindcss": "^0.6.9", + "svelte": "^5.17.1", + "svelte-check": "^4.1.3", + "tailwindcss": "^3.4.17", + "typescript": "^5.7.3", + "typescript-eslint": "^8.19.1", "unplugin-icons": "^0.22.0", "vite": "^5.4.11" }, "dependencies": { "argon2": "^0.41.1", - "better-sqlite3": "^11.1.2", + "better-sqlite3": "^11.7.2", "drizzle-orm": "^0.33.0", "jsonwebtoken": "^9.0.2", "ms": "^2.1.3", "node-schedule": "^2.1.1", - "uuid": "^11.0.3", + "uuid": "^11.0.4", "zod": "^3.24.1" + }, + "engines": { + "node": "^22.0.0", + "pnpm": "^9.0.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a44ef27..4e3c1fe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,807 +1,578 @@ -lockfileVersion: '6.0' +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -dependencies: - argon2: - specifier: ^0.41.1 - version: 0.41.1 - better-sqlite3: - specifier: ^11.1.2 - version: 11.7.0 - drizzle-orm: - specifier: ^0.33.0 - version: 0.33.0(@types/better-sqlite3@7.6.12)(better-sqlite3@11.7.0) - jsonwebtoken: - specifier: ^9.0.2 - version: 9.0.2 - ms: - specifier: ^2.1.3 - version: 2.1.3 - node-schedule: - specifier: ^2.1.1 - version: 2.1.1 - uuid: - specifier: ^11.0.3 - version: 11.0.3 - zod: - specifier: ^3.24.1 - version: 3.24.1 +importers: -devDependencies: - '@eslint/compat': - specifier: ^1.2.3 - version: 1.2.4(eslint@9.17.0) - '@iconify-json/material-symbols': - specifier: ^1.2.12 - version: 1.2.12 - '@sveltejs/adapter-node': - specifier: ^5.2.9 - version: 5.2.11(@sveltejs/kit@2.15.0) - '@sveltejs/kit': - specifier: ^2.0.0 - version: 2.15.0(@sveltejs/vite-plugin-svelte@4.0.4)(svelte@5.16.0)(vite@5.4.11) - '@sveltejs/vite-plugin-svelte': - specifier: ^4.0.0 - version: 4.0.4(svelte@5.16.0)(vite@5.4.11) - '@types/better-sqlite3': - specifier: ^7.6.11 - version: 7.6.12 - '@types/file-saver': - specifier: ^2.0.7 - version: 2.0.7 - '@types/jsonwebtoken': - specifier: ^9.0.7 - version: 9.0.7 - '@types/ms': - specifier: ^0.7.34 - version: 0.7.34 - '@types/node-schedule': - specifier: ^2.1.7 - version: 2.1.7 - autoprefixer: - specifier: ^10.4.20 - version: 10.4.20(postcss@8.4.49) - dexie: - specifier: ^4.0.10 - version: 4.0.10 - drizzle-kit: - specifier: ^0.22.0 - version: 0.22.8 - eslint: - specifier: ^9.7.0 - version: 9.17.0 - eslint-config-prettier: - specifier: ^9.1.0 - version: 9.1.0(eslint@9.17.0) - eslint-plugin-svelte: - specifier: ^2.36.0 - version: 2.46.1(eslint@9.17.0)(svelte@5.16.0) - eslint-plugin-tailwindcss: - specifier: ^3.17.5 - version: 3.17.5(tailwindcss@3.4.17) - file-saver: - specifier: ^2.0.5 - version: 2.0.5 - globals: - specifier: ^15.0.0 - version: 15.14.0 - heic2any: - specifier: ^0.0.4 - version: 0.0.4 - mime: - specifier: ^4.0.6 - version: 4.0.6 - prettier: - specifier: ^3.3.2 - version: 3.4.2 - prettier-plugin-svelte: - specifier: ^3.2.6 - version: 3.3.2(prettier@3.4.2)(svelte@5.16.0) - prettier-plugin-tailwindcss: - specifier: ^0.6.5 - version: 0.6.9(prettier-plugin-svelte@3.3.2)(prettier@3.4.2) - svelte: - specifier: ^5.0.0 - version: 5.16.0 - svelte-check: - specifier: ^4.0.0 - version: 4.1.1(svelte@5.16.0)(typescript@5.7.2) - tailwindcss: - specifier: ^3.4.9 - version: 3.4.17 - typescript: - specifier: ^5.0.0 - version: 5.7.2 - typescript-eslint: - specifier: ^8.0.0 - version: 8.18.2(eslint@9.17.0)(typescript@5.7.2) - unplugin-icons: - specifier: ^0.22.0 - version: 0.22.0(svelte@5.16.0) - vite: - specifier: ^5.4.11 - version: 5.4.11 + .: + dependencies: + argon2: + specifier: ^0.41.1 + version: 0.41.1 + better-sqlite3: + specifier: ^11.7.2 + version: 11.7.2 + drizzle-orm: + specifier: ^0.33.0 + version: 0.33.0(@types/better-sqlite3@7.6.12)(better-sqlite3@11.7.2) + jsonwebtoken: + specifier: ^9.0.2 + version: 9.0.2 + ms: + specifier: ^2.1.3 + version: 2.1.3 + node-schedule: + specifier: ^2.1.1 + version: 2.1.1 + uuid: + specifier: ^11.0.4 + version: 11.0.4 + zod: + specifier: ^3.24.1 + version: 3.24.1 + devDependencies: + '@eslint/compat': + specifier: ^1.2.4 + version: 1.2.4(eslint@9.17.0(jiti@1.21.7)) + '@iconify-json/material-symbols': + specifier: ^1.2.12 + version: 1.2.12 + '@sveltejs/adapter-node': + specifier: ^5.2.11 + version: 5.2.11(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5))) + '@sveltejs/kit': + specifier: ^2.15.2 + version: 2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)) + '@sveltejs/vite-plugin-svelte': + specifier: ^4.0.4 + version: 4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)) + '@types/better-sqlite3': + specifier: ^7.6.12 + version: 7.6.12 + '@types/file-saver': + specifier: ^2.0.7 + version: 2.0.7 + '@types/jsonwebtoken': + specifier: ^9.0.7 + version: 9.0.7 + '@types/ms': + specifier: ^0.7.34 + version: 0.7.34 + '@types/node-schedule': + specifier: ^2.1.7 + version: 2.1.7 + autoprefixer: + specifier: ^10.4.20 + version: 10.4.20(postcss@8.4.49) + dexie: + specifier: ^4.0.10 + version: 4.0.10 + drizzle-kit: + specifier: ^0.22.8 + version: 0.22.8 + eslint: + specifier: ^9.17.0 + version: 9.17.0(jiti@1.21.7) + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@9.17.0(jiti@1.21.7)) + eslint-plugin-svelte: + specifier: ^2.46.1 + version: 2.46.1(eslint@9.17.0(jiti@1.21.7))(svelte@5.17.1) + eslint-plugin-tailwindcss: + specifier: ^3.17.5 + version: 3.17.5(tailwindcss@3.4.17) + file-saver: + specifier: ^2.0.5 + version: 2.0.5 + globals: + specifier: ^15.14.0 + version: 15.14.0 + heic2any: + specifier: ^0.0.4 + version: 0.0.4 + mime: + specifier: ^4.0.6 + version: 4.0.6 + prettier: + specifier: ^3.4.2 + version: 3.4.2 + prettier-plugin-svelte: + specifier: ^3.3.2 + version: 3.3.2(prettier@3.4.2)(svelte@5.17.1) + prettier-plugin-tailwindcss: + specifier: ^0.6.9 + version: 0.6.9(prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.17.1))(prettier@3.4.2) + svelte: + specifier: ^5.17.1 + version: 5.17.1 + svelte-check: + specifier: ^4.1.3 + version: 4.1.3(picomatch@4.0.2)(svelte@5.17.1)(typescript@5.7.3) + tailwindcss: + specifier: ^3.4.17 + version: 3.4.17 + typescript: + specifier: ^5.7.3 + version: 5.7.3 + typescript-eslint: + specifier: ^8.19.1 + version: 8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3) + unplugin-icons: + specifier: ^0.22.0 + version: 0.22.0(svelte@5.17.1) + vite: + specifier: ^5.4.11 + version: 5.4.11(@types/node@22.10.5) packages: - /@alloc/quick-lru@5.2.0: + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - dev: true - /@ampproject/remapping@2.3.0: + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 - dev: true - /@antfu/install-pkg@0.4.1: + '@antfu/install-pkg@0.4.1': resolution: {integrity: sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==} - dependencies: - package-manager-detector: 0.2.8 - tinyexec: 0.3.1 - dev: true - /@antfu/install-pkg@0.5.0: + '@antfu/install-pkg@0.5.0': resolution: {integrity: sha512-dKnk2xlAyC7rvTkpkHmu+Qy/2Zc3Vm/l8PtNyIOGDBtXPY3kThfU4ORNEp3V7SXw5XSOb+tOJaUYpfquPzL/Tg==} - dependencies: - package-manager-detector: 0.2.8 - tinyexec: 0.3.1 - dev: true - /@antfu/utils@0.7.10: + '@antfu/utils@0.7.10': resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} - dev: true - /@esbuild-kit/core-utils@3.3.2: + '@esbuild-kit/core-utils@3.3.2': resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} deprecated: 'Merged into tsx: https://tsx.is' - dependencies: - esbuild: 0.18.20 - source-map-support: 0.5.21 - dev: true - /@esbuild-kit/esm-loader@2.6.5: + '@esbuild-kit/esm-loader@2.6.5': resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} deprecated: 'Merged into tsx: https://tsx.is' - dependencies: - '@esbuild-kit/core-utils': 3.3.2 - get-tsconfig: 4.8.1 - dev: true - /@esbuild/aix-ppc64@0.19.12: + '@esbuild/aix-ppc64@0.19.12': resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] - requiresBuild: true - dev: true - optional: true - /@esbuild/aix-ppc64@0.21.5: + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm64@0.18.20: + '@esbuild/android-arm64@0.18.20': resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm64@0.19.12: + '@esbuild/android-arm64@0.19.12': resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} engines: {node: '>=12'} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm64@0.21.5: + '@esbuild/android-arm64@0.21.5': resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm@0.18.20: + '@esbuild/android-arm@0.18.20': resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} engines: {node: '>=12'} cpu: [arm] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm@0.19.12: + '@esbuild/android-arm@0.19.12': resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} engines: {node: '>=12'} cpu: [arm] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm@0.21.5: + '@esbuild/android-arm@0.21.5': resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-x64@0.18.20: + '@esbuild/android-x64@0.18.20': resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} engines: {node: '>=12'} cpu: [x64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-x64@0.19.12: + '@esbuild/android-x64@0.19.12': resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} engines: {node: '>=12'} cpu: [x64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-x64@0.21.5: + '@esbuild/android-x64@0.21.5': resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-arm64@0.18.20: + '@esbuild/darwin-arm64@0.18.20': resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-arm64@0.19.12: + '@esbuild/darwin-arm64@0.19.12': resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-arm64@0.21.5: + '@esbuild/darwin-arm64@0.21.5': resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-x64@0.18.20: + '@esbuild/darwin-x64@0.18.20': resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-x64@0.19.12: + '@esbuild/darwin-x64@0.19.12': resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-x64@0.21.5: + '@esbuild/darwin-x64@0.21.5': resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-arm64@0.18.20: + '@esbuild/freebsd-arm64@0.18.20': resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-arm64@0.19.12: + '@esbuild/freebsd-arm64@0.19.12': resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-arm64@0.21.5: + '@esbuild/freebsd-arm64@0.21.5': resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-x64@0.18.20: + '@esbuild/freebsd-x64@0.18.20': resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-x64@0.19.12: + '@esbuild/freebsd-x64@0.19.12': resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-x64@0.21.5: + '@esbuild/freebsd-x64@0.21.5': resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm64@0.18.20: + '@esbuild/linux-arm64@0.18.20': resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm64@0.19.12: + '@esbuild/linux-arm64@0.19.12': resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm64@0.21.5: + '@esbuild/linux-arm64@0.21.5': resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm@0.18.20: + '@esbuild/linux-arm@0.18.20': resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} engines: {node: '>=12'} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm@0.19.12: + '@esbuild/linux-arm@0.19.12': resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} engines: {node: '>=12'} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm@0.21.5: + '@esbuild/linux-arm@0.21.5': resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ia32@0.18.20: + '@esbuild/linux-ia32@0.18.20': resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} engines: {node: '>=12'} cpu: [ia32] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ia32@0.19.12: + '@esbuild/linux-ia32@0.19.12': resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} engines: {node: '>=12'} cpu: [ia32] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ia32@0.21.5: + '@esbuild/linux-ia32@0.21.5': resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-loong64@0.18.20: + '@esbuild/linux-loong64@0.18.20': resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-loong64@0.19.12: + '@esbuild/linux-loong64@0.19.12': resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} engines: {node: '>=12'} cpu: [loong64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-loong64@0.21.5: + '@esbuild/linux-loong64@0.21.5': resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-mips64el@0.18.20: + '@esbuild/linux-mips64el@0.18.20': resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-mips64el@0.19.12: + '@esbuild/linux-mips64el@0.19.12': resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-mips64el@0.21.5: + '@esbuild/linux-mips64el@0.21.5': resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ppc64@0.18.20: + '@esbuild/linux-ppc64@0.18.20': resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ppc64@0.19.12: + '@esbuild/linux-ppc64@0.19.12': resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ppc64@0.21.5: + '@esbuild/linux-ppc64@0.21.5': resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-riscv64@0.18.20: + '@esbuild/linux-riscv64@0.18.20': resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-riscv64@0.19.12: + '@esbuild/linux-riscv64@0.19.12': resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-riscv64@0.21.5: + '@esbuild/linux-riscv64@0.21.5': resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-s390x@0.18.20: + '@esbuild/linux-s390x@0.18.20': resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} engines: {node: '>=12'} cpu: [s390x] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-s390x@0.19.12: + '@esbuild/linux-s390x@0.19.12': resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} engines: {node: '>=12'} cpu: [s390x] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-s390x@0.21.5: + '@esbuild/linux-s390x@0.21.5': resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-x64@0.18.20: + '@esbuild/linux-x64@0.18.20': resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} engines: {node: '>=12'} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-x64@0.19.12: + '@esbuild/linux-x64@0.19.12': resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} engines: {node: '>=12'} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-x64@0.21.5: + '@esbuild/linux-x64@0.21.5': resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/netbsd-x64@0.18.20: + '@esbuild/netbsd-x64@0.18.20': resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/netbsd-x64@0.19.12: + '@esbuild/netbsd-x64@0.19.12': resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/netbsd-x64@0.21.5: + '@esbuild/netbsd-x64@0.21.5': resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/openbsd-x64@0.18.20: + '@esbuild/openbsd-x64@0.18.20': resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/openbsd-x64@0.19.12: + '@esbuild/openbsd-x64@0.19.12': resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/openbsd-x64@0.21.5: + '@esbuild/openbsd-x64@0.21.5': resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/sunos-x64@0.18.20: + '@esbuild/sunos-x64@0.18.20': resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} engines: {node: '>=12'} cpu: [x64] os: [sunos] - requiresBuild: true - dev: true - optional: true - /@esbuild/sunos-x64@0.19.12: + '@esbuild/sunos-x64@0.19.12': resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} engines: {node: '>=12'} cpu: [x64] os: [sunos] - requiresBuild: true - dev: true - optional: true - /@esbuild/sunos-x64@0.21.5: + '@esbuild/sunos-x64@0.21.5': resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-arm64@0.18.20: + '@esbuild/win32-arm64@0.18.20': resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-arm64@0.19.12: + '@esbuild/win32-arm64@0.19.12': resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-arm64@0.21.5: + '@esbuild/win32-arm64@0.21.5': resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-ia32@0.18.20: + '@esbuild/win32-ia32@0.18.20': resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} engines: {node: '>=12'} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-ia32@0.19.12: + '@esbuild/win32-ia32@0.19.12': resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} engines: {node: '>=12'} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-ia32@0.21.5: + '@esbuild/win32-ia32@0.21.5': resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-x64@0.18.20: + '@esbuild/win32-x64@0.18.20': resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} engines: {node: '>=12'} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-x64@0.19.12: + '@esbuild/win32-x64@0.19.12': resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} engines: {node: '>=12'} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-x64@0.21.5: + '@esbuild/win32-x64@0.21.5': resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@eslint-community/eslint-utils@4.4.1(eslint@9.17.0): + '@eslint-community/eslint-utils@4.4.1': resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 9.17.0 - eslint-visitor-keys: 3.4.3 - dev: true - /@eslint-community/regexpp@4.12.1: + '@eslint-community/regexpp@4.12.1': resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - dev: true - /@eslint/compat@1.2.4(eslint@9.17.0): + '@eslint/compat@1.2.4': resolution: {integrity: sha512-S8ZdQj/N69YAtuqFt7653jwcvuUj131+6qGLUyDqfDg1OIoBQ66OCuXC473YQfO2AaxITTutiRQiDwoo7ZLYyg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: @@ -809,195 +580,106 @@ packages: peerDependenciesMeta: eslint: optional: true - dependencies: - eslint: 9.17.0 - dev: true - /@eslint/config-array@0.19.1: + '@eslint/config-array@0.19.1': resolution: {integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dependencies: - '@eslint/object-schema': 2.1.5 - debug: 4.4.0 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - dev: true - /@eslint/core@0.9.1: + '@eslint/core@0.9.1': resolution: {integrity: sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dependencies: - '@types/json-schema': 7.0.15 - dev: true - /@eslint/eslintrc@3.2.0: + '@eslint/eslintrc@3.2.0': resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dependencies: - ajv: 6.12.6 - debug: 4.4.0 - espree: 10.3.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: true - /@eslint/js@9.17.0: + '@eslint/js@9.17.0': resolution: {integrity: sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dev: true - /@eslint/object-schema@2.1.5: + '@eslint/object-schema@2.1.5': resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dev: true - /@eslint/plugin-kit@0.2.4: + '@eslint/plugin-kit@0.2.4': resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dependencies: - levn: 0.4.1 - dev: true - /@humanfs/core@0.19.1: + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} - dev: true - /@humanfs/node@0.16.6: + '@humanfs/node@0.16.6': resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} engines: {node: '>=18.18.0'} - dependencies: - '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.3.1 - dev: true - /@humanwhocodes/module-importer@1.0.1: + '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - dev: true - /@humanwhocodes/retry@0.3.1: + '@humanwhocodes/retry@0.3.1': resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} engines: {node: '>=18.18'} - dev: true - /@humanwhocodes/retry@0.4.1: + '@humanwhocodes/retry@0.4.1': resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} engines: {node: '>=18.18'} - dev: true - /@iconify-json/material-symbols@1.2.12: + '@iconify-json/material-symbols@1.2.12': resolution: {integrity: sha512-2p2T13Kccy7R2HNbdiVsIcHxjp4s9a+iKlfbtt29hldG1pVNaPIlMALNA9bjdEwPjwsVFe06INCbjCRc68JysQ==} - dependencies: - '@iconify/types': 2.0.0 - dev: true - /@iconify/types@2.0.0: + '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - dev: true - /@iconify/utils@2.2.1: + '@iconify/utils@2.2.1': resolution: {integrity: sha512-0/7J7hk4PqXmxo5PDBDxmnecw5PxklZJfNjIVG9FM0mEfVrvfudS22rYWsqVk6gR3UJ/mSYS90X4R3znXnqfNA==} - dependencies: - '@antfu/install-pkg': 0.4.1 - '@antfu/utils': 0.7.10 - '@iconify/types': 2.0.0 - debug: 4.4.0 - globals: 15.14.0 - kolorist: 1.8.0 - local-pkg: 0.5.1 - mlly: 1.7.3 - transitivePeerDependencies: - - supports-color - dev: true - /@isaacs/cliui@8.0.2: + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - dependencies: - string-width: 5.1.2 - string-width-cjs: /string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: /strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: /wrap-ansi@7.0.0 - dev: true - /@jridgewell/gen-mapping@0.3.8: + '@jridgewell/gen-mapping@0.3.8': resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 - dev: true - /@jridgewell/resolve-uri@3.1.2: + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - dev: true - /@jridgewell/set-array@1.2.1: + '@jridgewell/set-array@1.2.1': resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} - dev: true - /@jridgewell/sourcemap-codec@1.5.0: + '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - dev: true - /@jridgewell/trace-mapping@0.3.25: + '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - dev: true - /@nodelib/fs.scandir@2.1.5: + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - dev: true - /@nodelib/fs.stat@2.0.5: + '@nodelib/fs.stat@2.0.5': resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} - dev: true - /@nodelib/fs.walk@1.2.8: + '@nodelib/fs.walk@1.2.8': resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.18.0 - dev: true - /@phc/format@1.0.0: + '@phc/format@1.0.0': resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==} engines: {node: '>=10'} - dev: false - /@pkgjs/parseargs@0.11.0: + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - requiresBuild: true - dev: true - optional: true - /@polka/url@1.0.0-next.28: + '@polka/url@1.0.0-next.28': resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} - dev: true - /@rollup/plugin-commonjs@28.0.2(rollup@4.29.1): + '@rollup/plugin-commonjs@28.0.2': resolution: {integrity: sha512-BEFI2EDqzl+vA1rl97IDRZ61AIwGH093d9nz8+dThxJNH8oSoB7MjWvPCX3dkaK1/RCJ/1v/R1XB15FuSs0fQw==} engines: {node: '>=16.0.0 || 14 >= 14.17'} peerDependencies: @@ -1005,18 +687,8 @@ packages: peerDependenciesMeta: rollup: optional: true - dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.29.1) - commondir: 1.0.1 - estree-walker: 2.0.2 - fdir: 6.4.2(picomatch@4.0.2) - is-reference: 1.2.1 - magic-string: 0.30.17 - picomatch: 4.0.2 - rollup: 4.29.1 - dev: true - /@rollup/plugin-json@6.1.0(rollup@4.29.1): + '@rollup/plugin-json@6.1.0': resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1024,12 +696,8 @@ packages: peerDependenciesMeta: rollup: optional: true - dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.29.1) - rollup: 4.29.1 - dev: true - /@rollup/plugin-node-resolve@16.0.0(rollup@4.29.1): + '@rollup/plugin-node-resolve@16.0.0': resolution: {integrity: sha512-0FPvAeVUT/zdWoO0jnb/V5BlBsUSNfkIOtFHzMO4H9MOklrmQFY6FduVHKucNb/aTFxvnGhj4MNj/T1oNdDfNg==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1037,16 +705,8 @@ packages: peerDependenciesMeta: rollup: optional: true - dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.29.1) - '@types/resolve': 1.20.2 - deepmerge: 4.3.1 - is-module: 1.0.0 - resolve: 1.22.10 - rollup: 4.29.1 - dev: true - /@rollup/pluginutils@5.1.4(rollup@4.29.1): + '@rollup/pluginutils@5.1.4': resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1054,700 +714,385 @@ packages: peerDependenciesMeta: rollup: optional: true - dependencies: - '@types/estree': 1.0.6 - estree-walker: 2.0.2 - picomatch: 4.0.2 - rollup: 4.29.1 - dev: true - /@rollup/rollup-android-arm-eabi@4.29.1: - resolution: {integrity: sha512-ssKhA8RNltTZLpG6/QNkCSge+7mBQGUqJRisZ2MDQcEGaK93QESEgWK2iOpIDZ7k9zPVkG5AS3ksvD5ZWxmItw==} + '@rollup/rollup-android-arm-eabi@4.30.1': + resolution: {integrity: sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==} cpu: [arm] os: [android] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-android-arm64@4.29.1: - resolution: {integrity: sha512-CaRfrV0cd+NIIcVVN/jx+hVLN+VRqnuzLRmfmlzpOzB87ajixsN/+9L5xNmkaUUvEbI5BmIKS+XTwXsHEb65Ew==} + '@rollup/rollup-android-arm64@4.30.1': + resolution: {integrity: sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-darwin-arm64@4.29.1: - resolution: {integrity: sha512-2ORr7T31Y0Mnk6qNuwtyNmy14MunTAMx06VAPI6/Ju52W10zk1i7i5U3vlDRWjhOI5quBcrvhkCHyF76bI7kEw==} + '@rollup/rollup-darwin-arm64@4.30.1': + resolution: {integrity: sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-darwin-x64@4.29.1: - resolution: {integrity: sha512-j/Ej1oanzPjmN0tirRd5K2/nncAhS9W6ICzgxV+9Y5ZsP0hiGhHJXZ2JQ53iSSjj8m6cRY6oB1GMzNn2EUt6Ng==} + '@rollup/rollup-darwin-x64@4.30.1': + resolution: {integrity: sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-freebsd-arm64@4.29.1: - resolution: {integrity: sha512-91C//G6Dm/cv724tpt7nTyP+JdN12iqeXGFM1SqnljCmi5yTXriH7B1r8AD9dAZByHpKAumqP1Qy2vVNIdLZqw==} + '@rollup/rollup-freebsd-arm64@4.30.1': + resolution: {integrity: sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==} cpu: [arm64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-freebsd-x64@4.29.1: - resolution: {integrity: sha512-hEioiEQ9Dec2nIRoeHUP6hr1PSkXzQaCUyqBDQ9I9ik4gCXQZjJMIVzoNLBRGet+hIUb3CISMh9KXuCcWVW/8w==} + '@rollup/rollup-freebsd-x64@4.30.1': + resolution: {integrity: sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==} cpu: [x64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.29.1: - resolution: {integrity: sha512-Py5vFd5HWYN9zxBv3WMrLAXY3yYJ6Q/aVERoeUFwiDGiMOWsMs7FokXihSOaT/PMWUty/Pj60XDQndK3eAfE6A==} + '@rollup/rollup-linux-arm-gnueabihf@4.30.1': + resolution: {integrity: sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm-musleabihf@4.29.1: - resolution: {integrity: sha512-RiWpGgbayf7LUcuSNIbahr0ys2YnEERD4gYdISA06wa0i8RALrnzflh9Wxii7zQJEB2/Eh74dX4y/sHKLWp5uQ==} + '@rollup/rollup-linux-arm-musleabihf@4.30.1': + resolution: {integrity: sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm64-gnu@4.29.1: - resolution: {integrity: sha512-Z80O+taYxTQITWMjm/YqNoe9d10OX6kDh8X5/rFCMuPqsKsSyDilvfg+vd3iXIqtfmp+cnfL1UrYirkaF8SBZA==} + '@rollup/rollup-linux-arm64-gnu@4.30.1': + resolution: {integrity: sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm64-musl@4.29.1: - resolution: {integrity: sha512-fOHRtF9gahwJk3QVp01a/GqS4hBEZCV1oKglVVq13kcK3NeVlS4BwIFzOHDbmKzt3i0OuHG4zfRP0YoG5OF/rA==} + '@rollup/rollup-linux-arm64-musl@4.30.1': + resolution: {integrity: sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-loongarch64-gnu@4.29.1: - resolution: {integrity: sha512-5a7q3tnlbcg0OodyxcAdrrCxFi0DgXJSoOuidFUzHZ2GixZXQs6Tc3CHmlvqKAmOs5eRde+JJxeIf9DonkmYkw==} + '@rollup/rollup-linux-loongarch64-gnu@4.30.1': + resolution: {integrity: sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==} cpu: [loong64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-powerpc64le-gnu@4.29.1: - resolution: {integrity: sha512-9b4Mg5Yfz6mRnlSPIdROcfw1BU22FQxmfjlp/CShWwO3LilKQuMISMTtAu/bxmmrE6A902W2cZJuzx8+gJ8e9w==} + '@rollup/rollup-linux-powerpc64le-gnu@4.30.1': + resolution: {integrity: sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==} cpu: [ppc64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-riscv64-gnu@4.29.1: - resolution: {integrity: sha512-G5pn0NChlbRM8OJWpJFMX4/i8OEU538uiSv0P6roZcbpe/WfhEO+AT8SHVKfp8qhDQzaz7Q+1/ixMy7hBRidnQ==} + '@rollup/rollup-linux-riscv64-gnu@4.30.1': + resolution: {integrity: sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-s390x-gnu@4.29.1: - resolution: {integrity: sha512-WM9lIkNdkhVwiArmLxFXpWndFGuOka4oJOZh8EP3Vb8q5lzdSCBuhjavJsw68Q9AKDGeOOIHYzYm4ZFvmWez5g==} + '@rollup/rollup-linux-s390x-gnu@4.30.1': + resolution: {integrity: sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==} cpu: [s390x] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-x64-gnu@4.29.1: - resolution: {integrity: sha512-87xYCwb0cPGZFoGiErT1eDcssByaLX4fc0z2nRM6eMtV9njAfEE6OW3UniAoDhX4Iq5xQVpE6qO9aJbCFumKYQ==} + '@rollup/rollup-linux-x64-gnu@4.30.1': + resolution: {integrity: sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-x64-musl@4.29.1: - resolution: {integrity: sha512-xufkSNppNOdVRCEC4WKvlR1FBDyqCSCpQeMMgv9ZyXqqtKBfkw1yfGMTUTs9Qsl6WQbJnsGboWCp7pJGkeMhKA==} + '@rollup/rollup-linux-x64-musl@4.30.1': + resolution: {integrity: sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-arm64-msvc@4.29.1: - resolution: {integrity: sha512-F2OiJ42m77lSkizZQLuC+jiZ2cgueWQL5YC9tjo3AgaEw+KJmVxHGSyQfDUoYR9cci0lAywv2Clmckzulcq6ig==} + '@rollup/rollup-win32-arm64-msvc@4.30.1': + resolution: {integrity: sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-ia32-msvc@4.29.1: - resolution: {integrity: sha512-rYRe5S0FcjlOBZQHgbTKNrqxCBUmgDJem/VQTCcTnA2KCabYSWQDrytOzX7avb79cAAweNmMUb/Zw18RNd4mng==} + '@rollup/rollup-win32-ia32-msvc@4.30.1': + resolution: {integrity: sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-x64-msvc@4.29.1: - resolution: {integrity: sha512-+10CMg9vt1MoHj6x1pxyjPSMjHTIlqs8/tBztXvPAx24SKs9jwVnKqHJumlH/IzhaPUaj3T6T6wfZr8okdXaIg==} + '@rollup/rollup-win32-x64-msvc@4.30.1': + resolution: {integrity: sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@sveltejs/adapter-node@5.2.11(@sveltejs/kit@2.15.0): + '@sveltejs/adapter-node@5.2.11': resolution: {integrity: sha512-lR7/dfUaKFf3aI408KRDy/BVDYoqUws7zNOJz2Hl4JoshlTnMgdha3brXBRFXB+cWtYvJjjPhvmq3xqpbioi4w==} peerDependencies: '@sveltejs/kit': ^2.4.0 - dependencies: - '@rollup/plugin-commonjs': 28.0.2(rollup@4.29.1) - '@rollup/plugin-json': 6.1.0(rollup@4.29.1) - '@rollup/plugin-node-resolve': 16.0.0(rollup@4.29.1) - '@sveltejs/kit': 2.15.0(@sveltejs/vite-plugin-svelte@4.0.4)(svelte@5.16.0)(vite@5.4.11) - rollup: 4.29.1 - dev: true - /@sveltejs/kit@2.15.0(@sveltejs/vite-plugin-svelte@4.0.4)(svelte@5.16.0)(vite@5.4.11): - resolution: {integrity: sha512-FI1bhfhFNGI2sKg+BhiRyM4eaOvX+KZqRYSQqL5PK3ZZREX2xufZ6MzZAw79N846OnIxYNqcz/3VOUq+FPDd3w==} + '@sveltejs/kit@2.15.2': + resolution: {integrity: sha512-p208T1kdM6zd8k4YXIUM60pLWQ8dZqehXSiqn4NulXHyHibX53uIAL2xtNL8GjxX2IVPqPRT978MwVYhCKExdQ==} engines: {node: '>=18.13'} hasBin: true - requiresBuild: true peerDependencies: '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 svelte: ^4.0.0 || ^5.0.0-next.0 vite: ^5.0.3 || ^6.0.0 - dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.16.0)(vite@5.4.11) - '@types/cookie': 0.6.0 - cookie: 0.6.0 - devalue: 5.1.1 - esm-env: 1.2.1 - import-meta-resolve: 4.1.0 - kleur: 4.1.5 - magic-string: 0.30.17 - mrmime: 2.0.0 - sade: 1.8.1 - set-cookie-parser: 2.7.1 - sirv: 3.0.0 - svelte: 5.16.0 - tiny-glob: 0.2.9 - vite: 5.4.11 - dev: true - /@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4)(svelte@5.16.0)(vite@5.4.11): + '@sveltejs/vite-plugin-svelte-inspector@3.0.1': resolution: {integrity: sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22} peerDependencies: '@sveltejs/vite-plugin-svelte': ^4.0.0-next.0||^4.0.0 svelte: ^5.0.0-next.96 || ^5.0.0 vite: ^5.0.0 - dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.16.0)(vite@5.4.11) - debug: 4.4.0 - svelte: 5.16.0 - vite: 5.4.11 - transitivePeerDependencies: - - supports-color - dev: true - /@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11): + '@sveltejs/vite-plugin-svelte@4.0.4': resolution: {integrity: sha512-0ba1RQ/PHen5FGpdSrW7Y3fAMQjrXantECALeOiOdBdzR5+5vPP6HVZRLmZaQL+W8m++o+haIAKq5qT+MiZ7VA==} engines: {node: ^18.0.0 || ^20.0.0 || >=22} peerDependencies: svelte: ^5.0.0-next.96 || ^5.0.0 vite: ^5.0.0 - dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4)(svelte@5.16.0)(vite@5.4.11) - debug: 4.4.0 - deepmerge: 4.3.1 - kleur: 4.1.5 - magic-string: 0.30.17 - svelte: 5.16.0 - vite: 5.4.11 - vitefu: 1.0.4(vite@5.4.11) - transitivePeerDependencies: - - supports-color - dev: true - /@types/better-sqlite3@7.6.12: + '@types/better-sqlite3@7.6.12': resolution: {integrity: sha512-fnQmj8lELIj7BSrZQAdBMHEHX8OZLYIHXqAKT1O7tDfLxaINzf00PMjw22r3N/xXh0w/sGHlO6SVaCQ2mj78lg==} - dependencies: - '@types/node': 22.10.2 - /@types/cookie@0.6.0: + '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - dev: true - /@types/estree@1.0.6: + '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - dev: true - /@types/file-saver@2.0.7: + '@types/file-saver@2.0.7': resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==} - dev: true - /@types/json-schema@7.0.15: + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - dev: true - /@types/jsonwebtoken@9.0.7: + '@types/jsonwebtoken@9.0.7': resolution: {integrity: sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==} - dependencies: - '@types/node': 22.10.2 - dev: true - /@types/ms@0.7.34: + '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - dev: true - /@types/node-schedule@2.1.7: + '@types/node-schedule@2.1.7': resolution: {integrity: sha512-G7Z3R9H7r3TowoH6D2pkzUHPhcJrDF4Jz1JOQ80AX0K2DWTHoN9VC94XzFAPNMdbW9TBzMZ3LjpFi7RYdbxtXA==} - dependencies: - '@types/node': 22.10.2 - dev: true - /@types/node@22.10.2: - resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==} - dependencies: - undici-types: 6.20.0 + '@types/node@22.10.5': + resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} - /@types/resolve@1.20.2: + '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} - dev: true - /@typescript-eslint/eslint-plugin@8.18.2(@typescript-eslint/parser@8.18.2)(eslint@9.17.0)(typescript@5.7.2): - resolution: {integrity: sha512-adig4SzPLjeQ0Tm+jvsozSGiCliI2ajeURDGHjZ2llnA+A67HihCQ+a3amtPhUakd1GlwHxSRvzOZktbEvhPPg==} + '@typescript-eslint/eslint-plugin@8.19.1': + resolution: {integrity: sha512-tJzcVyvvb9h/PB96g30MpxACd9IrunT7GF9wfA9/0TJ1LxGOJx1TdPzSbBBnNED7K9Ka8ybJsnEpiXPktolTLg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.18.2(eslint@9.17.0)(typescript@5.7.2) - '@typescript-eslint/scope-manager': 8.18.2 - '@typescript-eslint/type-utils': 8.18.2(eslint@9.17.0)(typescript@5.7.2) - '@typescript-eslint/utils': 8.18.2(eslint@9.17.0)(typescript@5.7.2) - '@typescript-eslint/visitor-keys': 8.18.2 - eslint: 9.17.0 - graphemer: 1.4.0 - ignore: 5.3.2 - natural-compare: 1.4.0 - ts-api-utils: 1.4.3(typescript@5.7.2) - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/parser@8.18.2(eslint@9.17.0)(typescript@5.7.2): - resolution: {integrity: sha512-y7tcq4StgxQD4mDr9+Jb26dZ+HTZ/SkfqpXSiqeUXZHxOUyjWDKsmwKhJ0/tApR08DgOhrFAoAhyB80/p3ViuA==} + '@typescript-eslint/parser@8.19.1': + resolution: {integrity: sha512-67gbfv8rAwawjYx3fYArwldTQKoYfezNUT4D5ioWetr/xCrxXxvleo3uuiFuKfejipvq+og7mjz3b0G2bVyUCw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - dependencies: - '@typescript-eslint/scope-manager': 8.18.2 - '@typescript-eslint/types': 8.18.2 - '@typescript-eslint/typescript-estree': 8.18.2(typescript@5.7.2) - '@typescript-eslint/visitor-keys': 8.18.2 - debug: 4.4.0 - eslint: 9.17.0 - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/scope-manager@8.18.2: - resolution: {integrity: sha512-YJFSfbd0CJjy14r/EvWapYgV4R5CHzptssoag2M7y3Ra7XNta6GPAJPPP5KGB9j14viYXyrzRO5GkX7CRfo8/g==} + '@typescript-eslint/scope-manager@8.19.1': + resolution: {integrity: sha512-60L9KIuN/xgmsINzonOcMDSB8p82h95hoBfSBtXuO4jlR1R9L1xSkmVZKgCPVfavDlXihh4ARNjXhh1gGnLC7Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dependencies: - '@typescript-eslint/types': 8.18.2 - '@typescript-eslint/visitor-keys': 8.18.2 - dev: true - /@typescript-eslint/type-utils@8.18.2(eslint@9.17.0)(typescript@5.7.2): - resolution: {integrity: sha512-AB/Wr1Lz31bzHfGm/jgbFR0VB0SML/hd2P1yxzKDM48YmP7vbyJNHRExUE/wZsQj2wUCvbWH8poNHFuxLqCTnA==} + '@typescript-eslint/type-utils@8.19.1': + resolution: {integrity: sha512-Rp7k9lhDKBMRJB/nM9Ksp1zs4796wVNyihG9/TU9R6KCJDNkQbc2EOKjrBtLYh3396ZdpXLtr/MkaSEmNMtykw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - dependencies: - '@typescript-eslint/typescript-estree': 8.18.2(typescript@5.7.2) - '@typescript-eslint/utils': 8.18.2(eslint@9.17.0)(typescript@5.7.2) - debug: 4.4.0 - eslint: 9.17.0 - ts-api-utils: 1.4.3(typescript@5.7.2) - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/types@8.18.2: - resolution: {integrity: sha512-Z/zblEPp8cIvmEn6+tPDIHUbRu/0z5lqZ+NvolL5SvXWT5rQy7+Nch83M0++XzO0XrWRFWECgOAyE8bsJTl1GQ==} + '@typescript-eslint/types@8.19.1': + resolution: {integrity: sha512-JBVHMLj7B1K1v1051ZaMMgLW4Q/jre5qGK0Ew6UgXz1Rqh+/xPzV1aW581OM00X6iOfyr1be+QyW8LOUf19BbA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dev: true - /@typescript-eslint/typescript-estree@8.18.2(typescript@5.7.2): - resolution: {integrity: sha512-WXAVt595HjpmlfH4crSdM/1bcsqh+1weFRWIa9XMTx/XHZ9TCKMcr725tLYqWOgzKdeDrqVHxFotrvWcEsk2Tg==} + '@typescript-eslint/typescript-estree@8.19.1': + resolution: {integrity: sha512-jk/TZwSMJlxlNnqhy0Eod1PNEvCkpY6MXOXE/WLlblZ6ibb32i2We4uByoKPv1d0OD2xebDv4hbs3fm11SMw8Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.8.0' - dependencies: - '@typescript-eslint/types': 8.18.2 - '@typescript-eslint/visitor-keys': 8.18.2 - debug: 4.4.0 - fast-glob: 3.3.2 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.6.3 - ts-api-utils: 1.4.3(typescript@5.7.2) - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/utils@8.18.2(eslint@9.17.0)(typescript@5.7.2): - resolution: {integrity: sha512-Cr4A0H7DtVIPkauj4sTSXVl+VBWewE9/o40KcF3TV9aqDEOWoXF3/+oRXNby3DYzZeCATvbdksYsGZzplwnK/Q==} + '@typescript-eslint/utils@8.19.1': + resolution: {integrity: sha512-IxG5gLO0Ne+KaUc8iW1A+XuKLd63o4wlbI1Zp692n1xojCl/THvgIKXJXBZixTh5dd5+yTJ/VXH7GJaaw21qXA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0) - '@typescript-eslint/scope-manager': 8.18.2 - '@typescript-eslint/types': 8.18.2 - '@typescript-eslint/typescript-estree': 8.18.2(typescript@5.7.2) - eslint: 9.17.0 - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/visitor-keys@8.18.2: - resolution: {integrity: sha512-zORcwn4C3trOWiCqFQP1x6G3xTRyZ1LYydnj51cRnJ6hxBlr/cKPckk+PKPUw/fXmvfKTcw7bwY3w9izgx5jZw==} + '@typescript-eslint/visitor-keys@8.19.1': + resolution: {integrity: sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dependencies: - '@typescript-eslint/types': 8.18.2 - eslint-visitor-keys: 4.2.0 - dev: true - /acorn-jsx@5.3.2(acorn@8.14.0): + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - acorn: 8.14.0 - dev: true - /acorn-typescript@1.4.13(acorn@8.14.0): + acorn-typescript@1.4.13: resolution: {integrity: sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==} peerDependencies: acorn: '>=8.9.0' - dependencies: - acorn: 8.14.0 - dev: true - /acorn@8.14.0: + acorn@8.14.0: resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} hasBin: true - dev: true - /ajv@6.12.6: + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - dev: true - /ansi-regex@5.0.1: + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - dev: true - /ansi-regex@6.1.0: + ansi-regex@6.1.0: resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} engines: {node: '>=12'} - dev: true - /ansi-styles@4.3.0: + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - dependencies: - color-convert: 2.0.1 - dev: true - /ansi-styles@6.2.1: + ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - dev: true - /any-promise@1.3.0: + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - dev: true - /anymatch@3.1.3: + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - dev: true - /arg@5.0.2: + arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - dev: true - /argon2@0.41.1: + argon2@0.41.1: resolution: {integrity: sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==} engines: {node: '>=16.17.0'} - requiresBuild: true - dependencies: - '@phc/format': 1.0.0 - node-addon-api: 8.3.0 - node-gyp-build: 4.8.4 - dev: false - /argparse@2.0.1: + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: true - /aria-query@5.3.2: + aria-query@5.3.2: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} - dev: true - /autoprefixer@10.4.20(postcss@8.4.49): + autoprefixer@10.4.20: resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: postcss: ^8.1.0 - dependencies: - browserslist: 4.24.3 - caniuse-lite: 1.0.30001690 - fraction.js: 4.3.7 - normalize-range: 0.1.2 - picocolors: 1.1.1 - postcss: 8.4.49 - postcss-value-parser: 4.2.0 - dev: true - /axobject-query@4.1.0: + axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} - dev: true - /balanced-match@1.0.2: + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true - /base64-js@1.5.1: + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: false - /better-sqlite3@11.7.0: - resolution: {integrity: sha512-mXpa5jnIKKHeoGzBrUJrc65cXFKcILGZpU3FXR0pradUEm9MA7UZz02qfEejaMcm9iXrSOCenwwYMJ/tZ1y5Ig==} - requiresBuild: true - dependencies: - bindings: 1.5.0 - prebuild-install: 7.1.2 - dev: false + better-sqlite3@11.7.2: + resolution: {integrity: sha512-10a57cHVDmfNQS4jrZ9AH2t+2ekzYh5Rhbcnb4ytpmYweoLdogDmyTt5D+hLiY9b44Mx9foowb/4iXBTO2yP3Q==} - /binary-extensions@2.3.0: + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - dev: true - /bindings@1.5.0: + bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - dependencies: - file-uri-to-path: 1.0.0 - dev: false - /bl@4.1.0: + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.2 - dev: false - /brace-expansion@1.1.11: + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - dev: true - /brace-expansion@2.0.1: + brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - dependencies: - balanced-match: 1.0.2 - dev: true - /braces@3.0.3: + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - dependencies: - fill-range: 7.1.1 - dev: true - /browserslist@4.24.3: - resolution: {integrity: sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==} + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - dependencies: - caniuse-lite: 1.0.30001690 - electron-to-chromium: 1.5.76 - node-releases: 2.0.19 - update-browserslist-db: 1.1.1(browserslist@4.24.3) - dev: true - /buffer-equal-constant-time@1.0.1: + buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - dev: false - /buffer-from@1.1.2: + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - dev: true - /buffer@5.7.1: + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - dev: false - /callsites@3.1.0: + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - dev: true - /camelcase-css@2.0.1: + camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - dev: true - /caniuse-lite@1.0.30001690: - resolution: {integrity: sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==} - dev: true + caniuse-lite@1.0.30001692: + resolution: {integrity: sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==} - /chalk@4.1.2: + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - dev: true - /chokidar@3.6.0: + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - dev: true - /chokidar@4.0.3: + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} - dependencies: - readdirp: 4.0.2 - dev: true - /chownr@1.1.4: + chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - dev: false - /clsx@2.1.1: + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - dev: true - /color-convert@2.0.1: + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - dependencies: - color-name: 1.1.4 - dev: true - /color-name@1.1.4: + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true - /commander@4.1.1: + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} - dev: true - /commondir@1.0.1: + commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - dev: true - /concat-map@0.0.1: + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - dev: true - /confbox@0.1.8: + confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - dev: true - /cookie@0.6.0: + cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} - dev: true - /cron-parser@4.9.0: + cron-parser@4.9.0: resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} engines: {node: '>=12.0.0'} - dependencies: - luxon: 3.5.0 - dev: false - /cross-spawn@7.0.6: + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - dev: true - /cssesc@3.0.0: + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} hasBin: true - dev: true - /debug@4.4.0: + debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} peerDependencies: @@ -1755,64 +1100,43 @@ packages: peerDependenciesMeta: supports-color: optional: true - dependencies: - ms: 2.1.3 - dev: true - /decompress-response@6.0.0: + decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} - dependencies: - mimic-response: 3.1.0 - dev: false - /deep-extend@0.6.0: + deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} - dev: false - /deep-is@0.1.4: + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true - /deepmerge@4.3.1: + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - dev: true - /detect-libc@2.0.3: + detect-libc@2.0.3: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} - dev: false - /devalue@5.1.1: + devalue@5.1.1: resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} - dev: true - /dexie@4.0.10: + dexie@4.0.10: resolution: {integrity: sha512-eM2RzuR3i+M046r2Q0Optl3pS31qTWf8aFuA7H9wnsHTwl8EPvroVLwvQene/6paAs39Tbk6fWZcn2aZaHkc/w==} - dev: true - /didyoumean@1.2.2: + didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - dev: true - /dlv@1.1.3: + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - dev: true - /drizzle-kit@0.22.8: + drizzle-kit@0.22.8: resolution: {integrity: sha512-VjI4wsJjk3hSqHSa3TwBf+uvH6M6pRHyxyoVbt935GUzP9tUR/BRZ+MhEJNgryqbzN2Za1KP0eJMTgKEPsalYQ==} hasBin: true - dependencies: - '@esbuild-kit/esm-loader': 2.6.5 - esbuild: 0.19.12 - esbuild-register: 3.6.0(esbuild@0.19.12) - transitivePeerDependencies: - - supports-color - dev: true - /drizzle-orm@0.33.0(@types/better-sqlite3@7.6.12)(better-sqlite3@11.7.0): + drizzle-orm@0.33.0: resolution: {integrity: sha512-SHy72R2Rdkz0LEq0PSG/IdvnT3nGiWuRk+2tXZQ90GVq/XQhpCzu/EFT3V2rox+w8MlkBQxifF8pCStNYnERfA==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' @@ -1900,172 +1224,66 @@ packages: optional: true sqlite3: optional: true - dependencies: - '@types/better-sqlite3': 7.6.12 - better-sqlite3: 11.7.0 - dev: false - /eastasianwidth@0.2.0: + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true - /ecdsa-sig-formatter@1.0.11: + ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - dependencies: - safe-buffer: 5.2.1 - dev: false - /electron-to-chromium@1.5.76: - resolution: {integrity: sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==} - dev: true + electron-to-chromium@1.5.79: + resolution: {integrity: sha512-nYOxJNxQ9Om4EC88BE4pPoNI8xwSFf8pU/BAeOl4Hh/b/i6V4biTAzwV7pXi3ARKeoYO5JZKMIXTryXSVer5RA==} - /emoji-regex@8.0.0: + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true - /emoji-regex@9.2.2: + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - dev: true - /end-of-stream@1.4.4: + end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - dependencies: - once: 1.4.0 - dev: false - /esbuild-register@3.6.0(esbuild@0.19.12): + esbuild-register@3.6.0: resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} peerDependencies: esbuild: '>=0.12 <1' - dependencies: - debug: 4.4.0 - esbuild: 0.19.12 - transitivePeerDependencies: - - supports-color - dev: true - /esbuild@0.18.20: + esbuild@0.18.20: resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} engines: {node: '>=12'} hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/android-arm': 0.18.20 - '@esbuild/android-arm64': 0.18.20 - '@esbuild/android-x64': 0.18.20 - '@esbuild/darwin-arm64': 0.18.20 - '@esbuild/darwin-x64': 0.18.20 - '@esbuild/freebsd-arm64': 0.18.20 - '@esbuild/freebsd-x64': 0.18.20 - '@esbuild/linux-arm': 0.18.20 - '@esbuild/linux-arm64': 0.18.20 - '@esbuild/linux-ia32': 0.18.20 - '@esbuild/linux-loong64': 0.18.20 - '@esbuild/linux-mips64el': 0.18.20 - '@esbuild/linux-ppc64': 0.18.20 - '@esbuild/linux-riscv64': 0.18.20 - '@esbuild/linux-s390x': 0.18.20 - '@esbuild/linux-x64': 0.18.20 - '@esbuild/netbsd-x64': 0.18.20 - '@esbuild/openbsd-x64': 0.18.20 - '@esbuild/sunos-x64': 0.18.20 - '@esbuild/win32-arm64': 0.18.20 - '@esbuild/win32-ia32': 0.18.20 - '@esbuild/win32-x64': 0.18.20 - dev: true - /esbuild@0.19.12: + esbuild@0.19.12: resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} engines: {node: '>=12'} hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/aix-ppc64': 0.19.12 - '@esbuild/android-arm': 0.19.12 - '@esbuild/android-arm64': 0.19.12 - '@esbuild/android-x64': 0.19.12 - '@esbuild/darwin-arm64': 0.19.12 - '@esbuild/darwin-x64': 0.19.12 - '@esbuild/freebsd-arm64': 0.19.12 - '@esbuild/freebsd-x64': 0.19.12 - '@esbuild/linux-arm': 0.19.12 - '@esbuild/linux-arm64': 0.19.12 - '@esbuild/linux-ia32': 0.19.12 - '@esbuild/linux-loong64': 0.19.12 - '@esbuild/linux-mips64el': 0.19.12 - '@esbuild/linux-ppc64': 0.19.12 - '@esbuild/linux-riscv64': 0.19.12 - '@esbuild/linux-s390x': 0.19.12 - '@esbuild/linux-x64': 0.19.12 - '@esbuild/netbsd-x64': 0.19.12 - '@esbuild/openbsd-x64': 0.19.12 - '@esbuild/sunos-x64': 0.19.12 - '@esbuild/win32-arm64': 0.19.12 - '@esbuild/win32-ia32': 0.19.12 - '@esbuild/win32-x64': 0.19.12 - dev: true - /esbuild@0.21.5: + esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 - dev: true - /escalade@3.2.0: + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - dev: true - /escape-string-regexp@4.0.0: + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - dev: true - /eslint-compat-utils@0.5.1(eslint@9.17.0): + eslint-compat-utils@0.5.1: resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} engines: {node: '>=12'} peerDependencies: eslint: '>=6.0.0' - dependencies: - eslint: 9.17.0 - semver: 7.6.3 - dev: true - /eslint-config-prettier@9.1.0(eslint@9.17.0): + eslint-config-prettier@9.1.0: resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} hasBin: true peerDependencies: eslint: '>=7.0.0' - dependencies: - eslint: 9.17.0 - dev: true - /eslint-plugin-svelte@2.46.1(eslint@9.17.0)(svelte@5.16.0): + eslint-plugin-svelte@2.46.1: resolution: {integrity: sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==} engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: @@ -2074,62 +1292,30 @@ packages: peerDependenciesMeta: svelte: optional: true - dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0) - '@jridgewell/sourcemap-codec': 1.5.0 - eslint: 9.17.0 - eslint-compat-utils: 0.5.1(eslint@9.17.0) - esutils: 2.0.3 - known-css-properties: 0.35.0 - postcss: 8.4.49 - postcss-load-config: 3.1.4(postcss@8.4.49) - postcss-safe-parser: 6.0.0(postcss@8.4.49) - postcss-selector-parser: 6.1.2 - semver: 7.6.3 - svelte: 5.16.0 - svelte-eslint-parser: 0.43.0(svelte@5.16.0) - transitivePeerDependencies: - - ts-node - dev: true - /eslint-plugin-tailwindcss@3.17.5(tailwindcss@3.4.17): + eslint-plugin-tailwindcss@3.17.5: resolution: {integrity: sha512-8Mi7p7dm+mO1dHgRHHFdPu4RDTBk69Cn4P0B40vRQR+MrguUpwmKwhZy1kqYe3Km8/4nb+cyrCF+5SodOEmaow==} engines: {node: '>=18.12.0'} peerDependencies: tailwindcss: ^3.4.0 - dependencies: - fast-glob: 3.3.2 - postcss: 8.4.49 - tailwindcss: 3.4.17 - dev: true - /eslint-scope@7.2.2: + eslint-scope@7.2.2: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - dev: true - /eslint-scope@8.2.0: + eslint-scope@8.2.0: resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - dev: true - /eslint-visitor-keys@3.4.3: + eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - /eslint-visitor-keys@4.2.0: + eslint-visitor-keys@4.2.0: resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dev: true - /eslint@9.17.0: + eslint@9.17.0: resolution: {integrity: sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true @@ -2138,826 +1324,498 @@ packages: peerDependenciesMeta: jiti: optional: true - dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0) - '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.19.1 - '@eslint/core': 0.9.1 - '@eslint/eslintrc': 3.2.0 - '@eslint/js': 9.17.0 - '@eslint/plugin-kit': 0.2.4 - '@humanfs/node': 0.16.6 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.1 - '@types/estree': 1.0.6 - '@types/json-schema': 7.0.15 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.0 - escape-string-regexp: 4.0.0 - eslint-scope: 8.2.0 - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 - esquery: 1.6.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 - find-up: 5.0.0 - glob-parent: 6.0.2 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - transitivePeerDependencies: - - supports-color - dev: true - /esm-env@1.2.1: - resolution: {integrity: sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==} - dev: true + esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} - /espree@10.3.0: + espree@10.3.0: resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) - eslint-visitor-keys: 4.2.0 - dev: true - /espree@9.6.1: + espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) - eslint-visitor-keys: 3.4.3 - dev: true - /esquery@1.6.0: + esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} - dependencies: - estraverse: 5.3.0 - dev: true - /esrap@1.3.2: + esrap@1.3.2: resolution: {integrity: sha512-C4PXusxYhFT98GjLSmb20k9PREuUdporer50dhzGuJu9IJXktbMddVCMLAERl5dAHyAi73GWWCE4FVHGP1794g==} - dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - dev: true - /esrecurse@4.3.0: + esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} - dependencies: - estraverse: 5.3.0 - dev: true - /estraverse@5.3.0: + estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} - dev: true - /estree-walker@2.0.2: + estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - dev: true - /esutils@2.0.3: + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - dev: true - /expand-template@2.0.3: + expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} - dev: false - /fast-deep-equal@3.1.3: + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - dev: true - /fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - dev: true - /fast-json-stable-stringify@2.1.0: + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - dev: true - /fast-levenshtein@2.0.6: + fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: true - /fastq@1.18.0: + fastq@1.18.0: resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} - dependencies: - reusify: 1.0.4 - dev: true - /fdir@6.4.2(picomatch@4.0.2): + fdir@6.4.2: resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: picomatch: optional: true - dependencies: - picomatch: 4.0.2 - dev: true - /file-entry-cache@8.0.0: + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - dependencies: - flat-cache: 4.0.1 - dev: true - /file-saver@2.0.5: + file-saver@2.0.5: resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} - dev: true - /file-uri-to-path@1.0.0: + file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - dev: false - /fill-range@7.1.1: + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - dependencies: - to-regex-range: 5.0.1 - dev: true - /find-up@5.0.0: + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - dev: true - /flat-cache@4.0.1: + flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - dependencies: - flatted: 3.3.2 - keyv: 4.5.4 - dev: true - /flatted@3.3.2: + flatted@3.3.2: resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} - dev: true - /foreground-child@3.3.0: + foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - dev: true - /fraction.js@4.3.7: + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - dev: true - /fs-constants@1.0.0: + fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - dev: false - /fsevents@2.3.3: + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - requiresBuild: true - dev: true - optional: true - /function-bind@1.1.2: + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - dev: true - /get-tsconfig@4.8.1: + get-tsconfig@4.8.1: resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} - dependencies: - resolve-pkg-maps: 1.0.0 - dev: true - /github-from-package@0.0.0: + github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - dev: false - /glob-parent@5.1.2: + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} - dependencies: - is-glob: 4.0.3 - dev: true - /glob-parent@6.0.2: + glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - dependencies: - is-glob: 4.0.3 - dev: true - /glob@10.4.5: + glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true - dependencies: - foreground-child: 3.3.0 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - dev: true - /globals@14.0.0: + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - dev: true - /globals@15.14.0: + globals@15.14.0: resolution: {integrity: sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==} engines: {node: '>=18'} - dev: true - /globalyzer@0.1.0: + globalyzer@0.1.0: resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} - dev: true - /globrex@0.1.2: + globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - dev: true - /graphemer@1.4.0: + graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - dev: true - /has-flag@4.0.0: + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - dev: true - /hasown@2.0.2: + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - dependencies: - function-bind: 1.1.2 - dev: true - /heic2any@0.0.4: + heic2any@0.0.4: resolution: {integrity: sha512-3lLnZiDELfabVH87htnRolZ2iehX9zwpRyGNz22GKXIu0fznlblf0/ftppXKNqS26dqFSeqfIBhAmAj/uSp0cA==} - dev: true - /ieee754@1.2.1: + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: false - /ignore@5.3.2: + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - dev: true - /import-fresh@3.3.0: + import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - dev: true - /import-meta-resolve@4.1.0: + import-meta-resolve@4.1.0: resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} - dev: true - /imurmurhash@0.1.4: + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - dev: true - /inherits@2.0.4: + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: false - /ini@1.3.8: + ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - dev: false - /is-binary-path@2.1.0: + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - dependencies: - binary-extensions: 2.3.0 - dev: true - /is-core-module@2.16.1: + is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} - dependencies: - hasown: 2.0.2 - dev: true - /is-extglob@2.1.1: + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - dev: true - /is-fullwidth-code-point@3.0.0: + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - dev: true - /is-glob@4.0.3: + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - dependencies: - is-extglob: 2.1.1 - dev: true - /is-module@1.0.0: + is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} - dev: true - /is-number@7.0.0: + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - dev: true - /is-reference@1.2.1: + is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - dependencies: - '@types/estree': 1.0.6 - dev: true - /is-reference@3.0.3: + is-reference@3.0.3: resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} - dependencies: - '@types/estree': 1.0.6 - dev: true - /isexe@2.0.0: + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true - /jackspeak@3.4.3: + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - dev: true - /jiti@1.21.7: + jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true - dev: true - /js-yaml@4.1.0: + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - dependencies: - argparse: 2.0.1 - dev: true - /json-buffer@3.0.1: + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - dev: true - /json-schema-traverse@0.4.1: + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: true - /json-stable-stringify-without-jsonify@1.0.1: + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - dev: true - /jsonwebtoken@9.0.2: + jsonwebtoken@9.0.2: resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} engines: {node: '>=12', npm: '>=6'} - dependencies: - jws: 3.2.2 - lodash.includes: 4.3.0 - lodash.isboolean: 3.0.3 - lodash.isinteger: 4.0.4 - lodash.isnumber: 3.0.3 - lodash.isplainobject: 4.0.6 - lodash.isstring: 4.0.1 - lodash.once: 4.1.1 - ms: 2.1.3 - semver: 7.6.3 - dev: false - /jwa@1.4.1: + jwa@1.4.1: resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} - dependencies: - buffer-equal-constant-time: 1.0.1 - ecdsa-sig-formatter: 1.0.11 - safe-buffer: 5.2.1 - dev: false - /jws@3.2.2: + jws@3.2.2: resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} - dependencies: - jwa: 1.4.1 - safe-buffer: 5.2.1 - dev: false - /keyv@4.5.4: + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - dependencies: - json-buffer: 3.0.1 - dev: true - /kleur@4.1.5: + kleur@4.1.5: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} - dev: true - /known-css-properties@0.35.0: + known-css-properties@0.35.0: resolution: {integrity: sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==} - dev: true - /kolorist@1.8.0: + kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - dev: true - /levn@0.4.1: + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - dev: true - /lilconfig@2.1.0: + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} - dev: true - /lilconfig@3.1.3: + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} - dev: true - /lines-and-columns@1.2.4: + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - dev: true - /local-pkg@0.5.1: + local-pkg@0.5.1: resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} engines: {node: '>=14'} - dependencies: - mlly: 1.7.3 - pkg-types: 1.2.1 - dev: true - /locate-character@3.0.0: + locate-character@3.0.0: resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} - dev: true - /locate-path@6.0.0: + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - dependencies: - p-locate: 5.0.0 - dev: true - /lodash.includes@4.3.0: + lodash.includes@4.3.0: resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} - dev: false - /lodash.isboolean@3.0.3: + lodash.isboolean@3.0.3: resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} - dev: false - /lodash.isinteger@4.0.4: + lodash.isinteger@4.0.4: resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} - dev: false - /lodash.isnumber@3.0.3: + lodash.isnumber@3.0.3: resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} - dev: false - /lodash.isplainobject@4.0.6: + lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - dev: false - /lodash.isstring@4.0.1: + lodash.isstring@4.0.1: resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} - dev: false - /lodash.merge@4.6.2: + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: true - /lodash.once@4.1.1: + lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} - dev: false - /long-timeout@0.1.1: + long-timeout@0.1.1: resolution: {integrity: sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==} - dev: false - /lru-cache@10.4.3: + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - dev: true - /luxon@3.5.0: + luxon@3.5.0: resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==} engines: {node: '>=12'} - dev: false - /magic-string@0.30.17: + magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - dev: true - /merge2@1.4.1: + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - dev: true - /micromatch@4.0.8: + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - dev: true - /mime@4.0.6: + mime@4.0.6: resolution: {integrity: sha512-4rGt7rvQHBbaSOF9POGkk1ocRP16Md1x36Xma8sz8h8/vfCUI2OtEIeCqe4Ofes853x4xDoPiFLIT47J5fI/7A==} engines: {node: '>=16'} hasBin: true - dev: true - /mimic-response@3.1.0: + mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} - dev: false - /minimatch@3.1.2: + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - dependencies: - brace-expansion: 1.1.11 - dev: true - /minimatch@9.0.5: + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - dependencies: - brace-expansion: 2.0.1 - dev: true - /minimist@1.2.8: + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: false - /minipass@7.1.2: + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - dev: true - /mkdirp-classic@0.5.3: + mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - dev: false - /mlly@1.7.3: + mlly@1.7.3: resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==} - dependencies: - acorn: 8.14.0 - pathe: 1.1.2 - pkg-types: 1.2.1 - ufo: 1.5.4 - dev: true - /mri@1.2.0: + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} - dev: true - /mrmime@2.0.0: + mrmime@2.0.0: resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} engines: {node: '>=10'} - dev: true - /ms@2.1.3: + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - /mz@2.7.0: + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - dev: true - /nanoid@3.3.8: + nanoid@3.3.8: resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - dev: true - /napi-build-utils@1.0.2: + napi-build-utils@1.0.2: resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} - dev: false - /natural-compare@1.4.0: + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - dev: true - /node-abi@3.71.0: + node-abi@3.71.0: resolution: {integrity: sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==} engines: {node: '>=10'} - dependencies: - semver: 7.6.3 - dev: false - /node-addon-api@8.3.0: + node-addon-api@8.3.0: resolution: {integrity: sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==} engines: {node: ^18 || ^20 || >= 21} - dev: false - /node-gyp-build@4.8.4: + node-gyp-build@4.8.4: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true - dev: false - /node-releases@2.0.19: + node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} - dev: true - /node-schedule@2.1.1: + node-schedule@2.1.1: resolution: {integrity: sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==} engines: {node: '>=6'} - dependencies: - cron-parser: 4.9.0 - long-timeout: 0.1.1 - sorted-array-functions: 1.3.0 - dev: false - /normalize-path@3.0.0: + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - dev: true - /normalize-range@0.1.2: + normalize-range@0.1.2: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} - dev: true - /object-assign@4.1.1: + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - dev: true - /object-hash@3.0.0: + object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - dev: true - /once@1.4.0: + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - dependencies: - wrappy: 1.0.2 - dev: false - /optionator@0.9.4: + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - dev: true - /p-limit@3.1.0: + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} - dependencies: - yocto-queue: 0.1.0 - dev: true - /p-locate@5.0.0: + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - dependencies: - p-limit: 3.1.0 - dev: true - /package-json-from-dist@1.0.1: + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - dev: true - /package-manager-detector@0.2.8: + package-manager-detector@0.2.8: resolution: {integrity: sha512-ts9KSdroZisdvKMWVAVCXiKqnqNfXz4+IbrBG8/BWx/TR5le+jfenvoBuIZ6UWM9nz47W7AbD9qYfAwfWMIwzA==} - dev: true - /parent-module@1.0.1: + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - dependencies: - callsites: 3.1.0 - dev: true - /path-exists@4.0.0: + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - dev: true - /path-key@3.1.1: + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - dev: true - /path-parse@1.0.7: + path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: true - /path-scurry@1.11.1: + path-scurry@1.11.1: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - dev: true - /pathe@1.1.2: + pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - dev: true - /picocolors@1.1.1: + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - dev: true - /picomatch@2.3.1: + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - dev: true - /picomatch@4.0.2: + picomatch@4.0.2: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} - dev: true - /pify@2.3.0: + pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} - dev: true - /pirates@4.0.6: + pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - dev: true - /pkg-types@1.2.1: - resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==} - dependencies: - confbox: 0.1.8 - mlly: 1.7.3 - pathe: 1.1.2 - dev: true + pkg-types@1.3.0: + resolution: {integrity: sha512-kS7yWjVFCkIw9hqdJBoMxDdzEngmkr5FXeWZZfQ6GoYacjVnsW6l2CcYW/0ThD0vF4LPJgVYnrg4d0uuhwYQbg==} - /postcss-import@15.1.0(postcss@8.4.49): + postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} peerDependencies: postcss: ^8.0.0 - dependencies: - postcss: 8.4.49 - postcss-value-parser: 4.2.0 - read-cache: 1.0.0 - resolve: 1.22.10 - dev: true - /postcss-js@4.0.1(postcss@8.4.49): + postcss-js@4.0.1: resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} engines: {node: ^12 || ^14 || >= 16} peerDependencies: postcss: ^8.4.21 - dependencies: - camelcase-css: 2.0.1 - postcss: 8.4.49 - dev: true - /postcss-load-config@3.1.4(postcss@8.4.49): + postcss-load-config@3.1.4: resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} engines: {node: '>= 10'} peerDependencies: @@ -2968,13 +1826,8 @@ packages: optional: true ts-node: optional: true - dependencies: - lilconfig: 2.1.0 - postcss: 8.4.49 - yaml: 1.10.2 - dev: true - /postcss-load-config@4.0.2(postcss@8.4.49): + postcss-load-config@4.0.2: resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} engines: {node: '>= 14'} peerDependencies: @@ -2985,96 +1838,52 @@ packages: optional: true ts-node: optional: true - dependencies: - lilconfig: 3.1.3 - postcss: 8.4.49 - yaml: 2.6.1 - dev: true - /postcss-nested@6.2.0(postcss@8.4.49): + postcss-nested@6.2.0: resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.2.14 - dependencies: - postcss: 8.4.49 - postcss-selector-parser: 6.1.2 - dev: true - /postcss-safe-parser@6.0.0(postcss@8.4.49): + postcss-safe-parser@6.0.0: resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.3.3 - dependencies: - postcss: 8.4.49 - dev: true - /postcss-scss@4.0.9(postcss@8.4.49): + postcss-scss@4.0.9: resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.4.29 - dependencies: - postcss: 8.4.49 - dev: true - /postcss-selector-parser@6.1.2: + postcss-selector-parser@6.1.2: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - dev: true - /postcss-value-parser@4.2.0: + postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - dev: true - /postcss@8.4.49: + postcss@8.4.49: resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.8 - picocolors: 1.1.1 - source-map-js: 1.2.1 - dev: true - /prebuild-install@7.1.2: + prebuild-install@7.1.2: resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} engines: {node: '>=10'} hasBin: true - dependencies: - detect-libc: 2.0.3 - expand-template: 2.0.3 - github-from-package: 0.0.0 - minimist: 1.2.8 - mkdirp-classic: 0.5.3 - napi-build-utils: 1.0.2 - node-abi: 3.71.0 - pump: 3.0.2 - rc: 1.2.8 - simple-get: 4.0.1 - tar-fs: 2.1.1 - tunnel-agent: 0.6.0 - dev: false - /prelude-ls@1.2.1: + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - dev: true - /prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.16.0): + prettier-plugin-svelte@3.3.2: resolution: {integrity: sha512-kRPjH8wSj2iu+dO+XaUv4vD8qr5mdDmlak3IT/7AOgGIMRG86z/EHOLauFcClKEnOUf4A4nOA7sre5KrJD4Raw==} peerDependencies: prettier: ^3.0.0 svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 - dependencies: - prettier: 3.4.2 - svelte: 5.16.0 - dev: true - /prettier-plugin-tailwindcss@0.6.9(prettier-plugin-svelte@3.3.2)(prettier@3.4.2): + prettier-plugin-tailwindcss@0.6.9: resolution: {integrity: sha512-r0i3uhaZAXYP0At5xGfJH876W3HHGHDp+LCRUJrs57PBeQ6mYHMwr25KH8NPX44F2yGTvdnH7OqCshlQx183Eg==} engines: {node: '>=14.21.3'} peerDependencies: @@ -3128,302 +1937,165 @@ packages: optional: true prettier-plugin-svelte: optional: true - dependencies: - prettier: 3.4.2 - prettier-plugin-svelte: 3.3.2(prettier@3.4.2)(svelte@5.16.0) - dev: true - /prettier@3.4.2: + prettier@3.4.2: resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} engines: {node: '>=14'} hasBin: true - dev: true - /pump@3.0.2: + pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} - dependencies: - end-of-stream: 1.4.4 - once: 1.4.0 - dev: false - /punycode@2.3.1: + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - dev: true - /queue-microtask@1.2.3: + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: true - /rc@1.2.8: + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.8 - strip-json-comments: 2.0.1 - dev: false - /read-cache@1.0.0: + read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - dependencies: - pify: 2.3.0 - dev: true - /readable-stream@3.6.2: + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - dev: false - /readdirp@3.6.0: + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - dependencies: - picomatch: 2.3.1 - dev: true - /readdirp@4.0.2: + readdirp@4.0.2: resolution: {integrity: sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==} engines: {node: '>= 14.16.0'} - dev: true - /resolve-from@4.0.0: + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - dev: true - /resolve-pkg-maps@1.0.0: + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - dev: true - /resolve@1.22.10: + resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} hasBin: true - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - dev: true - /reusify@1.0.4: + reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: true - /rollup@4.29.1: - resolution: {integrity: sha512-RaJ45M/kmJUzSWDs1Nnd5DdV4eerC98idtUOVr6FfKcgxqvjwHmxc5upLF9qZU9EpsVzzhleFahrT3shLuJzIw==} + rollup@4.30.1: + resolution: {integrity: sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - dependencies: - '@types/estree': 1.0.6 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.29.1 - '@rollup/rollup-android-arm64': 4.29.1 - '@rollup/rollup-darwin-arm64': 4.29.1 - '@rollup/rollup-darwin-x64': 4.29.1 - '@rollup/rollup-freebsd-arm64': 4.29.1 - '@rollup/rollup-freebsd-x64': 4.29.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.29.1 - '@rollup/rollup-linux-arm-musleabihf': 4.29.1 - '@rollup/rollup-linux-arm64-gnu': 4.29.1 - '@rollup/rollup-linux-arm64-musl': 4.29.1 - '@rollup/rollup-linux-loongarch64-gnu': 4.29.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.29.1 - '@rollup/rollup-linux-riscv64-gnu': 4.29.1 - '@rollup/rollup-linux-s390x-gnu': 4.29.1 - '@rollup/rollup-linux-x64-gnu': 4.29.1 - '@rollup/rollup-linux-x64-musl': 4.29.1 - '@rollup/rollup-win32-arm64-msvc': 4.29.1 - '@rollup/rollup-win32-ia32-msvc': 4.29.1 - '@rollup/rollup-win32-x64-msvc': 4.29.1 - fsevents: 2.3.3 - dev: true - /run-parallel@1.2.0: + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - dependencies: - queue-microtask: 1.2.3 - dev: true - /sade@1.8.1: + sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} - dependencies: - mri: 1.2.0 - dev: true - /safe-buffer@5.2.1: + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - dev: false - /semver@7.6.3: + semver@7.6.3: resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} hasBin: true - /set-cookie-parser@2.7.1: + set-cookie-parser@2.7.1: resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} - dev: true - /shebang-command@2.0.0: + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} - dependencies: - shebang-regex: 3.0.0 - dev: true - /shebang-regex@3.0.0: + shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - dev: true - /signal-exit@4.1.0: + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - dev: true - /simple-concat@1.0.1: + simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - dev: false - /simple-get@4.0.1: + simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - dependencies: - decompress-response: 6.0.0 - once: 1.4.0 - simple-concat: 1.0.1 - dev: false - /sirv@3.0.0: + sirv@3.0.0: resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==} engines: {node: '>=18'} - dependencies: - '@polka/url': 1.0.0-next.28 - mrmime: 2.0.0 - totalist: 3.0.1 - dev: true - /sorted-array-functions@1.3.0: + sorted-array-functions@1.3.0: resolution: {integrity: sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==} - dev: false - /source-map-js@1.2.1: + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - dev: true - /source-map-support@0.5.21: + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - dev: true - /source-map@0.6.1: + source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - dev: true - /string-width@4.2.3: + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - dev: true - /string-width@5.1.2: + string-width@5.1.2: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 - dev: true - /string_decoder@1.3.0: + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - dependencies: - safe-buffer: 5.2.1 - dev: false - /strip-ansi@6.0.1: + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - dependencies: - ansi-regex: 5.0.1 - dev: true - /strip-ansi@7.1.0: + strip-ansi@7.1.0: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} - dependencies: - ansi-regex: 6.1.0 - dev: true - /strip-json-comments@2.0.1: + strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} - dev: false - /strip-json-comments@3.1.1: + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - dev: true - /sucrase@3.35.0: + sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true - dependencies: - '@jridgewell/gen-mapping': 0.3.8 - commander: 4.1.1 - glob: 10.4.5 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.6 - ts-interface-checker: 0.1.13 - dev: true - /supports-color@7.2.0: + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} - dependencies: - has-flag: 4.0.0 - dev: true - /supports-preserve-symlinks-flag@1.0.0: + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - dev: true - /svelte-check@4.1.1(svelte@5.16.0)(typescript@5.7.2): - resolution: {integrity: sha512-NfaX+6Qtc8W/CyVGS/F7/XdiSSyXz+WGYA9ZWV3z8tso14V2vzjfXviKaTFEzB7g8TqfgO2FOzP6XT4ApSTUTw==} + svelte-check@4.1.3: + resolution: {integrity: sha512-IEMoQDH+TrPKwKeIyJim+PU8FxnzQMXsFHR/ldErkHpPXEGHCujHUXiR8jg6qDMqzsif5BbDOUFORltu87ex7g==} engines: {node: '>= 18.0.0'} hasBin: true peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.0 typescript: '>=5.0.0' - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - chokidar: 4.0.3 - fdir: 6.4.2(picomatch@4.0.2) - picocolors: 1.1.1 - sade: 1.8.1 - svelte: 5.16.0 - typescript: 5.7.2 - transitivePeerDependencies: - - picomatch - dev: true - /svelte-eslint-parser@0.43.0(svelte@5.16.0): + svelte-eslint-parser@0.43.0: resolution: {integrity: sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -3431,178 +2103,79 @@ packages: peerDependenciesMeta: svelte: optional: true - dependencies: - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - postcss: 8.4.49 - postcss-scss: 4.0.9(postcss@8.4.49) - svelte: 5.16.0 - dev: true - /svelte@5.16.0: - resolution: {integrity: sha512-Ygqsiac6UogVED2ruKclU+pOeMThxWtp9LG+li7BXeDKC2paVIsRTMkNmcON4Zejerd1s5sZHWx6ZtU85xklVg==} + svelte@5.17.1: + resolution: {integrity: sha512-HitqD0XhU9OEytPuux/XYzxle4+7D8+fIb1tHbwMzOtBzDZZO+ESEuwMbahJ/3JoklfmRPB/Gzp74L87Qrxfpw==} engines: {node: '>=18'} - dependencies: - '@ampproject/remapping': 2.3.0 - '@jridgewell/sourcemap-codec': 1.5.0 - '@types/estree': 1.0.6 - acorn: 8.14.0 - acorn-typescript: 1.4.13(acorn@8.14.0) - aria-query: 5.3.2 - axobject-query: 4.1.0 - clsx: 2.1.1 - esm-env: 1.2.1 - esrap: 1.3.2 - is-reference: 3.0.3 - locate-character: 3.0.0 - magic-string: 0.30.17 - zimmerframe: 1.1.2 - dev: true - /tailwindcss@3.4.17: + tailwindcss@3.4.17: resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==} engines: {node: '>=14.0.0'} hasBin: true - dependencies: - '@alloc/quick-lru': 5.2.0 - arg: 5.0.2 - chokidar: 3.6.0 - didyoumean: 1.2.2 - dlv: 1.1.3 - fast-glob: 3.3.2 - glob-parent: 6.0.2 - is-glob: 4.0.3 - jiti: 1.21.7 - lilconfig: 3.1.3 - micromatch: 4.0.8 - normalize-path: 3.0.0 - object-hash: 3.0.0 - picocolors: 1.1.1 - postcss: 8.4.49 - postcss-import: 15.1.0(postcss@8.4.49) - postcss-js: 4.0.1(postcss@8.4.49) - postcss-load-config: 4.0.2(postcss@8.4.49) - postcss-nested: 6.2.0(postcss@8.4.49) - postcss-selector-parser: 6.1.2 - resolve: 1.22.10 - sucrase: 3.35.0 - transitivePeerDependencies: - - ts-node - dev: true - /tar-fs@2.1.1: + tar-fs@2.1.1: resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} - dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.2 - tar-stream: 2.2.0 - dev: false - /tar-stream@2.2.0: + tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} - dependencies: - bl: 4.1.0 - end-of-stream: 1.4.4 - fs-constants: 1.0.0 - inherits: 2.0.4 - readable-stream: 3.6.2 - dev: false - /thenify-all@1.6.0: + thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} - dependencies: - thenify: 3.3.1 - dev: true - /thenify@3.3.1: + thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - dependencies: - any-promise: 1.3.0 - dev: true - /tiny-glob@0.2.9: + tiny-glob@0.2.9: resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} - dependencies: - globalyzer: 0.1.0 - globrex: 0.1.2 - dev: true - /tinyexec@0.3.1: - resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} - dev: true + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - /to-regex-range@5.0.1: + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - dependencies: - is-number: 7.0.0 - dev: true - /totalist@3.0.1: + totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} - dev: true - /ts-api-utils@1.4.3(typescript@5.7.2): - resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} - engines: {node: '>=16'} + ts-api-utils@2.0.0: + resolution: {integrity: sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==} + engines: {node: '>=18.12'} peerDependencies: - typescript: '>=4.2.0' - dependencies: - typescript: 5.7.2 - dev: true + typescript: '>=4.8.4' - /ts-interface-checker@0.1.13: + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - dev: true - /tunnel-agent@0.6.0: + tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - dependencies: - safe-buffer: 5.2.1 - dev: false - /type-check@0.4.0: + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - dev: true - /typescript-eslint@8.18.2(eslint@9.17.0)(typescript@5.7.2): - resolution: {integrity: sha512-KuXezG6jHkvC3MvizeXgupZzaG5wjhU3yE8E7e6viOvAvD9xAWYp8/vy0WULTGe9DYDWcQu7aW03YIV3mSitrQ==} + typescript-eslint@8.19.1: + resolution: {integrity: sha512-LKPUQpdEMVOeKluHi8md7rwLcoXHhwvWp3x+sJkMuq3gGm9yaYJtPo8sRZSblMFJ5pcOGCAak/scKf1mvZDlQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - dependencies: - '@typescript-eslint/eslint-plugin': 8.18.2(@typescript-eslint/parser@8.18.2)(eslint@9.17.0)(typescript@5.7.2) - '@typescript-eslint/parser': 8.18.2(eslint@9.17.0)(typescript@5.7.2) - '@typescript-eslint/utils': 8.18.2(eslint@9.17.0)(typescript@5.7.2) - eslint: 9.17.0 - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - dev: true - /typescript@5.7.2: - resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} engines: {node: '>=14.17'} hasBin: true - dev: true - /ufo@1.5.4: + ufo@1.5.4: resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} - dev: true - /undici-types@6.20.0: + undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} - /unplugin-icons@0.22.0(svelte@5.16.0): + unplugin-icons@0.22.0: resolution: {integrity: sha512-CP+iZq5U7doOifer5bcM0jQ9t3Is7EGybIYt3myVxceI8Zuk8EZEpe1NPtJvh7iqMs1VdbK0L41t9+um9VuuLw==} peerDependencies: '@svgr/core': '>=7.0.0' @@ -3624,53 +2197,28 @@ packages: optional: true vue-template-es2015-compiler: optional: true - dependencies: - '@antfu/install-pkg': 0.5.0 - '@antfu/utils': 0.7.10 - '@iconify/utils': 2.2.1 - debug: 4.4.0 - kolorist: 1.8.0 - local-pkg: 0.5.1 - svelte: 5.16.0 - unplugin: 2.1.0 - transitivePeerDependencies: - - supports-color - dev: true - /unplugin@2.1.0: - resolution: {integrity: sha512-us4j03/499KhbGP8BU7Hrzrgseo+KdfJYWcbcajCOqsAyb8Gk0Yn2kiUIcZISYCb1JFaZfIuG3b42HmguVOKCQ==} + unplugin@2.1.2: + resolution: {integrity: sha512-Q3LU0e4zxKfRko1wMV2HmP8lB9KWislY7hxXpxd+lGx0PRInE4vhMBVEZwpdVYHvtqzhSrzuIfErsob6bQfCzw==} engines: {node: '>=18.12.0'} - dependencies: - acorn: 8.14.0 - webpack-virtual-modules: 0.6.2 - dev: true - /update-browserslist-db@1.1.1(browserslist@4.24.3): - resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + update-browserslist-db@1.1.2: + resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.24.3 - escalade: 3.2.0 - picocolors: 1.1.1 - dev: true - /uri-js@4.4.1: + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - dependencies: - punycode: 2.3.1 - dev: true - /util-deprecate@1.0.2: + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - /uuid@11.0.3: - resolution: {integrity: sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==} + uuid@11.0.4: + resolution: {integrity: sha512-IzL6VtTTYcAhA/oghbFJ1Dkmqev+FpQWnCBaKq/gUluLxliWvO8DPFWfIviRmYbtaavtSQe4WBL++rFjdcGWEg==} hasBin: true - dev: false - /vite@5.4.11: + vite@5.4.11: resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -3700,84 +2248,1919 @@ packages: optional: true terser: optional: true - dependencies: - esbuild: 0.21.5 - postcss: 8.4.49 - rollup: 4.29.1 - optionalDependencies: - fsevents: 2.3.3 - dev: true - /vitefu@1.0.4(vite@5.4.11): - resolution: {integrity: sha512-y6zEE3PQf6uu/Mt6DTJ9ih+kyJLr4XcSgHR2zUkM8SWDhuixEJxfJ6CZGMHh1Ec3vPLoEA0IHU5oWzVqw8ulow==} + vitefu@1.0.5: + resolution: {integrity: sha512-h4Vflt9gxODPFNGPwp4zAMZRpZR7eslzwH2c5hn5kNZ5rhnKyRJ50U+yGCdc2IRaBs8O4haIgLNGrV5CrpMsCA==} peerDependencies: vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 peerDependenciesMeta: vite: optional: true - dependencies: - vite: 5.4.11 - dev: true - /webpack-virtual-modules@0.6.2: + webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - dev: true - /which@2.0.2: + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true - dependencies: - isexe: 2.0.0 - dev: true - /word-wrap@1.2.5: + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - dev: true - /wrap-ansi@7.0.0: + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + yaml@2.7.0: + resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} + engines: {node: '>= 14'} + hasBin: true + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zimmerframe@1.1.2: + resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} + + zod@3.24.1: + resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@antfu/install-pkg@0.4.1': + dependencies: + package-manager-detector: 0.2.8 + tinyexec: 0.3.2 + + '@antfu/install-pkg@0.5.0': + dependencies: + package-manager-detector: 0.2.8 + tinyexec: 0.3.2 + + '@antfu/utils@0.7.10': {} + + '@esbuild-kit/core-utils@3.3.2': + dependencies: + esbuild: 0.18.20 + source-map-support: 0.5.21 + + '@esbuild-kit/esm-loader@2.6.5': + dependencies: + '@esbuild-kit/core-utils': 3.3.2 + get-tsconfig: 4.8.1 + + '@esbuild/aix-ppc64@0.19.12': + optional: true + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.18.20': + optional: true + + '@esbuild/android-arm64@0.19.12': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.18.20': + optional: true + + '@esbuild/android-arm@0.19.12': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.18.20': + optional: true + + '@esbuild/android-x64@0.19.12': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.18.20': + optional: true + + '@esbuild/darwin-arm64@0.19.12': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.18.20': + optional: true + + '@esbuild/darwin-x64@0.19.12': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.18.20': + optional: true + + '@esbuild/freebsd-arm64@0.19.12': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.18.20': + optional: true + + '@esbuild/freebsd-x64@0.19.12': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.18.20': + optional: true + + '@esbuild/linux-arm64@0.19.12': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.18.20': + optional: true + + '@esbuild/linux-arm@0.19.12': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.18.20': + optional: true + + '@esbuild/linux-ia32@0.19.12': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.18.20': + optional: true + + '@esbuild/linux-loong64@0.19.12': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.18.20': + optional: true + + '@esbuild/linux-mips64el@0.19.12': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.18.20': + optional: true + + '@esbuild/linux-ppc64@0.19.12': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.18.20': + optional: true + + '@esbuild/linux-riscv64@0.19.12': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.18.20': + optional: true + + '@esbuild/linux-s390x@0.19.12': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.18.20': + optional: true + + '@esbuild/linux-x64@0.19.12': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.18.20': + optional: true + + '@esbuild/netbsd-x64@0.19.12': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.18.20': + optional: true + + '@esbuild/openbsd-x64@0.19.12': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.18.20': + optional: true + + '@esbuild/sunos-x64@0.19.12': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.18.20': + optional: true + + '@esbuild/win32-arm64@0.19.12': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.18.20': + optional: true + + '@esbuild/win32-ia32@0.19.12': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.18.20': + optional: true + + '@esbuild/win32-x64@0.19.12': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@eslint-community/eslint-utils@4.4.1(eslint@9.17.0(jiti@1.21.7))': + dependencies: + eslint: 9.17.0(jiti@1.21.7) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/compat@1.2.4(eslint@9.17.0(jiti@1.21.7))': + optionalDependencies: + eslint: 9.17.0(jiti@1.21.7) + + '@eslint/config-array@0.19.1': + dependencies: + '@eslint/object-schema': 2.1.5 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/core@0.9.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.2.0': + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.17.0': {} + + '@eslint/object-schema@2.1.5': {} + + '@eslint/plugin-kit@0.2.4': + dependencies: + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.1': {} + + '@iconify-json/material-symbols@1.2.12': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify/types@2.0.0': {} + + '@iconify/utils@2.2.1': + dependencies: + '@antfu/install-pkg': 0.4.1 + '@antfu/utils': 0.7.10 + '@iconify/types': 2.0.0 + debug: 4.4.0 + globals: 15.14.0 + kolorist: 1.8.0 + local-pkg: 0.5.1 + mlly: 1.7.3 + transitivePeerDependencies: + - supports-color + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.18.0 + + '@phc/format@1.0.0': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@polka/url@1.0.0-next.28': {} + + '@rollup/plugin-commonjs@28.0.2(rollup@4.30.1)': + dependencies: + '@rollup/pluginutils': 5.1.4(rollup@4.30.1) + commondir: 1.0.1 + estree-walker: 2.0.2 + fdir: 6.4.2(picomatch@4.0.2) + is-reference: 1.2.1 + magic-string: 0.30.17 + picomatch: 4.0.2 + optionalDependencies: + rollup: 4.30.1 + + '@rollup/plugin-json@6.1.0(rollup@4.30.1)': + dependencies: + '@rollup/pluginutils': 5.1.4(rollup@4.30.1) + optionalDependencies: + rollup: 4.30.1 + + '@rollup/plugin-node-resolve@16.0.0(rollup@4.30.1)': + dependencies: + '@rollup/pluginutils': 5.1.4(rollup@4.30.1) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-module: 1.0.0 + resolve: 1.22.10 + optionalDependencies: + rollup: 4.30.1 + + '@rollup/pluginutils@5.1.4(rollup@4.30.1)': + dependencies: + '@types/estree': 1.0.6 + estree-walker: 2.0.2 + picomatch: 4.0.2 + optionalDependencies: + rollup: 4.30.1 + + '@rollup/rollup-android-arm-eabi@4.30.1': + optional: true + + '@rollup/rollup-android-arm64@4.30.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.30.1': + optional: true + + '@rollup/rollup-darwin-x64@4.30.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.30.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.30.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.30.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.30.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.30.1': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.30.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.30.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.30.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.30.1': + optional: true + + '@sveltejs/adapter-node@5.2.11(@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)))': + dependencies: + '@rollup/plugin-commonjs': 28.0.2(rollup@4.30.1) + '@rollup/plugin-json': 6.1.0(rollup@4.30.1) + '@rollup/plugin-node-resolve': 16.0.0(rollup@4.30.1) + '@sveltejs/kit': 2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)) + rollup: 4.30.1 + + '@sveltejs/kit@2.15.2(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5))': + dependencies: + '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)) + '@types/cookie': 0.6.0 + cookie: 0.6.0 + devalue: 5.1.1 + esm-env: 1.2.2 + import-meta-resolve: 4.1.0 + kleur: 4.1.5 + magic-string: 0.30.17 + mrmime: 2.0.0 + sade: 1.8.1 + set-cookie-parser: 2.7.1 + sirv: 3.0.0 + svelte: 5.17.1 + tiny-glob: 0.2.9 + vite: 5.4.11(@types/node@22.10.5) + + '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5))': + dependencies: + '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)) + debug: 4.4.0 + svelte: 5.17.1 + vite: 5.4.11(@types/node@22.10.5) + transitivePeerDependencies: + - supports-color + + '@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5))': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)))(svelte@5.17.1)(vite@5.4.11(@types/node@22.10.5)) + debug: 4.4.0 + deepmerge: 4.3.1 + kleur: 4.1.5 + magic-string: 0.30.17 + svelte: 5.17.1 + vite: 5.4.11(@types/node@22.10.5) + vitefu: 1.0.5(vite@5.4.11(@types/node@22.10.5)) + transitivePeerDependencies: + - supports-color + + '@types/better-sqlite3@7.6.12': + dependencies: + '@types/node': 22.10.5 + + '@types/cookie@0.6.0': {} + + '@types/estree@1.0.6': {} + + '@types/file-saver@2.0.7': {} + + '@types/json-schema@7.0.15': {} + + '@types/jsonwebtoken@9.0.7': + dependencies: + '@types/node': 22.10.5 + + '@types/ms@0.7.34': {} + + '@types/node-schedule@2.1.7': + dependencies: + '@types/node': 22.10.5 + + '@types/node@22.10.5': + dependencies: + undici-types: 6.20.0 + + '@types/resolve@1.20.2': {} + + '@typescript-eslint/eslint-plugin@8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3))(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3) + '@typescript-eslint/scope-manager': 8.19.1 + '@typescript-eslint/type-utils': 8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3) + '@typescript-eslint/utils': 8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.19.1 + eslint: 9.17.0(jiti@1.21.7) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.19.1 + '@typescript-eslint/types': 8.19.1 + '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.19.1 + debug: 4.4.0 + eslint: 9.17.0(jiti@1.21.7) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.19.1': + dependencies: + '@typescript-eslint/types': 8.19.1 + '@typescript-eslint/visitor-keys': 8.19.1 + + '@typescript-eslint/type-utils@8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.7.3) + '@typescript-eslint/utils': 8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3) + debug: 4.4.0 + eslint: 9.17.0(jiti@1.21.7) + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.19.1': {} + + '@typescript-eslint/typescript-estree@8.19.1(typescript@5.7.3)': + dependencies: + '@typescript-eslint/types': 8.19.1 + '@typescript-eslint/visitor-keys': 8.19.1 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@1.21.7)) + '@typescript-eslint/scope-manager': 8.19.1 + '@typescript-eslint/types': 8.19.1 + '@typescript-eslint/typescript-estree': 8.19.1(typescript@5.7.3) + eslint: 9.17.0(jiti@1.21.7) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.19.1': + dependencies: + '@typescript-eslint/types': 8.19.1 + eslint-visitor-keys: 4.2.0 + + acorn-jsx@5.3.2(acorn@8.14.0): + dependencies: + acorn: 8.14.0 + + acorn-typescript@1.4.13(acorn@8.14.0): + dependencies: + acorn: 8.14.0 + + acorn@8.14.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + argon2@0.41.1: + dependencies: + '@phc/format': 1.0.0 + node-addon-api: 8.3.0 + node-gyp-build: 4.8.4 + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + autoprefixer@10.4.20(postcss@8.4.49): + dependencies: + browserslist: 4.24.4 + caniuse-lite: 1.0.30001692 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + + axobject-query@4.1.0: {} + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + better-sqlite3@11.7.2: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.2 + + binary-extensions@2.3.0: {} + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.24.4: + dependencies: + caniuse-lite: 1.0.30001692 + electron-to-chromium: 1.5.79 + node-releases: 2.0.19 + update-browserslist-db: 1.1.2(browserslist@4.24.4) + + buffer-equal-constant-time@1.0.1: {} + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001692: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@4.0.3: + dependencies: + readdirp: 4.0.2 + + chownr@1.1.4: {} + + clsx@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@4.1.1: {} + + commondir@1.0.1: {} + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + cookie@0.6.0: {} + + cron-parser@4.9.0: + dependencies: + luxon: 3.5.0 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-extend@0.6.0: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + detect-libc@2.0.3: {} + + devalue@5.1.1: {} + + dexie@4.0.10: {} + + didyoumean@1.2.2: {} + + dlv@1.1.3: {} + + drizzle-kit@0.22.8: + dependencies: + '@esbuild-kit/esm-loader': 2.6.5 + esbuild: 0.19.12 + esbuild-register: 3.6.0(esbuild@0.19.12) + transitivePeerDependencies: + - supports-color + + drizzle-orm@0.33.0(@types/better-sqlite3@7.6.12)(better-sqlite3@11.7.2): + optionalDependencies: + '@types/better-sqlite3': 7.6.12 + better-sqlite3: 11.7.2 + + eastasianwidth@0.2.0: {} + + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + electron-to-chromium@1.5.79: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + end-of-stream@1.4.4: + dependencies: + once: 1.4.0 + + esbuild-register@3.6.0(esbuild@0.19.12): + dependencies: + debug: 4.4.0 + esbuild: 0.19.12 + transitivePeerDependencies: + - supports-color + + esbuild@0.18.20: + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + + esbuild@0.19.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.19.12 + '@esbuild/android-arm': 0.19.12 + '@esbuild/android-arm64': 0.19.12 + '@esbuild/android-x64': 0.19.12 + '@esbuild/darwin-arm64': 0.19.12 + '@esbuild/darwin-x64': 0.19.12 + '@esbuild/freebsd-arm64': 0.19.12 + '@esbuild/freebsd-x64': 0.19.12 + '@esbuild/linux-arm': 0.19.12 + '@esbuild/linux-arm64': 0.19.12 + '@esbuild/linux-ia32': 0.19.12 + '@esbuild/linux-loong64': 0.19.12 + '@esbuild/linux-mips64el': 0.19.12 + '@esbuild/linux-ppc64': 0.19.12 + '@esbuild/linux-riscv64': 0.19.12 + '@esbuild/linux-s390x': 0.19.12 + '@esbuild/linux-x64': 0.19.12 + '@esbuild/netbsd-x64': 0.19.12 + '@esbuild/openbsd-x64': 0.19.12 + '@esbuild/sunos-x64': 0.19.12 + '@esbuild/win32-arm64': 0.19.12 + '@esbuild/win32-ia32': 0.19.12 + '@esbuild/win32-x64': 0.19.12 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-compat-utils@0.5.1(eslint@9.17.0(jiti@1.21.7)): + dependencies: + eslint: 9.17.0(jiti@1.21.7) + semver: 7.6.3 + + eslint-config-prettier@9.1.0(eslint@9.17.0(jiti@1.21.7)): + dependencies: + eslint: 9.17.0(jiti@1.21.7) + + eslint-plugin-svelte@2.46.1(eslint@9.17.0(jiti@1.21.7))(svelte@5.17.1): + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@1.21.7)) + '@jridgewell/sourcemap-codec': 1.5.0 + eslint: 9.17.0(jiti@1.21.7) + eslint-compat-utils: 0.5.1(eslint@9.17.0(jiti@1.21.7)) + esutils: 2.0.3 + known-css-properties: 0.35.0 + postcss: 8.4.49 + postcss-load-config: 3.1.4(postcss@8.4.49) + postcss-safe-parser: 6.0.0(postcss@8.4.49) + postcss-selector-parser: 6.1.2 + semver: 7.6.3 + svelte-eslint-parser: 0.43.0(svelte@5.17.1) + optionalDependencies: + svelte: 5.17.1 + transitivePeerDependencies: + - ts-node + + eslint-plugin-tailwindcss@3.17.5(tailwindcss@3.4.17): + dependencies: + fast-glob: 3.3.3 + postcss: 8.4.49 + tailwindcss: 3.4.17 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-scope@8.2.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.17.0(jiti@1.21.7): + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@1.21.7)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.19.1 + '@eslint/core': 0.9.1 + '@eslint/eslintrc': 3.2.0 + '@eslint/js': 9.17.0 + '@eslint/plugin-kit': 0.2.4 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.1 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + escape-string-regexp: 4.0.0 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 1.21.7 + transitivePeerDependencies: + - supports-color + + esm-env@1.2.2: {} + + espree@10.3.0: + dependencies: + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 4.2.0 + + espree@9.6.1: + dependencies: + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 3.4.3 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrap@1.3.2: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + expand-template@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.18.0: + dependencies: + reusify: 1.0.4 + + fdir@6.4.2(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + file-saver@2.0.5: {} + + file-uri-to-path@1.0.0: {} + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.2 + keyv: 4.5.4 + + flatted@3.3.2: {} + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fraction.js@4.3.7: {} + + fs-constants@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-tsconfig@4.8.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + github-from-package@0.0.0: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + globals@14.0.0: {} + + globals@15.14.0: {} + + globalyzer@0.1.0: {} + + globrex@0.1.2: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + heic2any@0.0.4: {} + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-meta-resolve@4.1.0: {} + + imurmurhash@0.1.4: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-module@1.0.0: {} + + is-number@7.0.0: {} + + is-reference@1.2.1: + dependencies: + '@types/estree': 1.0.6 + + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.6 + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@1.21.7: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.6.3 + + jwa@1.4.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.2: + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kleur@4.1.5: {} + + known-css-properties@0.35.0: {} + + kolorist@1.8.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@2.1.0: {} + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + local-pkg@0.5.1: + dependencies: + mlly: 1.7.3 + pkg-types: 1.3.0 + + locate-character@3.0.0: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.merge@4.6.2: {} + + lodash.once@4.1.1: {} + + long-timeout@0.1.1: {} + + lru-cache@10.4.3: {} + + luxon@3.5.0: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime@4.0.6: {} + + mimic-response@3.1.0: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + mkdirp-classic@0.5.3: {} + + mlly@1.7.3: + dependencies: + acorn: 8.14.0 + pathe: 1.1.2 + pkg-types: 1.3.0 + ufo: 1.5.4 + + mri@1.2.0: {} + + mrmime@2.0.0: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.8: {} + + napi-build-utils@1.0.2: {} + + natural-compare@1.4.0: {} + + node-abi@3.71.0: + dependencies: + semver: 7.6.3 + + node-addon-api@8.3.0: {} + + node-gyp-build@4.8.4: {} + + node-releases@2.0.19: {} + + node-schedule@2.1.1: + dependencies: + cron-parser: 4.9.0 + long-timeout: 0.1.1 + sorted-array-functions: 1.3.0 + + normalize-path@3.0.0: {} + + normalize-range@0.1.2: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: {} + + package-manager-detector@0.2.8: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + pathe@1.1.2: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.2: {} + + pify@2.3.0: {} + + pirates@4.0.6: {} + + pkg-types@1.3.0: + dependencies: + confbox: 0.1.8 + mlly: 1.7.3 + pathe: 1.1.2 + + postcss-import@15.1.0(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.10 + + postcss-js@4.0.1(postcss@8.4.49): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.49 + + postcss-load-config@3.1.4(postcss@8.4.49): + dependencies: + lilconfig: 2.1.0 + yaml: 1.10.2 + optionalDependencies: + postcss: 8.4.49 + + postcss-load-config@4.0.2(postcss@8.4.49): + dependencies: + lilconfig: 3.1.3 + yaml: 2.7.0 + optionalDependencies: + postcss: 8.4.49 + + postcss-nested@6.2.0(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + postcss-selector-parser: 6.1.2 + + postcss-safe-parser@6.0.0(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + + postcss-scss@4.0.9(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.49: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prebuild-install@7.1.2: + dependencies: + detect-libc: 2.0.3 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 1.0.2 + node-abi: 3.71.0 + pump: 3.0.2 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.1 + tunnel-agent: 0.6.0 + + prelude-ls@1.2.1: {} + + prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.17.1): + dependencies: + prettier: 3.4.2 + svelte: 5.17.1 + + prettier-plugin-tailwindcss@0.6.9(prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.17.1))(prettier@3.4.2): + dependencies: + prettier: 3.4.2 + optionalDependencies: + prettier-plugin-svelte: 3.3.2(prettier@3.4.2)(svelte@5.17.1) + + prettier@3.4.2: {} + + pump@3.0.2: + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + readdirp@4.0.2: {} + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.0.4: {} + + rollup@4.30.1: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.30.1 + '@rollup/rollup-android-arm64': 4.30.1 + '@rollup/rollup-darwin-arm64': 4.30.1 + '@rollup/rollup-darwin-x64': 4.30.1 + '@rollup/rollup-freebsd-arm64': 4.30.1 + '@rollup/rollup-freebsd-x64': 4.30.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.30.1 + '@rollup/rollup-linux-arm-musleabihf': 4.30.1 + '@rollup/rollup-linux-arm64-gnu': 4.30.1 + '@rollup/rollup-linux-arm64-musl': 4.30.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.30.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.30.1 + '@rollup/rollup-linux-riscv64-gnu': 4.30.1 + '@rollup/rollup-linux-s390x-gnu': 4.30.1 + '@rollup/rollup-linux-x64-gnu': 4.30.1 + '@rollup/rollup-linux-x64-musl': 4.30.1 + '@rollup/rollup-win32-arm64-msvc': 4.30.1 + '@rollup/rollup-win32-ia32-msvc': 4.30.1 + '@rollup/rollup-win32-x64-msvc': 4.30.1 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + sade@1.8.1: + dependencies: + mri: 1.2.0 + + safe-buffer@5.2.1: {} + + semver@7.6.3: {} + + set-cookie-parser@2.7.1: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + sirv@3.0.0: + dependencies: + '@polka/url': 1.0.0-next.28 + mrmime: 2.0.0 + totalist: 3.0.1 + + sorted-array-functions@1.3.0: {} + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-json-comments@2.0.1: {} + + strip-json-comments@3.1.1: {} + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + svelte-check@4.1.3(picomatch@4.0.2)(svelte@5.17.1)(typescript@5.7.3): + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + chokidar: 4.0.3 + fdir: 6.4.2(picomatch@4.0.2) + picocolors: 1.1.1 + sade: 1.8.1 + svelte: 5.17.1 + typescript: 5.7.3 + transitivePeerDependencies: + - picomatch + + svelte-eslint-parser@0.43.0(svelte@5.17.1): + dependencies: + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + postcss: 8.4.49 + postcss-scss: 4.0.9(postcss@8.4.49) + optionalDependencies: + svelte: 5.17.1 + + svelte@5.17.1: + dependencies: + '@ampproject/remapping': 2.3.0 + '@jridgewell/sourcemap-codec': 1.5.0 + '@types/estree': 1.0.6 + acorn: 8.14.0 + acorn-typescript: 1.4.13(acorn@8.14.0) + aria-query: 5.3.2 + axobject-query: 4.1.0 + clsx: 2.1.1 + esm-env: 1.2.2 + esrap: 1.3.2 + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.17 + zimmerframe: 1.1.2 + + tailwindcss@3.4.17: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.4.49 + postcss-import: 15.1.0(postcss@8.4.49) + postcss-js: 4.0.1(postcss@8.4.49) + postcss-load-config: 4.0.2(postcss@8.4.49) + postcss-nested: 6.2.0(postcss@8.4.49) + postcss-selector-parser: 6.1.2 + resolve: 1.22.10 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + + tar-fs@2.1.1: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.2 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tiny-glob@0.2.9: + dependencies: + globalyzer: 0.1.0 + globrex: 0.1.2 + + tinyexec@0.3.2: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + totalist@3.0.1: {} + + ts-api-utils@2.0.0(typescript@5.7.3): + dependencies: + typescript: 5.7.3 + + ts-interface-checker@0.1.13: {} + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3))(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3) + '@typescript-eslint/parser': 8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3) + '@typescript-eslint/utils': 8.19.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3) + eslint: 9.17.0(jiti@1.21.7) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + typescript@5.7.3: {} + + ufo@1.5.4: {} + + undici-types@6.20.0: {} + + unplugin-icons@0.22.0(svelte@5.17.1): + dependencies: + '@antfu/install-pkg': 0.5.0 + '@antfu/utils': 0.7.10 + '@iconify/utils': 2.2.1 + debug: 4.4.0 + kolorist: 1.8.0 + local-pkg: 0.5.1 + unplugin: 2.1.2 + optionalDependencies: + svelte: 5.17.1 + transitivePeerDependencies: + - supports-color + + unplugin@2.1.2: + dependencies: + acorn: 8.14.0 + webpack-virtual-modules: 0.6.2 + + update-browserslist-db@1.1.2(browserslist@4.24.4): + dependencies: + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + uuid@11.0.4: {} + + vite@5.4.11(@types/node@22.10.5): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.49 + rollup: 4.30.1 + optionalDependencies: + '@types/node': 22.10.5 + fsevents: 2.3.3 + + vitefu@1.0.5(vite@5.4.11(@types/node@22.10.5)): + optionalDependencies: + vite: 5.4.11(@types/node@22.10.5) + + webpack-virtual-modules@0.6.2: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true - /wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} + wrap-ansi@8.1.0: dependencies: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - dev: true - /wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: false + wrappy@1.0.2: {} - /yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} - dev: true + yaml@1.10.2: {} - /yaml@2.6.1: - resolution: {integrity: sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==} - engines: {node: '>= 14'} - hasBin: true - dev: true + yaml@2.7.0: {} - /yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - dev: true + yocto-queue@0.1.0: {} - /zimmerframe@1.1.2: - resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} - dev: true + zimmerframe@1.1.2: {} - /zod@3.24.1: - resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} - dev: false + zod@3.24.1: {} From 0bdf990dae5b9eabaf5eb5796fa7ef4372ad3943 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 11 Jan 2025 03:55:19 +0900 Subject: [PATCH 02/17] =?UTF-8?q?DB=EC=97=90=20=EB=8F=99=EC=8B=9C=EC=A0=81?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=A0=91=EA=B7=BC=ED=95=98=EB=8D=94?= =?UTF-8?q?=EB=9D=BC=EB=8F=84=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=AC=B4?= =?UTF-8?q?=EA=B2=B0=EC=84=B1=EC=9D=B4=20=EA=B9=A8=EC=A7=80=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8F=84=EB=A1=9D=20DB=20=EC=A0=91=EA=B7=BC=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/db/client.ts | 103 ++++----- src/lib/server/db/error.ts | 21 ++ src/lib/server/db/file.ts | 227 +++++++++++-------- src/lib/server/db/mek.ts | 58 ++--- src/lib/server/db/token.ts | 120 +++++----- src/lib/server/db/user.ts | 2 +- src/lib/server/services/auth.ts | 62 +++-- src/lib/server/services/client.ts | 90 ++++---- src/lib/server/services/directory.ts | 80 +++---- src/lib/server/services/file.ts | 141 +++++------- src/lib/server/services/mek.ts | 18 +- src/routes/api/file/[id]/download/+server.ts | 2 +- 12 files changed, 486 insertions(+), 438 deletions(-) create mode 100644 src/lib/server/db/error.ts diff --git a/src/lib/server/db/client.ts b/src/lib/server/db/client.ts index bdf2404..7a7b58b 100644 --- a/src/lib/server/db/client.ts +++ b/src/lib/server/db/client.ts @@ -1,30 +1,36 @@ -import { and, or, eq, gt, lte, count } from "drizzle-orm"; +import { SqliteError } from "better-sqlite3"; +import { and, or, eq, gt, lte } from "drizzle-orm"; import db from "./drizzle"; +import { IntegrityError } from "./error"; import { client, userClient, userClientChallenge } from "./schema"; export const createClient = async (encPubKey: string, sigPubKey: string, userId: number) => { - return await db.transaction(async (tx) => { - const clients = await tx - .select() - .from(client) - .where(or(eq(client.encPubKey, sigPubKey), eq(client.sigPubKey, encPubKey))); - if (clients.length > 0) { - throw new Error("Already used public key(s)"); - } + return await db.transaction( + async (tx) => { + const clients = await tx + .select({ id: client.id }) + .from(client) + .where(or(eq(client.encPubKey, sigPubKey), eq(client.sigPubKey, encPubKey))) + .limit(1); + if (clients.length !== 0) { + throw new IntegrityError("Public key(s) already registered"); + } - const insertRes = await tx - .insert(client) - .values({ encPubKey, sigPubKey }) - .returning({ id: client.id }); - const { id: clientId } = insertRes[0]!; - await tx.insert(userClient).values({ userId, clientId }); + const newClients = await tx + .insert(client) + .values({ encPubKey, sigPubKey }) + .returning({ id: client.id }); + const { id: clientId } = newClients[0]!; + await tx.insert(userClient).values({ userId, clientId }); - return clientId; - }); + return clientId; + }, + { behavior: "exclusive" }, + ); }; export const getClient = async (clientId: number) => { - const clients = await db.select().from(client).where(eq(client.id, clientId)).execute(); + const clients = await db.select().from(client).where(eq(client.id, clientId)).limit(1); return clients[0] ?? null; }; @@ -33,24 +39,23 @@ export const getClientByPubKeys = async (encPubKey: string, sigPubKey: string) = .select() .from(client) .where(and(eq(client.encPubKey, encPubKey), eq(client.sigPubKey, sigPubKey))) - .execute(); + .limit(1); return clients[0] ?? null; }; -export const countClientByPubKey = async (pubKey: string) => { - const clients = await db - .select({ count: count() }) - .from(client) - .where(or(eq(client.encPubKey, pubKey), eq(client.encPubKey, pubKey))); - return clients[0]?.count ?? 0; -}; - export const createUserClient = async (userId: number, clientId: number) => { - await db.insert(userClient).values({ userId, clientId }).execute(); + try { + await db.insert(userClient).values({ userId, clientId }); + } catch (e) { + if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_PRIMARYKEY") { + throw new IntegrityError("User client already exists"); + } + throw e; + } }; export const getAllUserClients = async (userId: number) => { - return await db.select().from(userClient).where(eq(userClient.userId, userId)).execute(); + return await db.select().from(userClient).where(eq(userClient.userId, userId)); }; export const getUserClient = async (userId: number, clientId: number) => { @@ -58,7 +63,7 @@ export const getUserClient = async (userId: number, clientId: number) => { .select() .from(userClient) .where(and(eq(userClient.userId, userId), eq(userClient.clientId, clientId))) - .execute(); + .limit(1); return userClients[0] ?? null; }; @@ -68,7 +73,7 @@ export const getUserClientWithDetails = async (userId: number, clientId: number) .from(userClient) .innerJoin(client, eq(userClient.clientId, client.id)) .where(and(eq(userClient.userId, userId), eq(userClient.clientId, clientId))) - .execute(); + .limit(1); return userClients[0] ?? null; }; @@ -82,8 +87,7 @@ export const setUserClientStateToPending = async (userId: number, clientId: numb eq(userClient.clientId, clientId), eq(userClient.state, "challenging"), ), - ) - .execute(); + ); }; export const setUserClientStateToActive = async (userId: number, clientId: number) => { @@ -96,8 +100,7 @@ export const setUserClientStateToActive = async (userId: number, clientId: numbe eq(userClient.clientId, clientId), eq(userClient.state, "pending"), ), - ) - .execute(); + ); }; export const registerUserClientChallenge = async ( @@ -107,16 +110,13 @@ export const registerUserClientChallenge = async ( allowedIp: string, expiresAt: Date, ) => { - await db - .insert(userClientChallenge) - .values({ - userId, - clientId, - answer, - allowedIp, - expiresAt, - }) - .execute(); + await db.insert(userClientChallenge).values({ + userId, + clientId, + answer, + allowedIp, + expiresAt, + }); }; export const getUserClientChallenge = async (answer: string, ip: string) => { @@ -131,21 +131,14 @@ export const getUserClientChallenge = async (answer: string, ip: string) => { eq(userClientChallenge.isUsed, false), ), ) - .execute(); + .limit(1); return challenges[0] ?? null; }; export const markUserClientChallengeAsUsed = async (id: number) => { - await db - .update(userClientChallenge) - .set({ isUsed: true }) - .where(eq(userClientChallenge.id, id)) - .execute(); + await db.update(userClientChallenge).set({ isUsed: true }).where(eq(userClientChallenge.id, id)); }; export const cleanupExpiredUserClientChallenges = async () => { - await db - .delete(userClientChallenge) - .where(lte(userClientChallenge.expiresAt, new Date())) - .execute(); + await db.delete(userClientChallenge).where(lte(userClientChallenge.expiresAt, new Date())); }; diff --git a/src/lib/server/db/error.ts b/src/lib/server/db/error.ts new file mode 100644 index 0000000..7644800 --- /dev/null +++ b/src/lib/server/db/error.ts @@ -0,0 +1,21 @@ +type IntegrityErrorMessages = + // Client + | "Public key(s) already registered" + | "User client already exists" + // File + | "Directory not found" + | "File not found" + | "Invalid DEK version" + // MEK + | "MEK already registered" + | "Inactive MEK version" + // Token + | "Refresh token not found" + | "Refresh token already registered"; + +export class IntegrityError extends Error { + constructor(public message: IntegrityErrorMessages) { + super(message); + this.name = "IntegrityError"; + } +} diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index 2fe4b53..270add3 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -1,5 +1,6 @@ import { and, eq, isNull } from "drizzle-orm"; import db from "./drizzle"; +import { IntegrityError } from "./error"; import { directory, file, mek } from "./schema"; type DirectoryId = "root" | number; @@ -27,40 +28,42 @@ export interface NewFileParams { encNameIv: string; } -export const registerNewDirectory = async (params: NewDirectoryParams) => { - return await db.transaction(async (tx) => { - const meks = await tx - .select() - .from(mek) - .where(and(eq(mek.userId, params.userId), eq(mek.state, "active"))); - if (meks[0]?.version !== params.mekVersion) { - throw new Error("Invalid MEK version"); - } +export const registerDirectory = async (params: NewDirectoryParams) => { + await db.transaction( + async (tx) => { + const meks = await tx + .select({ version: mek.version }) + .from(mek) + .where(and(eq(mek.userId, params.userId), eq(mek.state, "active"))) + .limit(1); + if (meks[0]?.version !== params.mekVersion) { + throw new IntegrityError("Inactive MEK version"); + } - const now = new Date(); - await tx.insert(directory).values({ - createdAt: now, - parentId: params.parentId === "root" ? null : params.parentId, - userId: params.userId, - mekVersion: params.mekVersion, - encDek: params.encDek, - dekVersion: params.dekVersion, - encName: { ciphertext: params.encName, iv: params.encNameIv }, - }); - }); + await tx.insert(directory).values({ + createdAt: new Date(), + parentId: params.parentId === "root" ? null : params.parentId, + userId: params.userId, + mekVersion: params.mekVersion, + encDek: params.encDek, + dekVersion: params.dekVersion, + encName: { ciphertext: params.encName, iv: params.encNameIv }, + }); + }, + { behavior: "exclusive" }, + ); }; -export const getAllDirectoriesByParent = async (userId: number, directoryId: DirectoryId) => { +export const getAllDirectoriesByParent = async (userId: number, parentId: DirectoryId) => { return await db .select() .from(directory) .where( and( eq(directory.userId, userId), - directoryId === "root" ? isNull(directory.parentId) : eq(directory.parentId, directoryId), + parentId === "root" ? isNull(directory.parentId) : eq(directory.parentId, parentId), ), - ) - .execute(); + ); }; export const getDirectory = async (userId: number, directoryId: number) => { @@ -68,7 +71,7 @@ export const getDirectory = async (userId: number, directoryId: number) => { .select() .from(directory) .where(and(eq(directory.userId, userId), eq(directory.id, directoryId))) - .execute(); + .limit(1); return res[0] ?? null; }; @@ -79,72 +82,87 @@ export const setDirectoryEncName = async ( encName: string, encNameIv: string, ) => { - const res = await db - .update(directory) - .set({ encName: { ciphertext: encName, iv: encNameIv } }) - .where( - and( - eq(directory.userId, userId), - eq(directory.id, directoryId), - eq(directory.dekVersion, dekVersion), - ), - ) - .execute(); - return res.changes > 0; + await db.transaction( + async (tx) => { + const directories = await tx + .select({ version: directory.dekVersion }) + .from(directory) + .where(and(eq(directory.userId, userId), eq(directory.id, directoryId))) + .limit(1); + if (!directories[0]) { + throw new IntegrityError("Directory not found"); + } else if (directories[0].version.getTime() !== dekVersion.getTime()) { + throw new IntegrityError("Invalid DEK version"); + } + + await tx + .update(directory) + .set({ encName: { ciphertext: encName, iv: encNameIv } }) + .where(and(eq(directory.userId, userId), eq(directory.id, directoryId))); + }, + { behavior: "exclusive" }, + ); }; export const unregisterDirectory = async (userId: number, directoryId: number) => { - return await db.transaction(async (tx) => { - const getFilePaths = async (parentId: number) => { - const files = await tx - .select({ path: file.path }) - .from(file) - .where(and(eq(file.userId, userId), eq(file.parentId, parentId))); - return files.map(({ path }) => path); - }; - const unregisterSubDirectoriesRecursively = async (directoryId: number): Promise => { - const subDirectories = await tx - .select({ id: directory.id }) - .from(directory) - .where(and(eq(directory.userId, userId), eq(directory.parentId, directoryId))); - const subDirectoryFilePaths = await Promise.all( - subDirectories.map(async ({ id }) => await unregisterSubDirectoriesRecursively(id)), - ); - const filePaths = await getFilePaths(directoryId); + return await db.transaction( + async (tx) => { + const unregisterFiles = async (parentId: number) => { + const files = await tx + .delete(file) + .where(and(eq(file.userId, userId), eq(file.parentId, parentId))) + .returning({ path: file.path }); + return files.map(({ path }) => path); + }; + const unregisterDirectoryRecursively = async (directoryId: number): Promise => { + const filePaths = await unregisterFiles(directoryId); + const subDirectories = await tx + .select({ id: directory.id }) + .from(directory) + .where(and(eq(directory.userId, userId), eq(directory.parentId, directoryId))); + const subDirectoryFilePaths = await Promise.all( + subDirectories.map(async ({ id }) => await unregisterDirectoryRecursively(id)), + ); - await tx.delete(file).where(eq(file.parentId, directoryId)); - await tx.delete(directory).where(eq(directory.id, directoryId)); - - return filePaths.concat(...subDirectoryFilePaths); - }; - return await unregisterSubDirectoriesRecursively(directoryId); - }); + const deleteRes = await tx.delete(directory).where(eq(directory.id, directoryId)); + if (deleteRes.changes === 0) { + throw new IntegrityError("Directory not found"); + } + return filePaths.concat(...subDirectoryFilePaths); + }; + return await unregisterDirectoryRecursively(directoryId); + }, + { behavior: "exclusive" }, + ); }; -export const registerNewFile = async (params: NewFileParams) => { - await db.transaction(async (tx) => { - const meks = await tx - .select() - .from(mek) - .where(and(eq(mek.userId, params.userId), eq(mek.state, "active"))); - if (meks[0]?.version !== params.mekVersion) { - throw new Error("Invalid MEK version"); - } +export const registerFile = async (params: NewFileParams) => { + await db.transaction( + async (tx) => { + const meks = await tx + .select({ version: mek.version }) + .from(mek) + .where(and(eq(mek.userId, params.userId), eq(mek.state, "active"))) + .limit(1); + if (meks[0]?.version !== params.mekVersion) { + throw new IntegrityError("Inactive MEK version"); + } - const now = new Date(); - await tx.insert(file).values({ - path: params.path, - parentId: params.parentId === "root" ? null : params.parentId, - createdAt: now, - userId: params.userId, - mekVersion: params.mekVersion, - contentType: params.contentType, - encDek: params.encDek, - dekVersion: params.dekVersion, - encContentIv: params.encContentIv, - encName: { ciphertext: params.encName, iv: params.encNameIv }, - }); - }); + await tx.insert(file).values({ + path: params.path, + parentId: params.parentId === "root" ? null : params.parentId, + createdAt: new Date(), + userId: params.userId, + mekVersion: params.mekVersion, + contentType: params.contentType, + encDek: params.encDek, + dekVersion: params.dekVersion, + encContentIv: params.encContentIv, + encName: { ciphertext: params.encName, iv: params.encNameIv }, + }); + }, + { behavior: "exclusive" }, + ); }; export const getAllFilesByParent = async (userId: number, parentId: DirectoryId) => { @@ -156,8 +174,7 @@ export const getAllFilesByParent = async (userId: number, parentId: DirectoryId) eq(file.userId, userId), parentId === "root" ? isNull(file.parentId) : eq(file.parentId, parentId), ), - ) - .execute(); + ); }; export const getFile = async (userId: number, fileId: number) => { @@ -165,7 +182,7 @@ export const getFile = async (userId: number, fileId: number) => { .select() .from(file) .where(and(eq(file.userId, userId), eq(file.id, fileId))) - .execute(); + .limit(1); return res[0] ?? null; }; @@ -176,19 +193,35 @@ export const setFileEncName = async ( encName: string, encNameIv: string, ) => { - const res = await db - .update(file) - .set({ encName: { ciphertext: encName, iv: encNameIv } }) - .where(and(eq(file.userId, userId), eq(file.id, fileId), eq(file.dekVersion, dekVersion))) - .execute(); - return res.changes > 0; + await db.transaction( + async (tx) => { + const files = await tx + .select({ version: file.dekVersion }) + .from(file) + .where(and(eq(file.userId, userId), eq(file.id, fileId))) + .limit(1); + if (!files[0]) { + throw new IntegrityError("File not found"); + } else if (files[0].version.getTime() !== dekVersion.getTime()) { + throw new IntegrityError("Invalid DEK version"); + } + + await tx + .update(file) + .set({ encName: { ciphertext: encName, iv: encNameIv } }) + .where(and(eq(file.userId, userId), eq(file.id, fileId))); + }, + { behavior: "exclusive" }, + ); }; export const unregisterFile = async (userId: number, fileId: number) => { - const res = await db + const files = await db .delete(file) .where(and(eq(file.userId, userId), eq(file.id, fileId))) - .returning({ path: file.path }) - .execute(); - return res[0]?.path ?? null; + .returning({ path: file.path }); + if (!files[0]) { + throw new IntegrityError("File not found"); + } + return files[0].path; }; diff --git a/src/lib/server/db/mek.ts b/src/lib/server/db/mek.ts index 7215ce0..237ef59 100644 --- a/src/lib/server/db/mek.ts +++ b/src/lib/server/db/mek.ts @@ -1,5 +1,7 @@ +import { SqliteError } from "better-sqlite3"; import { and, or, eq } from "drizzle-orm"; import db from "./drizzle"; +import { IntegrityError } from "./error"; import { mek, clientMek } from "./schema"; export const registerInitialMek = async ( @@ -8,22 +10,32 @@ export const registerInitialMek = async ( encMek: string, encMekSig: string, ) => { - await db.transaction(async (tx) => { - await tx.insert(mek).values({ - userId, - version: 1, - createdBy, - createdAt: new Date(), - state: "active", - }); - await tx.insert(clientMek).values({ - userId, - clientId: createdBy, - mekVersion: 1, - encMek, - encMekSig, - }); - }); + await db.transaction( + async (tx) => { + try { + await tx.insert(mek).values({ + userId, + version: 1, + createdBy, + createdAt: new Date(), + state: "active", + }); + await tx.insert(clientMek).values({ + userId, + clientId: createdBy, + mekVersion: 1, + encMek, + encMekSig, + }); + } catch (e) { + if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_PRIMARYKEY") { + throw new IntegrityError("MEK already registered"); + } + throw e; + } + }, + { behavior: "exclusive" }, + ); }; export const getInitialMek = async (userId: number) => { @@ -31,19 +43,10 @@ export const getInitialMek = async (userId: number) => { .select() .from(mek) .where(and(eq(mek.userId, userId), eq(mek.version, 1))) - .execute(); + .limit(1); return meks[0] ?? null; }; -export const getActiveMekVersion = async (userId: number) => { - const meks = await db - .select({ version: mek.version }) - .from(mek) - .where(and(eq(mek.userId, userId), eq(mek.state, "active"))) - .execute(); - return meks[0]?.version ?? null; -}; - export const getAllValidClientMeks = async (userId: number, clientId: number) => { return await db .select() @@ -55,6 +58,5 @@ export const getAllValidClientMeks = async (userId: number, clientId: number) => eq(clientMek.clientId, clientId), or(eq(mek.state, "active"), eq(mek.state, "retired")), ), - ) - .execute(); + ); }; diff --git a/src/lib/server/db/token.ts b/src/lib/server/db/token.ts index e26a8ef..25bf1de 100644 --- a/src/lib/server/db/token.ts +++ b/src/lib/server/db/token.ts @@ -2,6 +2,7 @@ import { SqliteError } from "better-sqlite3"; import { and, eq, gt, lte } from "drizzle-orm"; import env from "$lib/server/loadenv"; import db from "./drizzle"; +import { IntegrityError } from "./error"; import { refreshToken, tokenUpgradeChallenge } from "./schema"; const expiresAt = () => new Date(Date.now() + env.jwt.refreshExp); @@ -12,44 +13,45 @@ export const registerRefreshToken = async ( tokenId: string, ) => { try { - await db - .insert(refreshToken) - .values({ - id: tokenId, - userId, - clientId, - expiresAt: expiresAt(), - }) - .execute(); - return true; + await db.insert(refreshToken).values({ + id: tokenId, + userId, + clientId, + expiresAt: expiresAt(), + }); } catch (e) { if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { - return false; + throw new IntegrityError("Refresh token already registered"); } throw e; } }; export const getRefreshToken = async (tokenId: string) => { - const tokens = await db.select().from(refreshToken).where(eq(refreshToken.id, tokenId)).execute(); + const tokens = await db.select().from(refreshToken).where(eq(refreshToken.id, tokenId)).limit(1); return tokens[0] ?? null; }; export const rotateRefreshToken = async (oldTokenId: string, newTokenId: string) => { - return await db.transaction(async (tx) => { - await tx - .delete(tokenUpgradeChallenge) - .where(eq(tokenUpgradeChallenge.refreshTokenId, oldTokenId)); - const res = await db - .update(refreshToken) - .set({ - id: newTokenId, - expiresAt: expiresAt(), - }) - .where(eq(refreshToken.id, oldTokenId)) - .execute(); - return res.changes > 0; - }); + await db.transaction( + async (tx) => { + await tx + .delete(tokenUpgradeChallenge) + .where(eq(tokenUpgradeChallenge.refreshTokenId, oldTokenId)); + + const res = await tx + .update(refreshToken) + .set({ + id: newTokenId, + expiresAt: expiresAt(), + }) + .where(eq(refreshToken.id, oldTokenId)); + if (res.changes === 0) { + throw new IntegrityError("Refresh token not found"); + } + }, + { behavior: "exclusive" }, + ); }; export const upgradeRefreshToken = async ( @@ -57,29 +59,34 @@ export const upgradeRefreshToken = async ( newTokenId: string, clientId: number, ) => { - return await db.transaction(async (tx) => { - await tx - .delete(tokenUpgradeChallenge) - .where(eq(tokenUpgradeChallenge.refreshTokenId, oldTokenId)); - const res = await tx - .update(refreshToken) - .set({ - id: newTokenId, - clientId, - expiresAt: expiresAt(), - }) - .where(eq(refreshToken.id, oldTokenId)) - .execute(); - return res.changes > 0; - }); + await db.transaction( + async (tx) => { + await tx + .delete(tokenUpgradeChallenge) + .where(eq(tokenUpgradeChallenge.refreshTokenId, oldTokenId)); + + const res = await tx + .update(refreshToken) + .set({ + id: newTokenId, + clientId, + expiresAt: expiresAt(), + }) + .where(eq(refreshToken.id, oldTokenId)); + if (res.changes === 0) { + throw new IntegrityError("Refresh token not found"); + } + }, + { behavior: "exclusive" }, + ); }; export const revokeRefreshToken = async (tokenId: string) => { - await db.delete(refreshToken).where(eq(refreshToken.id, tokenId)).execute(); + await db.delete(refreshToken).where(eq(refreshToken.id, tokenId)); }; export const cleanupExpiredRefreshTokens = async () => { - await db.delete(refreshToken).where(lte(refreshToken.expiresAt, new Date())).execute(); + await db.delete(refreshToken).where(lte(refreshToken.expiresAt, new Date())); }; export const registerTokenUpgradeChallenge = async ( @@ -89,16 +96,13 @@ export const registerTokenUpgradeChallenge = async ( allowedIp: string, expiresAt: Date, ) => { - await db - .insert(tokenUpgradeChallenge) - .values({ - refreshTokenId: tokenId, - clientId, - answer, - allowedIp, - expiresAt, - }) - .execute(); + await db.insert(tokenUpgradeChallenge).values({ + refreshTokenId: tokenId, + clientId, + answer, + allowedIp, + expiresAt, + }); }; export const getTokenUpgradeChallenge = async (answer: string, ip: string) => { @@ -113,7 +117,7 @@ export const getTokenUpgradeChallenge = async (answer: string, ip: string) => { eq(tokenUpgradeChallenge.isUsed, false), ), ) - .execute(); + .limit(1); return challenges[0] ?? null; }; @@ -121,13 +125,9 @@ export const markTokenUpgradeChallengeAsUsed = async (id: number) => { await db .update(tokenUpgradeChallenge) .set({ isUsed: true }) - .where(eq(tokenUpgradeChallenge.id, id)) - .execute(); + .where(eq(tokenUpgradeChallenge.id, id)); }; export const cleanupExpiredTokenUpgradeChallenges = async () => { - await db - .delete(tokenUpgradeChallenge) - .where(lte(tokenUpgradeChallenge.expiresAt, new Date())) - .execute(); + await db.delete(tokenUpgradeChallenge).where(lte(tokenUpgradeChallenge.expiresAt, new Date())); }; diff --git a/src/lib/server/db/user.ts b/src/lib/server/db/user.ts index 38a53f0..1efe43a 100644 --- a/src/lib/server/db/user.ts +++ b/src/lib/server/db/user.ts @@ -3,6 +3,6 @@ import db from "./drizzle"; import { user } from "./schema"; export const getUserByEmail = async (email: string) => { - const users = await db.select().from(user).where(eq(user.email, email)).execute(); + const users = await db.select().from(user).where(eq(user.email, email)).limit(1); return users[0] ?? null; }; diff --git a/src/lib/server/services/auth.ts b/src/lib/server/services/auth.ts index 53c2e51..36a3c5a 100644 --- a/src/lib/server/services/auth.ts +++ b/src/lib/server/services/auth.ts @@ -4,9 +4,10 @@ import { v4 as uuidv4 } from "uuid"; import { getClient, getClientByPubKeys, getUserClient } from "$lib/server/db/client"; import { getUserByEmail } from "$lib/server/db/user"; import env from "$lib/server/loadenv"; +import { IntegrityError } from "$lib/server/db/error"; import { - getRefreshToken, registerRefreshToken, + getRefreshToken, rotateRefreshToken, upgradeRefreshToken, revokeRefreshToken, @@ -29,10 +30,15 @@ const issueRefreshToken = async (userId: number, clientId?: number) => { const jti = uuidv4(); const token = issueToken({ type: "refresh", jti }); - if (!(await registerRefreshToken(userId, clientId ?? null, jti))) { - error(403, "Already logged in"); + try { + await registerRefreshToken(userId, clientId ?? null, jti); + return token; + } catch (e) { + if (e instanceof IntegrityError && e.message === "Refresh token already registered") { + error(409, "Already logged in"); + } + throw e; } - return token; }; export const login = async (email: string, password: string) => { @@ -57,7 +63,7 @@ const verifyRefreshToken = async (refreshToken: string) => { const tokenData = await getRefreshToken(tokenPayload.jti); if (!tokenData) { - error(500, "Refresh token not found"); + error(500, "Invalid refresh token"); } return { @@ -76,13 +82,18 @@ export const refreshToken = async (refreshToken: string) => { const { jti: oldJti, userId, clientId } = await verifyRefreshToken(refreshToken); const newJti = uuidv4(); - if (!(await rotateRefreshToken(oldJti, newJti))) { - error(500, "Refresh token not found"); + try { + await rotateRefreshToken(oldJti, newJti); + return { + accessToken: issueAccessToken(userId, clientId), + refreshToken: issueToken({ type: "refresh", jti: newJti }), + }; + } catch (e) { + if (e instanceof IntegrityError && e.message === "Refresh token not found") { + error(500, "Invalid refresh token"); + } + throw e; } - return { - accessToken: issueAccessToken(userId, clientId), - refreshToken: issueToken({ type: "refresh", jti: newJti }), - }; }; const expiresAt = () => new Date(Date.now() + env.challenge.tokenUpgradeExp); @@ -120,7 +131,7 @@ export const createTokenUpgradeChallenge = async ( if (!client) { error(401, "Invalid public key(s)"); } else if (!userClient || userClient.state === "challenging") { - error(401, "Unregistered client"); + error(403, "Unregistered client"); } return { challenge: await createChallenge(ip, jti, client.id, encPubKey) }; @@ -139,26 +150,31 @@ export const upgradeToken = async ( const challenge = await getTokenUpgradeChallenge(answer, ip); if (!challenge) { - error(401, "Invalid challenge answer"); + error(403, "Invalid challenge answer"); } else if (challenge.refreshTokenId !== oldJti) { error(403, "Forbidden"); } + await markTokenUpgradeChallengeAsUsed(challenge.id); + const client = await getClient(challenge.clientId); if (!client) { error(500, "Invalid challenge answer"); } else if (!verifySignature(Buffer.from(answer, "base64"), answerSig, client.sigPubKey)) { - error(401, "Invalid challenge answer signature"); + error(403, "Invalid challenge answer signature"); } - await markTokenUpgradeChallengeAsUsed(challenge.id); - - const newJti = uuidv4(); - if (!(await upgradeRefreshToken(oldJti, newJti, client.id))) { - error(500, "Refresh token not found"); + try { + const newJti = uuidv4(); + await upgradeRefreshToken(oldJti, newJti, client.id); + return { + accessToken: issueAccessToken(userId, client.id), + refreshToken: issueToken({ type: "refresh", jti: newJti }), + }; + } catch (e) { + if (e instanceof IntegrityError && e.message === "Refresh token not found") { + error(500, "Invalid refresh token"); + } + throw e; } - return { - accessToken: issueAccessToken(userId, client.id), - refreshToken: issueToken({ type: "refresh", jti: newJti }), - }; }; diff --git a/src/lib/server/services/client.ts b/src/lib/server/services/client.ts index 1f99d3a..73973bb 100644 --- a/src/lib/server/services/client.ts +++ b/src/lib/server/services/client.ts @@ -3,7 +3,6 @@ import { createClient, getClient, getClientByPubKeys, - countClientByPubKey, createUserClient, getAllUserClients, getUserClient, @@ -12,6 +11,7 @@ import { getUserClientChallenge, markUserClientChallengeAsUsed, } from "$lib/server/db/client"; +import { IntegrityError } from "$lib/server/db/error"; import { verifyPubKey, verifySignature, generateChallenge } from "$lib/server/modules/crypto"; import { isInitialMekNeeded } from "$lib/server/modules/mek"; import env from "$lib/server/loadenv"; @@ -29,8 +29,8 @@ export const getUserClientList = async (userId: number) => { const expiresAt = () => new Date(Date.now() + env.challenge.userClientExp); const createUserClientChallenge = async ( - userId: number, ip: string, + userId: number, clientId: number, encPubKey: string, ) => { @@ -45,33 +45,59 @@ export const registerUserClient = async ( encPubKey: string, sigPubKey: string, ) => { - let clientId; - const client = await getClientByPubKeys(encPubKey, sigPubKey); if (client) { - const userClient = await getUserClient(userId, client.id); - if (userClient) { - error(409, "Client already registered"); + try { + await createUserClient(userId, client.id); + return { challenge: await createUserClientChallenge(ip, userId, client.id, encPubKey) }; + } catch (e) { + if (e instanceof IntegrityError && e.message === "User client already exists") { + error(409, "Client already registered"); + } + throw e; } - - await createUserClient(userId, client.id); - clientId = client.id; } else { - if (!verifyPubKey(encPubKey) || !verifyPubKey(sigPubKey)) { + if (encPubKey === sigPubKey) { + error(400, "Same public keys"); + } else if (!verifyPubKey(encPubKey) || !verifyPubKey(sigPubKey)) { error(400, "Invalid public key(s)"); - } else if (encPubKey === sigPubKey) { - error(400, "Public keys must be different"); - } else if ( - (await countClientByPubKey(encPubKey)) > 0 || - (await countClientByPubKey(sigPubKey)) > 0 - ) { - error(409, "Public key(s) already registered"); } - clientId = await createClient(encPubKey, sigPubKey, userId); + try { + const clientId = await createClient(encPubKey, sigPubKey, userId); + return { challenge: await createUserClientChallenge(ip, userId, clientId, encPubKey) }; + } catch (e) { + if (e instanceof IntegrityError && e.message === "Public key(s) already registered") { + error(409, "Public key(s) already used"); + } + throw e; + } + } +}; + +export const verifyUserClient = async ( + userId: number, + ip: string, + answer: string, + answerSig: string, +) => { + const challenge = await getUserClientChallenge(answer, ip); + if (!challenge) { + error(403, "Invalid challenge answer"); + } else if (challenge.userId !== userId) { + error(403, "Forbidden"); } - return { challenge: await createUserClientChallenge(userId, ip, clientId, encPubKey) }; + await markUserClientChallengeAsUsed(challenge.id); + + const client = await getClient(challenge.clientId); + if (!client) { + error(500, "Invalid challenge answer"); + } else if (!verifySignature(Buffer.from(answer, "base64"), answerSig, client.sigPubKey)) { + error(403, "Invalid challenge answer signature"); + } + + await setUserClientStateToPending(userId, challenge.clientId); }; export const getUserClientStatus = async (userId: number, clientId: number) => { @@ -85,27 +111,3 @@ export const getUserClientStatus = async (userId: number, clientId: number) => { isInitialMekNeeded: await isInitialMekNeeded(userId), }; }; - -export const verifyUserClient = async ( - userId: number, - ip: string, - answer: string, - answerSig: string, -) => { - const challenge = await getUserClientChallenge(answer, ip); - if (!challenge) { - error(401, "Invalid challenge answer"); - } else if (challenge.userId !== userId) { - error(403, "Forbidden"); - } - - const client = await getClient(challenge.clientId); - if (!client) { - error(500, "Invalid challenge answer"); - } else if (!verifySignature(Buffer.from(answer, "base64"), answerSig, client.sigPubKey)) { - error(401, "Invalid challenge answer signature"); - } - - await markUserClientChallengeAsUsed(challenge.id); - await setUserClientStateToPending(userId, challenge.clientId); -}; diff --git a/src/lib/server/services/directory.ts b/src/lib/server/services/directory.ts index 01d39d5..5dc408f 100644 --- a/src/lib/server/services/directory.ts +++ b/src/lib/server/services/directory.ts @@ -1,44 +1,15 @@ import { error } from "@sveltejs/kit"; import { unlink } from "fs/promises"; +import { IntegrityError } from "$lib/server/db/error"; import { + registerDirectory, getAllDirectoriesByParent, - registerNewDirectory, getDirectory, setDirectoryEncName, unregisterDirectory, getAllFilesByParent, type NewDirectoryParams, } from "$lib/server/db/file"; -import { getActiveMekVersion } from "$lib/server/db/mek"; - -export const deleteDirectory = async (userId: number, directoryId: number) => { - const directory = await getDirectory(userId, directoryId); - if (!directory) { - error(404, "Invalid directory id"); - } - - const filePaths = await unregisterDirectory(userId, directoryId); - filePaths.map((path) => unlink(path)); // Intended -}; - -export const renameDirectory = async ( - userId: number, - directoryId: number, - dekVersion: Date, - newEncName: string, - newEncNameIv: string, -) => { - const directory = await getDirectory(userId, directoryId); - if (!directory) { - error(404, "Invalid directory id"); - } else if (directory.dekVersion.getTime() !== dekVersion.getTime()) { - error(400, "Invalid DEK version"); - } - - if (!(await setDirectoryEncName(userId, directoryId, dekVersion, newEncName, newEncNameIv))) { - error(500, "Invalid directory id or DEK version"); - } -}; export const getDirectoryInformation = async (userId: number, directoryId: "root" | number) => { const directory = directoryId !== "root" ? await getDirectory(userId, directoryId) : undefined; @@ -62,19 +33,52 @@ export const getDirectoryInformation = async (userId: number, directoryId: "root }; }; -export const createDirectory = async (params: NewDirectoryParams) => { - const activeMekVersion = await getActiveMekVersion(params.userId); - if (activeMekVersion === null) { - error(500, "Invalid MEK version"); - } else if (activeMekVersion !== params.mekVersion) { - error(400, "Invalid MEK version"); +export const deleteDirectory = async (userId: number, directoryId: number) => { + try { + const filePaths = await unregisterDirectory(userId, directoryId); + filePaths.map((path) => unlink(path)); // Intended + } catch (e) { + if (e instanceof IntegrityError && e.message === "Directory not found") { + error(404, "Invalid directory id"); + } + throw e; } +}; +export const renameDirectory = async ( + userId: number, + directoryId: number, + dekVersion: Date, + newEncName: string, + newEncNameIv: string, +) => { + try { + await setDirectoryEncName(userId, directoryId, dekVersion, newEncName, newEncNameIv); + } catch (e) { + if (e instanceof IntegrityError) { + if (e.message === "Directory not found") { + error(404, "Invalid directory id"); + } else if (e.message === "Invalid DEK version") { + error(400, "Invalid DEK version"); + } + } + throw e; + } +}; + +export const createDirectory = async (params: NewDirectoryParams) => { const oneMinuteAgo = new Date(Date.now() - 60 * 1000); const oneMinuteLater = new Date(Date.now() + 60 * 1000); if (params.dekVersion <= oneMinuteAgo || params.dekVersion >= oneMinuteLater) { error(400, "Invalid DEK version"); } - await registerNewDirectory(params); + try { + await registerDirectory(params); + } catch (e) { + if (e instanceof IntegrityError && e.message === "Inactive MEK version") { + error(400, "Invalid MEK version"); + } + throw e; + } }; diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index 7bf9b72..c87414c 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -1,77 +1,19 @@ import { error } from "@sveltejs/kit"; -import { createReadStream, createWriteStream, ReadStream, WriteStream } from "fs"; +import { createReadStream, createWriteStream } from "fs"; import { mkdir, stat, unlink } from "fs/promises"; import { dirname } from "path"; +import { Readable, Writable } from "stream"; import { v4 as uuidv4 } from "uuid"; +import { IntegrityError } from "$lib/server/db/error"; import { - registerNewFile, + registerFile, getFile, setFileEncName, unregisterFile, type NewFileParams, } from "$lib/server/db/file"; -import { getActiveMekVersion } from "$lib/server/db/mek"; import env from "$lib/server/loadenv"; -export const deleteFile = async (userId: number, fileId: number) => { - const file = await getFile(userId, fileId); - if (!file) { - error(404, "Invalid file id"); - } - - const path = await unregisterFile(userId, fileId); - if (!path) { - error(500, "Invalid file id"); - } - - unlink(path); // Intended -}; - -const convertToReadableStream = (readStream: ReadStream) => { - return new ReadableStream({ - start: (controller) => { - readStream.on("data", (chunk) => controller.enqueue(new Uint8Array(chunk as Buffer))); - readStream.on("end", () => controller.close()); - readStream.on("error", (e) => controller.error(e)); - }, - cancel: () => { - readStream.destroy(); - }, - }); -}; - -export const getFileStream = async (userId: number, fileId: number) => { - const file = await getFile(userId, fileId); - if (!file) { - error(404, "Invalid file id"); - } - - const { size } = await stat(file.path); - return { - encContentStream: convertToReadableStream(createReadStream(file.path)), - encContentSize: size, - }; -}; - -export const renameFile = async ( - userId: number, - fileId: number, - dekVersion: Date, - newEncName: string, - newEncNameIv: string, -) => { - const file = await getFile(userId, fileId); - if (!file) { - error(404, "Invalid file id"); - } else if (file.dekVersion.getTime() !== dekVersion.getTime()) { - error(400, "Invalid DEK version"); - } - - if (!(await setFileEncName(userId, fileId, dekVersion, newEncName, newEncNameIv))) { - error(500, "Invalid file id or DEK version"); - } -}; - export const getFileInformation = async (userId: number, fileId: number) => { const file = await getFile(userId, fileId); if (!file) { @@ -89,20 +31,50 @@ export const getFileInformation = async (userId: number, fileId: number) => { }; }; -const convertToWritableStream = (writeStream: WriteStream) => { - return new WritableStream({ - write: (chunk) => - new Promise((resolve, reject) => { - writeStream.write(chunk, (e) => { - if (e) { - reject(e); - } else { - resolve(); - } - }); - }), - close: () => new Promise((resolve) => writeStream.end(resolve)), - }); +export const deleteFile = async (userId: number, fileId: number) => { + try { + const filePath = await unregisterFile(userId, fileId); + unlink(filePath); // Intended + } catch (e) { + if (e instanceof IntegrityError && e.message === "File not found") { + error(404, "Invalid file id"); + } + throw e; + } +}; + +export const getFileStream = async (userId: number, fileId: number) => { + const file = await getFile(userId, fileId); + if (!file) { + error(404, "Invalid file id"); + } + + const { size } = await stat(file.path); + return { + encContentStream: Readable.toWeb(createReadStream(file.path)), + encContentSize: size, + }; +}; + +export const renameFile = async ( + userId: number, + fileId: number, + dekVersion: Date, + newEncName: string, + newEncNameIv: string, +) => { + try { + await setFileEncName(userId, fileId, dekVersion, newEncName, newEncNameIv); + } catch (e) { + if (e instanceof IntegrityError) { + if (e.message === "File not found") { + error(404, "Invalid file id"); + } else if (e.message === "Invalid DEK version") { + error(400, "Invalid DEK version"); + } + } + throw e; + } }; const safeUnlink = async (path: string) => { @@ -113,13 +85,6 @@ export const uploadFile = async ( params: Omit, encContentStream: ReadableStream, ) => { - const activeMekVersion = await getActiveMekVersion(params.userId); - if (activeMekVersion === null) { - error(500, "Invalid MEK version"); - } else if (activeMekVersion !== params.mekVersion) { - error(400, "Invalid MEK version"); - } - const oneMinuteAgo = new Date(Date.now() - 60 * 1000); const oneMinuteLater = new Date(Date.now() + 60 * 1000); if (params.dekVersion <= oneMinuteAgo || params.dekVersion >= oneMinuteLater) { @@ -131,14 +96,20 @@ export const uploadFile = async ( try { await encContentStream.pipeTo( - convertToWritableStream(createWriteStream(path, { flags: "wx", mode: 0o600 })), + Writable.toWeb(createWriteStream(path, { flags: "wx", mode: 0o600 })), ); - await registerNewFile({ + await registerFile({ ...params, path, }); } catch (e) { await safeUnlink(path); + + if (e instanceof IntegrityError) { + if (e.message === "Inactive MEK version") { + error(400, "Invalid MEK version"); + } + } throw e; } }; diff --git a/src/lib/server/services/mek.ts b/src/lib/server/services/mek.ts index 95caef9..e0deeb0 100644 --- a/src/lib/server/services/mek.ts +++ b/src/lib/server/services/mek.ts @@ -1,7 +1,8 @@ import { error } from "@sveltejs/kit"; import { setUserClientStateToActive } from "$lib/server/db/client"; +import { IntegrityError } from "$lib/server/db/error"; import { registerInitialMek, getAllValidClientMeks } from "$lib/server/db/mek"; -import { isInitialMekNeeded, verifyClientEncMekSig } from "$lib/server/modules/mek"; +import { verifyClientEncMekSig } from "$lib/server/modules/mek"; export const getClientMekList = async (userId: number, clientId: number) => { const clientMeks = await getAllValidClientMeks(userId, clientId); @@ -21,12 +22,17 @@ export const registerInitialActiveMek = async ( encMek: string, encMekSig: string, ) => { - if (!(await isInitialMekNeeded(userId))) { - error(409, "Initial MEK already registered"); - } else if (!(await verifyClientEncMekSig(userId, createdBy, 1, encMek, encMekSig))) { + if (!(await verifyClientEncMekSig(userId, createdBy, 1, encMek, encMekSig))) { error(400, "Invalid signature"); } - await registerInitialMek(userId, createdBy, encMek, encMekSig); - await setUserClientStateToActive(userId, createdBy); + try { + await registerInitialMek(userId, createdBy, encMek, encMekSig); + await setUserClientStateToActive(userId, createdBy); + } catch (e) { + if (e instanceof IntegrityError && e.message === "MEK already registered") { + error(409, "Initial MEK already registered"); + } + throw e; + } }; diff --git a/src/routes/api/file/[id]/download/+server.ts b/src/routes/api/file/[id]/download/+server.ts index 42b832f..58f915d 100644 --- a/src/routes/api/file/[id]/download/+server.ts +++ b/src/routes/api/file/[id]/download/+server.ts @@ -16,7 +16,7 @@ export const GET: RequestHandler = async ({ cookies, params }) => { const { id } = zodRes.data; const { encContentStream, encContentSize } = await getFileStream(userId, id); - return new Response(encContentStream, { + return new Response(encContentStream as ReadableStream, { headers: { "Content-Type": "application/octet-stream", "Content-Length": encContentSize.toString(), From 1a86c8d9e01777341083a2635ccd1238f7288f3e Mon Sep 17 00:00:00 2001 From: static Date: Sun, 12 Jan 2025 07:28:38 +0900 Subject: [PATCH 03/17] =?UTF-8?q?=EB=B0=B1=EC=97=94=EB=93=9C=EC=97=90?= =?UTF-8?q?=EC=84=9C=20JWT=EA=B0=80=20=EC=95=84=EB=8B=8C=20=EC=84=B8?= =?UTF-8?q?=EC=85=98=20ID=20=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 7 +- docker-compose.yaml | 7 +- package.json | 2 - pnpm-lock.yaml | 94 ----------- src/app.d.ts | 14 +- src/hooks.server.ts | 30 +--- src/lib/server/db/error.ts | 8 +- src/lib/server/db/schema/client.ts | 2 +- src/lib/server/db/schema/index.ts | 2 +- .../server/db/schema/{token.ts => session.ts} | 21 ++- src/lib/server/db/session.ts | 124 ++++++++++++++ src/lib/server/db/token.ts | 133 --------------- src/lib/server/loadenv.ts | 11 +- src/lib/server/middlewares/authenticate.ts | 34 ++++ src/lib/server/middlewares/index.ts | 2 + src/lib/server/middlewares/setAgentInfo.ts | 18 ++ src/lib/server/modules/auth.ts | 159 +++++++++++------- src/lib/server/modules/crypto.ts | 33 +++- src/lib/server/schemas/auth.ts | 12 +- src/lib/server/services/auth.ts | 153 ++++------------- src/routes/api/auth/login/+server.ts | 14 +- src/routes/api/auth/logout/+server.ts | 13 +- src/routes/api/auth/refreshToken/+server.ts | 23 --- src/routes/api/auth/upgradeSession/+server.ts | 26 +++ .../api/auth/upgradeSession/verify/+server.ts | 16 ++ src/routes/api/auth/upgradeToken/+server.ts | 25 --- .../api/auth/upgradeToken/verify/+server.ts | 33 ---- src/routes/api/client/list/+server.ts | 12 +- src/routes/api/client/register/+server.ts | 11 +- .../api/client/register/verify/+server.ts | 11 +- src/routes/api/client/status/+server.ts | 12 +- src/routes/api/directory/[id]/+server.ts | 4 +- .../api/directory/[id]/delete/+server.ts | 4 +- .../api/directory/[id]/rename/+server.ts | 4 +- src/routes/api/directory/create/+server.ts | 4 +- src/routes/api/file/[id]/+server.ts | 4 +- src/routes/api/file/[id]/delete/+server.ts | 4 +- src/routes/api/file/[id]/download/+server.ts | 4 +- src/routes/api/file/[id]/rename/+server.ts | 4 +- src/routes/api/file/upload/+server.ts | 4 +- src/routes/api/mek/list/+server.ts | 4 +- .../api/mek/register/initial/+server.ts | 9 +- 42 files changed, 487 insertions(+), 624 deletions(-) rename src/lib/server/db/schema/{token.ts => session.ts} (52%) create mode 100644 src/lib/server/db/session.ts delete mode 100644 src/lib/server/db/token.ts create mode 100644 src/lib/server/middlewares/authenticate.ts create mode 100644 src/lib/server/middlewares/index.ts create mode 100644 src/lib/server/middlewares/setAgentInfo.ts delete mode 100644 src/routes/api/auth/refreshToken/+server.ts create mode 100644 src/routes/api/auth/upgradeSession/+server.ts create mode 100644 src/routes/api/auth/upgradeSession/verify/+server.ts delete mode 100644 src/routes/api/auth/upgradeToken/+server.ts delete mode 100644 src/routes/api/auth/upgradeToken/verify/+server.ts diff --git a/.env.example b/.env.example index c0eef8e..128bd9f 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,9 @@ # Required environment variables -JWT_SECRET= +SESSION_SECRET= # Optional environment variables DATABASE_URL= -JWT_ACCESS_TOKEN_EXPIRES= -JWT_REFRESH_TOKEN_EXPIRES= +SESSION_EXPIRES= USER_CLIENT_CHALLENGE_EXPIRES= -TOKEN_UPGRADE_CHALLENGE_EXPIRES= +SESSION_UPGRADE_CHALLENGE_EXPIRES= LIBRARY_PATH= diff --git a/docker-compose.yaml b/docker-compose.yaml index ffe08ab..aecd8c8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -8,11 +8,10 @@ services: environment: # ArkVault - DATABASE_URL=/app/data/database.sqlite - - JWT_SECRET=${JWT_SECRET:?} # Required - - JWT_ACCESS_TOKEN_EXPIRES - - JWT_REFRESH_TOKEN_EXPIRES + - SESSION_SECRET=${SESSION_SECRET:?} # Required + - SESSION_EXPIRES - USER_CLIENT_CHALLENGE_EXPIRES - - TOKEN_UPGRADE_CHALLENGE_EXPIRES + - SESSION_UPGRADE_CHALLENGE_EXPIRES - LIBRARY_PATH=/app/data/library # SvelteKit - ADDRESS_HEADER=${TRUST_PROXY:+X-Forwarded-For} diff --git a/package.json b/package.json index c15ca72..41cbbde 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ "@sveltejs/vite-plugin-svelte": "^4.0.4", "@types/better-sqlite3": "^7.6.12", "@types/file-saver": "^2.0.7", - "@types/jsonwebtoken": "^9.0.7", "@types/ms": "^0.7.34", "@types/node-schedule": "^2.1.7", "autoprefixer": "^10.4.20", @@ -53,7 +52,6 @@ "argon2": "^0.41.1", "better-sqlite3": "^11.7.2", "drizzle-orm": "^0.33.0", - "jsonwebtoken": "^9.0.2", "ms": "^2.1.3", "node-schedule": "^2.1.1", "uuid": "^11.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4e3c1fe..e6e96c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,9 +17,6 @@ importers: drizzle-orm: specifier: ^0.33.0 version: 0.33.0(@types/better-sqlite3@7.6.12)(better-sqlite3@11.7.2) - jsonwebtoken: - specifier: ^9.0.2 - version: 9.0.2 ms: specifier: ^2.1.3 version: 2.1.3 @@ -54,9 +51,6 @@ importers: '@types/file-saver': specifier: ^2.0.7 version: 2.0.7 - '@types/jsonwebtoken': - specifier: ^9.0.7 - version: 9.0.7 '@types/ms': specifier: ^0.7.34 version: 0.7.34 @@ -854,9 +848,6 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/jsonwebtoken@9.0.7': - resolution: {integrity: sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==} - '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} @@ -1016,9 +1007,6 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - buffer-equal-constant-time@1.0.1: - resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -1228,9 +1216,6 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - ecdsa-sig-formatter@1.0.11: - resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - electron-to-chromium@1.5.79: resolution: {integrity: sha512-nYOxJNxQ9Om4EC88BE4pPoNI8xwSFf8pU/BAeOl4Hh/b/i6V4biTAzwV7pXi3ARKeoYO5JZKMIXTryXSVer5RA==} @@ -1555,16 +1540,6 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - jsonwebtoken@9.0.2: - resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} - engines: {node: '>=12', npm: '>=6'} - - jwa@1.4.1: - resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} - - jws@3.2.2: - resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -1604,30 +1579,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash.includes@4.3.0: - resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} - - lodash.isboolean@3.0.3: - resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} - - lodash.isinteger@4.0.4: - resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} - - lodash.isnumber@3.0.3: - resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} - - lodash.isplainobject@4.0.6: - resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - - lodash.isstring@4.0.1: - resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} - lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash.once@4.1.1: - resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} - long-timeout@0.1.1: resolution: {integrity: sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==} @@ -2809,10 +2763,6 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/jsonwebtoken@9.0.7': - dependencies: - '@types/node': 22.10.5 - '@types/ms@0.7.34': {} '@types/node-schedule@2.1.7': @@ -3001,8 +2951,6 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.2(browserslist@4.24.4) - buffer-equal-constant-time@1.0.1: {} - buffer-from@1.1.2: {} buffer@5.7.1: @@ -3108,10 +3056,6 @@ snapshots: eastasianwidth@0.2.0: {} - ecdsa-sig-formatter@1.0.11: - dependencies: - safe-buffer: 5.2.1 - electron-to-chromium@1.5.79: {} emoji-regex@8.0.0: {} @@ -3499,30 +3443,6 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} - jsonwebtoken@9.0.2: - dependencies: - jws: 3.2.2 - lodash.includes: 4.3.0 - lodash.isboolean: 3.0.3 - lodash.isinteger: 4.0.4 - lodash.isnumber: 3.0.3 - lodash.isplainobject: 4.0.6 - lodash.isstring: 4.0.1 - lodash.once: 4.1.1 - ms: 2.1.3 - semver: 7.6.3 - - jwa@1.4.1: - dependencies: - buffer-equal-constant-time: 1.0.1 - ecdsa-sig-formatter: 1.0.11 - safe-buffer: 5.2.1 - - jws@3.2.2: - dependencies: - jwa: 1.4.1 - safe-buffer: 5.2.1 - keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -3555,22 +3475,8 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash.includes@4.3.0: {} - - lodash.isboolean@3.0.3: {} - - lodash.isinteger@4.0.4: {} - - lodash.isnumber@3.0.3: {} - - lodash.isplainobject@4.0.6: {} - - lodash.isstring@4.0.1: {} - lodash.merge@4.6.2: {} - lodash.once@4.1.1: {} - long-timeout@0.1.1: {} lru-cache@10.4.3: {} diff --git a/src/app.d.ts b/src/app.d.ts index 0904582..bfe1252 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -5,11 +5,15 @@ import "unplugin-icons/types/svelte"; declare global { namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface PageState {} - // interface Platform {} + interface Locals { + ip: string; + userAgent: string; + session?: { + id: string; + userId: number; + clientId?: number; + }; + } } } diff --git a/src/hooks.server.ts b/src/hooks.server.ts index e237845..9dac88c 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,34 +1,22 @@ -import { redirect, type ServerInit, type Handle } from "@sveltejs/kit"; +import type { ServerInit } from "@sveltejs/kit"; +import { sequence } from "@sveltejs/kit/hooks"; import schedule from "node-schedule"; import { cleanupExpiredUserClientChallenges } from "$lib/server/db/client"; import { migrateDB } from "$lib/server/db/drizzle"; import { - cleanupExpiredRefreshTokens, - cleanupExpiredTokenUpgradeChallenges, -} from "$lib/server/db/token"; + cleanupExpiredSessions, + cleanupExpiredSessionUpgradeChallenges, +} from "$lib/server/db/session"; +import { authenticate, setAgentInfo } from "$lib/server/middlewares"; export const init: ServerInit = () => { migrateDB(); schedule.scheduleJob("0 * * * *", () => { cleanupExpiredUserClientChallenges(); - cleanupExpiredRefreshTokens(); - cleanupExpiredTokenUpgradeChallenges(); + cleanupExpiredSessions(); + cleanupExpiredSessionUpgradeChallenges(); }); }; -export const handle: Handle = async ({ event, resolve }) => { - if (["/api", "/auth"].some((path) => event.url.pathname.startsWith(path))) { - return await resolve(event); - } - - const accessToken = event.cookies.get("accessToken"); - if (accessToken) { - return await resolve(event); - } else { - redirect( - 302, - "/auth/login?redirect=" + encodeURIComponent(event.url.pathname + event.url.search), - ); - } -}; +export const handle = sequence(setAgentInfo, authenticate); diff --git a/src/lib/server/db/error.ts b/src/lib/server/db/error.ts index 7644800..beadb6f 100644 --- a/src/lib/server/db/error.ts +++ b/src/lib/server/db/error.ts @@ -1,4 +1,6 @@ type IntegrityErrorMessages = + // Challenge + | "Challenge already registered" // Client | "Public key(s) already registered" | "User client already exists" @@ -9,9 +11,9 @@ type IntegrityErrorMessages = // MEK | "MEK already registered" | "Inactive MEK version" - // Token - | "Refresh token not found" - | "Refresh token already registered"; + // Session + | "Session not found" + | "Session already exists"; export class IntegrityError extends Error { constructor(public message: IntegrityErrorMessages) { diff --git a/src/lib/server/db/schema/client.ts b/src/lib/server/db/schema/client.ts index 437695d..eacd9c9 100644 --- a/src/lib/server/db/schema/client.ts +++ b/src/lib/server/db/schema/client.ts @@ -39,7 +39,7 @@ export const userClientChallenge = sqliteTable("user_client_challenge", { clientId: integer("client_id") .notNull() .references(() => client.id), - answer: text("challenge").notNull().unique(), // Base64 + answer: text("answer").notNull().unique(), // Base64 allowedIp: text("allowed_ip").notNull(), expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(), isUsed: integer("is_used", { mode: "boolean" }).notNull().default(false), diff --git a/src/lib/server/db/schema/index.ts b/src/lib/server/db/schema/index.ts index 4344b00..41fd4fe 100644 --- a/src/lib/server/db/schema/index.ts +++ b/src/lib/server/db/schema/index.ts @@ -1,5 +1,5 @@ export * from "./client"; export * from "./file"; export * from "./mek"; -export * from "./token"; +export * from "./session"; export * from "./user"; diff --git a/src/lib/server/db/schema/token.ts b/src/lib/server/db/schema/session.ts similarity index 52% rename from src/lib/server/db/schema/token.ts rename to src/lib/server/db/schema/session.ts index 72106d7..5f2129d 100644 --- a/src/lib/server/db/schema/token.ts +++ b/src/lib/server/db/schema/session.ts @@ -2,31 +2,34 @@ import { sqliteTable, text, integer, unique } from "drizzle-orm/sqlite-core"; import { client } from "./client"; import { user } from "./user"; -export const refreshToken = sqliteTable( - "refresh_token", +export const session = sqliteTable( + "session", { - id: text("id").primaryKey(), + id: text("id").notNull().primaryKey(), userId: integer("user_id") .notNull() .references(() => user.id), clientId: integer("client_id").references(() => client.id), - expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(), // Only used for cleanup + createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(), + lastUsedAt: integer("last_used_at", { mode: "timestamp_ms" }).notNull(), + lastUsedByIp: text("last_used_by_ip"), + lastUsedByUserAgent: text("last_used_by_user_agent"), }, (t) => ({ unq: unique().on(t.userId, t.clientId), }), ); -export const tokenUpgradeChallenge = sqliteTable("token_upgrade_challenge", { +export const sessionUpgradeChallenge = sqliteTable("session_upgrade_challenge", { id: integer("id").primaryKey(), - refreshTokenId: text("refresh_token_id") + sessionId: text("session_id") .notNull() - .references(() => refreshToken.id), + .references(() => session.id) + .unique(), clientId: integer("client_id") .notNull() .references(() => client.id), - answer: text("challenge").notNull().unique(), // Base64 + answer: text("answer").notNull().unique(), // Base64 allowedIp: text("allowed_ip").notNull(), expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(), - isUsed: integer("is_used", { mode: "boolean" }).notNull().default(false), }); diff --git a/src/lib/server/db/session.ts b/src/lib/server/db/session.ts new file mode 100644 index 0000000..276090a --- /dev/null +++ b/src/lib/server/db/session.ts @@ -0,0 +1,124 @@ +import { SqliteError } from "better-sqlite3"; +import { and, eq, gt, lte, isNull } from "drizzle-orm"; +import env from "$lib/server/loadenv"; +import db from "./drizzle"; +import { IntegrityError } from "./error"; +import { session, sessionUpgradeChallenge } from "./schema"; + +export const createSession = async ( + userId: number, + clientId: number | null, + sessionId: string, + ip: string | null, + userAgent: string | null, +) => { + try { + const now = new Date(); + await db.insert(session).values({ + id: sessionId, + userId, + clientId, + createdAt: now, + lastUsedAt: now, + lastUsedByIp: ip, + lastUsedByUserAgent: userAgent, + }); + } catch (e) { + if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { + throw new IntegrityError("Session already exists"); + } + throw e; + } +}; + +export const refreshSession = async ( + sessionId: string, + ip: string | null, + userAgent: string | null, +) => { + const now = new Date(); + const sessions = await db + .update(session) + .set({ + lastUsedAt: now, + lastUsedByIp: ip, + lastUsedByUserAgent: userAgent, + }) + .where( + and( + eq(session.id, sessionId), + gt(session.lastUsedAt, new Date(now.getTime() - env.session.exp)), + ), + ) + .returning({ userId: session.userId, clientId: session.clientId }); + if (!sessions[0]) { + throw new IntegrityError("Session not found"); + } + return sessions[0]; +}; + +export const upgradeSession = async (sessionId: string, clientId: number) => { + const res = await db + .update(session) + .set({ clientId }) + .where(and(eq(session.id, sessionId), isNull(session.clientId))); + if (res.changes === 0) { + throw new IntegrityError("Session not found"); + } +}; + +export const deleteSession = async (sessionId: string) => { + await db.delete(session).where(eq(session.id, sessionId)); +}; + +export const cleanupExpiredSessions = async () => { + await db.delete(session).where(lte(session.lastUsedAt, new Date(Date.now() - env.session.exp))); +}; + +export const registerSessionUpgradeChallenge = async ( + sessionId: string, + clientId: number, + answer: string, + allowedIp: string, + expiresAt: Date, +) => { + try { + await db.insert(sessionUpgradeChallenge).values({ + sessionId, + clientId, + answer, + allowedIp, + expiresAt, + }); + } catch (e) { + if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { + throw new IntegrityError("Challenge already registered"); + } + throw e; + } +}; + +export const consumeSessionUpgradeChallenge = async ( + sessionId: string, + answer: string, + ip: string, +) => { + const challenges = await db + .delete(sessionUpgradeChallenge) + .where( + and( + eq(sessionUpgradeChallenge.sessionId, sessionId), + eq(sessionUpgradeChallenge.answer, answer), + eq(sessionUpgradeChallenge.allowedIp, ip), + gt(sessionUpgradeChallenge.expiresAt, new Date()), + ), + ) + .returning({ clientId: sessionUpgradeChallenge.clientId }); + return challenges[0] ?? null; +}; + +export const cleanupExpiredSessionUpgradeChallenges = async () => { + await db + .delete(sessionUpgradeChallenge) + .where(lte(sessionUpgradeChallenge.expiresAt, new Date())); +}; diff --git a/src/lib/server/db/token.ts b/src/lib/server/db/token.ts deleted file mode 100644 index 25bf1de..0000000 --- a/src/lib/server/db/token.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { SqliteError } from "better-sqlite3"; -import { and, eq, gt, lte } from "drizzle-orm"; -import env from "$lib/server/loadenv"; -import db from "./drizzle"; -import { IntegrityError } from "./error"; -import { refreshToken, tokenUpgradeChallenge } from "./schema"; - -const expiresAt = () => new Date(Date.now() + env.jwt.refreshExp); - -export const registerRefreshToken = async ( - userId: number, - clientId: number | null, - tokenId: string, -) => { - try { - await db.insert(refreshToken).values({ - id: tokenId, - userId, - clientId, - expiresAt: expiresAt(), - }); - } catch (e) { - if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { - throw new IntegrityError("Refresh token already registered"); - } - throw e; - } -}; - -export const getRefreshToken = async (tokenId: string) => { - const tokens = await db.select().from(refreshToken).where(eq(refreshToken.id, tokenId)).limit(1); - return tokens[0] ?? null; -}; - -export const rotateRefreshToken = async (oldTokenId: string, newTokenId: string) => { - await db.transaction( - async (tx) => { - await tx - .delete(tokenUpgradeChallenge) - .where(eq(tokenUpgradeChallenge.refreshTokenId, oldTokenId)); - - const res = await tx - .update(refreshToken) - .set({ - id: newTokenId, - expiresAt: expiresAt(), - }) - .where(eq(refreshToken.id, oldTokenId)); - if (res.changes === 0) { - throw new IntegrityError("Refresh token not found"); - } - }, - { behavior: "exclusive" }, - ); -}; - -export const upgradeRefreshToken = async ( - oldTokenId: string, - newTokenId: string, - clientId: number, -) => { - await db.transaction( - async (tx) => { - await tx - .delete(tokenUpgradeChallenge) - .where(eq(tokenUpgradeChallenge.refreshTokenId, oldTokenId)); - - const res = await tx - .update(refreshToken) - .set({ - id: newTokenId, - clientId, - expiresAt: expiresAt(), - }) - .where(eq(refreshToken.id, oldTokenId)); - if (res.changes === 0) { - throw new IntegrityError("Refresh token not found"); - } - }, - { behavior: "exclusive" }, - ); -}; - -export const revokeRefreshToken = async (tokenId: string) => { - await db.delete(refreshToken).where(eq(refreshToken.id, tokenId)); -}; - -export const cleanupExpiredRefreshTokens = async () => { - await db.delete(refreshToken).where(lte(refreshToken.expiresAt, new Date())); -}; - -export const registerTokenUpgradeChallenge = async ( - tokenId: string, - clientId: number, - answer: string, - allowedIp: string, - expiresAt: Date, -) => { - await db.insert(tokenUpgradeChallenge).values({ - refreshTokenId: tokenId, - clientId, - answer, - allowedIp, - expiresAt, - }); -}; - -export const getTokenUpgradeChallenge = async (answer: string, ip: string) => { - const challenges = await db - .select() - .from(tokenUpgradeChallenge) - .where( - and( - eq(tokenUpgradeChallenge.answer, answer), - eq(tokenUpgradeChallenge.allowedIp, ip), - gt(tokenUpgradeChallenge.expiresAt, new Date()), - eq(tokenUpgradeChallenge.isUsed, false), - ), - ) - .limit(1); - return challenges[0] ?? null; -}; - -export const markTokenUpgradeChallengeAsUsed = async (id: number) => { - await db - .update(tokenUpgradeChallenge) - .set({ isUsed: true }) - .where(eq(tokenUpgradeChallenge.id, id)); -}; - -export const cleanupExpiredTokenUpgradeChallenges = async () => { - await db.delete(tokenUpgradeChallenge).where(lte(tokenUpgradeChallenge.expiresAt, new Date())); -}; diff --git a/src/lib/server/loadenv.ts b/src/lib/server/loadenv.ts index a57eff8..01e442a 100644 --- a/src/lib/server/loadenv.ts +++ b/src/lib/server/loadenv.ts @@ -3,19 +3,18 @@ import { building } from "$app/environment"; import { env } from "$env/dynamic/private"; if (!building) { - if (!env.JWT_SECRET) throw new Error("JWT_SECRET is not set"); + if (!env.SESSION_SECRET) throw new Error("SESSION_SECRET not set"); } export default { databaseUrl: env.DATABASE_URL || "local.db", - jwt: { - secret: env.JWT_SECRET, - accessExp: ms(env.JWT_ACCESS_TOKEN_EXPIRES || "5m"), - refreshExp: ms(env.JWT_REFRESH_TOKEN_EXPIRES || "14d"), + session: { + secret: env.SESSION_SECRET!, + exp: ms(env.SESSION_EXPIRES || "14d"), }, challenge: { userClientExp: ms(env.USER_CLIENT_CHALLENGE_EXPIRES || "5m"), - tokenUpgradeExp: ms(env.TOKEN_UPGRADE_CHALLENGE_EXPIRES || "5m"), + sessionUpgradeExp: ms(env.SESSION_UPGRADE_CHALLENGE_EXPIRES || "5m"), }, libraryPath: env.LIBRARY_PATH || "library", }; diff --git a/src/lib/server/middlewares/authenticate.ts b/src/lib/server/middlewares/authenticate.ts new file mode 100644 index 0000000..f484578 --- /dev/null +++ b/src/lib/server/middlewares/authenticate.ts @@ -0,0 +1,34 @@ +import { error, redirect, type Handle } from "@sveltejs/kit"; +import { authenticate, AuthenticationError } from "$lib/server/modules/auth"; + +const whitelist = ["/auth/login", "/api/auth/login"]; + +export const authenticateMiddleware: Handle = async ({ event, resolve }) => { + const { pathname, search } = event.url; + if (whitelist.some((path) => pathname.startsWith(path))) { + return await resolve(event); + } + + try { + const sessionIdSigned = event.cookies.get("sessionId"); + if (!sessionIdSigned) { + throw new AuthenticationError(401, "Session id not found"); + } + + const { ip, userAgent } = event.locals; + event.locals.session = await authenticate(sessionIdSigned, ip, userAgent); + } catch (e) { + if (e instanceof AuthenticationError) { + if (pathname.startsWith("/api")) { + error(e.status, e.message); + } else { + redirect(302, "/auth/login?redirect=" + encodeURIComponent(pathname + search)); + } + } + throw e; + } + + return await resolve(event); +}; + +export default authenticateMiddleware; diff --git a/src/lib/server/middlewares/index.ts b/src/lib/server/middlewares/index.ts new file mode 100644 index 0000000..4af122e --- /dev/null +++ b/src/lib/server/middlewares/index.ts @@ -0,0 +1,2 @@ +export { default as authenticate } from "./authenticate"; +export { default as setAgentInfo } from "./setAgentInfo"; diff --git a/src/lib/server/middlewares/setAgentInfo.ts b/src/lib/server/middlewares/setAgentInfo.ts new file mode 100644 index 0000000..d272f2a --- /dev/null +++ b/src/lib/server/middlewares/setAgentInfo.ts @@ -0,0 +1,18 @@ +import { error, type Handle } from "@sveltejs/kit"; + +export const setAgentInfoMiddleware: Handle = async ({ event, resolve }) => { + const ip = event.getClientAddress(); + const userAgent = event.request.headers.get("User-Agent"); + if (!ip) { + error(500, "IP address not found"); + } else if (!userAgent) { + error(400, "User agent not found"); + } + + event.locals.ip = ip; + event.locals.userAgent = userAgent; + + return await resolve(event); +}; + +export default setAgentInfoMiddleware; diff --git a/src/lib/server/modules/auth.ts b/src/lib/server/modules/auth.ts index 37248ed..4e03783 100644 --- a/src/lib/server/modules/auth.ts +++ b/src/lib/server/modules/auth.ts @@ -1,90 +1,127 @@ -import { error, type Cookies } from "@sveltejs/kit"; -import jwt from "jsonwebtoken"; +import { error } from "@sveltejs/kit"; import { getUserClient } from "$lib/server/db/client"; +import { IntegrityError } from "$lib/server/db/error"; +import { createSession, refreshSession } from "$lib/server/db/session"; import env from "$lib/server/loadenv"; +import { issueSessionId, verifySessionId } from "$lib/server/modules/crypto"; -type TokenPayload = - | { - type: "access"; - userId: number; - clientId?: number; - } - | { - type: "refresh"; - jti: string; - }; - -export enum TokenError { - EXPIRED, - INVALID, +interface Session { + sessionId: string; + userId: number; + clientId?: number; } -type Permission = "pendingClient" | "activeClient"; +interface ClientSession extends Session { + clientId: number; +} -export const issueToken = (payload: TokenPayload) => { - return jwt.sign(payload, env.jwt.secret, { - expiresIn: (payload.type === "access" ? env.jwt.accessExp : env.jwt.refreshExp) / 1000, - }); +export class AuthenticationError extends Error { + constructor( + public status: 400 | 401, + message: string, + ) { + super(message); + this.name = "AuthenticationError"; + } +} + +export const startSession = async (userId: number, ip: string, userAgent: string) => { + const { sessionId, sessionIdSigned } = await issueSessionId(32, env.session.secret); + await createSession(userId, null, sessionId, ip, userAgent); + return sessionIdSigned; }; -export const verifyToken = (token: string) => { +export const authenticate = async (sessionIdSigned: string, ip: string, userAgent: string) => { + const sessionId = verifySessionId(sessionIdSigned, env.session.secret); + if (!sessionId) { + throw new AuthenticationError(400, "Invalid session id"); + } + try { - return jwt.verify(token, env.jwt.secret) as TokenPayload; - } catch (error) { - if (error instanceof jwt.TokenExpiredError) { - return TokenError.EXPIRED; + const { userId, clientId } = await refreshSession(sessionId, ip, userAgent); + return { + id: sessionId, + userId, + clientId: clientId ?? undefined, + }; + } catch (e) { + if (e instanceof IntegrityError && e.message === "Session not found") { + throw new AuthenticationError(401, "Invalid session id"); } - return TokenError.INVALID; + throw e; } }; -export const authenticate = (cookies: Cookies) => { - const accessToken = cookies.get("accessToken"); - if (!accessToken) { - error(401, "Access token not found"); - } - - const tokenPayload = verifyToken(accessToken); - if (tokenPayload === TokenError.EXPIRED) { - error(401, "Access token expired"); - } else if (tokenPayload === TokenError.INVALID || tokenPayload.type !== "access") { - error(401, "Invalid access token"); - } - - return { - userId: tokenPayload.userId, - clientId: tokenPayload.clientId, - }; -}; +export async function authorize(locals: App.Locals, requiredPermission: "any"): Promise; export async function authorize( - cookies: Cookies, + locals: App.Locals, + requiredPermission: "notClient", +): Promise; + +export async function authorize( + locals: App.Locals, + requiredPermission: "anyClient", +): Promise; + +export async function authorize( + locals: App.Locals, requiredPermission: "pendingClient", -): Promise<{ userId: number; clientId: number }>; +): Promise; export async function authorize( - cookies: Cookies, + locals: App.Locals, requiredPermission: "activeClient", -): Promise<{ userId: number; clientId: number }>; +): Promise; export async function authorize( - cookies: Cookies, - requiredPermission: Permission, -): Promise<{ userId: number; clientId?: number }> { - const tokenPayload = authenticate(cookies); - const { userId, clientId } = tokenPayload; - const userClient = clientId ? await getUserClient(userId, clientId) : undefined; + locals: App.Locals, + requiredPermission: "any" | "notClient" | "anyClient" | "pendingClient" | "activeClient", +): Promise { + if (!locals.session) { + error(500, "Unauthenticated"); + } + + const { id: sessionId, userId, clientId } = locals.session; switch (requiredPermission) { - case "pendingClient": - if (!userClient || userClient.state !== "pending") { + case "any": + break; + case "notClient": + if (clientId) { error(403, "Forbidden"); } - return tokenPayload; - case "activeClient": - if (!userClient || userClient.state !== "active") { + break; + case "anyClient": + if (!clientId) { error(403, "Forbidden"); } - return tokenPayload; + break; + case "pendingClient": { + if (!clientId) { + error(403, "Forbidden"); + } + const userClient = await getUserClient(userId, clientId); + if (!userClient) { + error(500, "Invalid session id"); + } else if (userClient.state !== "pending") { + error(403, "Forbidden"); + } + break; + } + case "activeClient": { + if (!clientId) { + error(403, "Forbidden"); + } + const userClient = await getUserClient(userId, clientId); + if (!userClient) { + error(500, "Invalid session id"); + } else if (userClient.state !== "active") { + error(403, "Forbidden"); + } + break; + } } + + return { sessionId, userId, clientId }; } diff --git a/src/lib/server/modules/crypto.ts b/src/lib/server/modules/crypto.ts index de3dbf4..6bd7898 100644 --- a/src/lib/server/modules/crypto.ts +++ b/src/lib/server/modules/crypto.ts @@ -1,4 +1,12 @@ -import { constants, randomBytes, createPublicKey, publicEncrypt, verify } from "crypto"; +import { + constants, + randomBytes, + createPublicKey, + publicEncrypt, + verify, + createHmac, + timingSafeEqual, +} from "crypto"; import { promisify } from "util"; const makePubKeyToPem = (pubKey: string) => @@ -34,3 +42,26 @@ export const generateChallenge = async (length: number, encPubKey: string) => { const challenge = encryptAsymmetric(answer, encPubKey); return { answer, challenge }; }; + +export const issueSessionId = async (length: number, secret: string) => { + const sessionId = await promisify(randomBytes)(length); + const sessionIdHex = sessionId.toString("hex"); + const sessionIdHmac = createHmac("sha256", secret).update(sessionId).digest("hex"); + return { + sessionId: sessionIdHex, + sessionIdSigned: `${sessionIdHex}.${sessionIdHmac}`, + }; +}; + +export const verifySessionId = (sessionIdSigned: string, secret: string) => { + const [sessionIdHex, sessionIdHmac] = sessionIdSigned.split("."); + if (!sessionIdHex || !sessionIdHmac) return; + if ( + timingSafeEqual( + Buffer.from(sessionIdHmac, "hex"), + createHmac("sha256", secret).update(Buffer.from(sessionIdHex, "hex")).digest(), + ) + ) { + return sessionIdHex; + } +}; diff --git a/src/lib/server/schemas/auth.ts b/src/lib/server/schemas/auth.ts index 10c8fcc..91858c9 100644 --- a/src/lib/server/schemas/auth.ts +++ b/src/lib/server/schemas/auth.ts @@ -6,19 +6,19 @@ export const loginRequest = z.object({ }); export type LoginRequest = z.infer; -export const tokenUpgradeRequest = z.object({ +export const sessionUpgradeRequest = z.object({ encPubKey: z.string().base64().nonempty(), sigPubKey: z.string().base64().nonempty(), }); -export type TokenUpgradeRequest = z.infer; +export type SessionUpgradeRequest = z.infer; -export const tokenUpgradeResponse = z.object({ +export const sessionUpgradeResponse = z.object({ challenge: z.string().base64().nonempty(), }); -export type TokenUpgradeResponse = z.infer; +export type SessionUpgradeResponse = z.infer; -export const tokenUpgradeVerifyRequest = z.object({ +export const sessionUpgradeVerifyRequest = z.object({ answer: z.string().base64().nonempty(), answerSig: z.string().base64().nonempty(), }); -export type TokenUpgradeVerifyRequest = z.infer; +export type SessionUpgradeVerifyRequest = z.infer; diff --git a/src/lib/server/services/auth.ts b/src/lib/server/services/auth.ts index 36a3c5a..c3fee31 100644 --- a/src/lib/server/services/auth.ts +++ b/src/lib/server/services/auth.ts @@ -1,131 +1,49 @@ import { error } from "@sveltejs/kit"; import argon2 from "argon2"; -import { v4 as uuidv4 } from "uuid"; import { getClient, getClientByPubKeys, getUserClient } from "$lib/server/db/client"; -import { getUserByEmail } from "$lib/server/db/user"; -import env from "$lib/server/loadenv"; import { IntegrityError } from "$lib/server/db/error"; import { - registerRefreshToken, - getRefreshToken, - rotateRefreshToken, - upgradeRefreshToken, - revokeRefreshToken, - registerTokenUpgradeChallenge, - getTokenUpgradeChallenge, - markTokenUpgradeChallengeAsUsed, -} from "$lib/server/db/token"; -import { issueToken, verifyToken, TokenError } from "$lib/server/modules/auth"; + upgradeSession, + deleteSession, + registerSessionUpgradeChallenge, + consumeSessionUpgradeChallenge, +} from "$lib/server/db/session"; +import { getUserByEmail } from "$lib/server/db/user"; +import env from "$lib/server/loadenv"; +import { startSession } from "$lib/server/modules/auth"; import { verifySignature, generateChallenge } from "$lib/server/modules/crypto"; const verifyPassword = async (hash: string, password: string) => { return await argon2.verify(hash, password); }; -const issueAccessToken = (userId: number, clientId?: number) => { - return issueToken({ type: "access", userId, clientId }); -}; - -const issueRefreshToken = async (userId: number, clientId?: number) => { - const jti = uuidv4(); - const token = issueToken({ type: "refresh", jti }); - - try { - await registerRefreshToken(userId, clientId ?? null, jti); - return token; - } catch (e) { - if (e instanceof IntegrityError && e.message === "Refresh token already registered") { - error(409, "Already logged in"); - } - throw e; - } -}; - -export const login = async (email: string, password: string) => { +export const login = async (email: string, password: string, ip: string, userAgent: string) => { const user = await getUserByEmail(email); if (!user || !(await verifyPassword(user.password, password))) { error(401, "Invalid email or password"); } - return { - accessToken: issueAccessToken(user.id), - refreshToken: await issueRefreshToken(user.id), - }; -}; - -const verifyRefreshToken = async (refreshToken: string) => { - const tokenPayload = verifyToken(refreshToken); - if (tokenPayload === TokenError.EXPIRED) { - error(401, "Refresh token expired"); - } else if (tokenPayload === TokenError.INVALID || tokenPayload.type !== "refresh") { - error(401, "Invalid refresh token"); - } - - const tokenData = await getRefreshToken(tokenPayload.jti); - if (!tokenData) { - error(500, "Invalid refresh token"); - } - - return { - jti: tokenPayload.jti, - userId: tokenData.userId, - clientId: tokenData.clientId ?? undefined, - }; -}; - -export const logout = async (refreshToken: string) => { - const { jti } = await verifyRefreshToken(refreshToken); - await revokeRefreshToken(jti); -}; - -export const refreshToken = async (refreshToken: string) => { - const { jti: oldJti, userId, clientId } = await verifyRefreshToken(refreshToken); - const newJti = uuidv4(); - try { - await rotateRefreshToken(oldJti, newJti); - return { - accessToken: issueAccessToken(userId, clientId), - refreshToken: issueToken({ type: "refresh", jti: newJti }), - }; + return { sessionIdSigned: await startSession(user.id, ip, userAgent) }; } catch (e) { - if (e instanceof IntegrityError && e.message === "Refresh token not found") { - error(500, "Invalid refresh token"); + if (e instanceof IntegrityError && e.message === "Session already exists") { + error(403, "Already logged in"); } throw e; } }; -const expiresAt = () => new Date(Date.now() + env.challenge.tokenUpgradeExp); - -const createChallenge = async ( - ip: string, - tokenId: string, - clientId: number, - encPubKey: string, -) => { - const { answer, challenge } = await generateChallenge(32, encPubKey); - await registerTokenUpgradeChallenge( - tokenId, - clientId, - answer.toString("base64"), - ip, - expiresAt(), - ); - return challenge.toString("base64"); +export const logout = async (sessionId: string) => { + await deleteSession(sessionId); }; -export const createTokenUpgradeChallenge = async ( - refreshToken: string, +export const createSessionUpgradeChallenge = async ( + sessionId: string, + userId: number, ip: string, encPubKey: string, sigPubKey: string, ) => { - const { jti, userId, clientId } = await verifyRefreshToken(refreshToken); - if (clientId) { - error(403, "Forbidden"); - } - const client = await getClientByPubKeys(encPubKey, sigPubKey); const userClient = client ? await getUserClient(userId, client.id) : undefined; if (!client) { @@ -134,29 +52,29 @@ export const createTokenUpgradeChallenge = async ( error(403, "Unregistered client"); } - return { challenge: await createChallenge(ip, jti, client.id, encPubKey) }; + const { answer, challenge } = await generateChallenge(32, encPubKey); + await registerSessionUpgradeChallenge( + sessionId, + client.id, + answer.toString("base64"), + ip, + new Date(Date.now() + env.challenge.sessionUpgradeExp), + ); + + return { challenge: challenge.toString("base64") }; }; -export const upgradeToken = async ( - refreshToken: string, +export const verifySessionUpgradeChallenge = async ( + sessionId: string, ip: string, answer: string, answerSig: string, ) => { - const { jti: oldJti, userId, clientId } = await verifyRefreshToken(refreshToken); - if (clientId) { - error(403, "Forbidden"); - } - - const challenge = await getTokenUpgradeChallenge(answer, ip); + const challenge = await consumeSessionUpgradeChallenge(sessionId, answer, ip); if (!challenge) { error(403, "Invalid challenge answer"); - } else if (challenge.refreshTokenId !== oldJti) { - error(403, "Forbidden"); } - await markTokenUpgradeChallengeAsUsed(challenge.id); - const client = await getClient(challenge.clientId); if (!client) { error(500, "Invalid challenge answer"); @@ -165,15 +83,10 @@ export const upgradeToken = async ( } try { - const newJti = uuidv4(); - await upgradeRefreshToken(oldJti, newJti, client.id); - return { - accessToken: issueAccessToken(userId, client.id), - refreshToken: issueToken({ type: "refresh", jti: newJti }), - }; + await upgradeSession(sessionId, client.id); } catch (e) { - if (e instanceof IntegrityError && e.message === "Refresh token not found") { - error(500, "Invalid refresh token"); + if (e instanceof IntegrityError && e.message === "Session not found") { + error(500, "Invalid challenge answer"); } throw e; } diff --git a/src/routes/api/auth/login/+server.ts b/src/routes/api/auth/login/+server.ts index 479f561..d748f6a 100644 --- a/src/routes/api/auth/login/+server.ts +++ b/src/routes/api/auth/login/+server.ts @@ -4,20 +4,16 @@ import { loginRequest } from "$lib/server/schemas"; import { login } from "$lib/server/services/auth"; import type { RequestHandler } from "./$types"; -export const POST: RequestHandler = async ({ request, cookies }) => { +export const POST: RequestHandler = async ({ locals, request, cookies }) => { const zodRes = loginRequest.safeParse(await request.json()); if (!zodRes.success) error(400, "Invalid request body"); const { email, password } = zodRes.data; - const { accessToken, refreshToken } = await login(email, password); - cookies.set("accessToken", accessToken, { + const { sessionIdSigned } = await login(email, password, locals.ip, locals.userAgent); + cookies.set("sessionId", sessionIdSigned, { path: "/", - maxAge: env.jwt.accessExp / 1000, - sameSite: "strict", - }); - cookies.set("refreshToken", refreshToken, { - path: "/api/auth", - maxAge: env.jwt.refreshExp / 1000, + maxAge: env.session.exp / 1000, + secure: true, sameSite: "strict", }); diff --git a/src/routes/api/auth/logout/+server.ts b/src/routes/api/auth/logout/+server.ts index f9f0ea6..b5c1f11 100644 --- a/src/routes/api/auth/logout/+server.ts +++ b/src/routes/api/auth/logout/+server.ts @@ -1,14 +1,13 @@ -import { error, text } from "@sveltejs/kit"; +import { text } from "@sveltejs/kit"; +import { authorize } from "$lib/server/modules/auth"; import { logout } from "$lib/server/services/auth"; import type { RequestHandler } from "./$types"; -export const POST: RequestHandler = async ({ cookies }) => { - const token = cookies.get("refreshToken"); - if (!token) error(401, "Refresh token not found"); +export const POST: RequestHandler = async ({ locals, cookies }) => { + const { sessionId } = await authorize(locals, "any"); - await logout(token); - cookies.delete("accessToken", { path: "/" }); - cookies.delete("refreshToken", { path: "/api/auth" }); + await logout(sessionId); + cookies.delete("sessionId", { path: "/" }); return text("Logged out", { headers: { "Content-Type": "text/plain" } }); }; diff --git a/src/routes/api/auth/refreshToken/+server.ts b/src/routes/api/auth/refreshToken/+server.ts deleted file mode 100644 index 374fd8c..0000000 --- a/src/routes/api/auth/refreshToken/+server.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { error, text } from "@sveltejs/kit"; -import env from "$lib/server/loadenv"; -import { refreshToken as doRefreshToken } from "$lib/server/services/auth"; -import type { RequestHandler } from "./$types"; - -export const POST: RequestHandler = async ({ cookies }) => { - const token = cookies.get("refreshToken"); - if (!token) error(401, "Refresh token not found"); - - const { accessToken, refreshToken } = await doRefreshToken(token); - cookies.set("accessToken", accessToken, { - path: "/", - maxAge: env.jwt.accessExp / 1000, - sameSite: "strict", - }); - cookies.set("refreshToken", refreshToken, { - path: "/api/auth", - maxAge: env.jwt.refreshExp / 1000, - sameSite: "strict", - }); - - return text("Token refreshed", { headers: { "Content-Type": "text/plain" } }); -}; diff --git a/src/routes/api/auth/upgradeSession/+server.ts b/src/routes/api/auth/upgradeSession/+server.ts new file mode 100644 index 0000000..760f4c0 --- /dev/null +++ b/src/routes/api/auth/upgradeSession/+server.ts @@ -0,0 +1,26 @@ +import { error, json } from "@sveltejs/kit"; +import { authorize } from "$lib/server/modules/auth"; +import { + sessionUpgradeRequest, + sessionUpgradeResponse, + type SessionUpgradeResponse, +} from "$lib/server/schemas"; +import { createSessionUpgradeChallenge } from "$lib/server/services/auth"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ locals, request }) => { + const { sessionId, userId } = await authorize(locals, "notClient"); + + const zodRes = sessionUpgradeRequest.safeParse(await request.json()); + if (!zodRes.success) error(400, "Invalid request body"); + const { encPubKey, sigPubKey } = zodRes.data; + + const { challenge } = await createSessionUpgradeChallenge( + sessionId, + userId, + locals.ip, + encPubKey, + sigPubKey, + ); + return json(sessionUpgradeResponse.parse({ challenge } satisfies SessionUpgradeResponse)); +}; diff --git a/src/routes/api/auth/upgradeSession/verify/+server.ts b/src/routes/api/auth/upgradeSession/verify/+server.ts new file mode 100644 index 0000000..82cb315 --- /dev/null +++ b/src/routes/api/auth/upgradeSession/verify/+server.ts @@ -0,0 +1,16 @@ +import { error, text } from "@sveltejs/kit"; +import { authorize } from "$lib/server/modules/auth"; +import { sessionUpgradeVerifyRequest } from "$lib/server/schemas"; +import { verifySessionUpgradeChallenge } from "$lib/server/services/auth"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ locals, request }) => { + const { sessionId } = await authorize(locals, "notClient"); + + const zodRes = sessionUpgradeVerifyRequest.safeParse(await request.json()); + if (!zodRes.success) error(400, "Invalid request body"); + const { answer, answerSig } = zodRes.data; + + await verifySessionUpgradeChallenge(sessionId, locals.ip, answer, answerSig); + return text("Session upgraded", { headers: { "Content-Type": "text/plain" } }); +}; diff --git a/src/routes/api/auth/upgradeToken/+server.ts b/src/routes/api/auth/upgradeToken/+server.ts deleted file mode 100644 index cb09582..0000000 --- a/src/routes/api/auth/upgradeToken/+server.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { error, json } from "@sveltejs/kit"; -import { - tokenUpgradeRequest, - tokenUpgradeResponse, - type TokenUpgradeResponse, -} from "$lib/server/schemas"; -import { createTokenUpgradeChallenge } from "$lib/server/services/auth"; -import type { RequestHandler } from "./$types"; - -export const POST: RequestHandler = async ({ request, cookies, getClientAddress }) => { - const token = cookies.get("refreshToken"); - if (!token) error(401, "Refresh token not found"); - - const zodRes = tokenUpgradeRequest.safeParse(await request.json()); - if (!zodRes.success) error(400, "Invalid request body"); - const { encPubKey, sigPubKey } = zodRes.data; - - const { challenge } = await createTokenUpgradeChallenge( - token, - getClientAddress(), - encPubKey, - sigPubKey, - ); - return json(tokenUpgradeResponse.parse({ challenge } satisfies TokenUpgradeResponse)); -}; diff --git a/src/routes/api/auth/upgradeToken/verify/+server.ts b/src/routes/api/auth/upgradeToken/verify/+server.ts deleted file mode 100644 index eb78286..0000000 --- a/src/routes/api/auth/upgradeToken/verify/+server.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { error, text } from "@sveltejs/kit"; -import env from "$lib/server/loadenv"; -import { tokenUpgradeVerifyRequest } from "$lib/server/schemas"; -import { upgradeToken } from "$lib/server/services/auth"; -import type { RequestHandler } from "./$types"; - -export const POST: RequestHandler = async ({ request, cookies, getClientAddress }) => { - const token = cookies.get("refreshToken"); - if (!token) error(401, "Refresh token not found"); - - const zodRes = tokenUpgradeVerifyRequest.safeParse(await request.json()); - if (!zodRes.success) error(400, "Invalid request body"); - const { answer, answerSig } = zodRes.data; - - const { accessToken, refreshToken } = await upgradeToken( - token, - getClientAddress(), - answer, - answerSig, - ); - cookies.set("accessToken", accessToken, { - path: "/", - maxAge: env.jwt.accessExp / 1000, - sameSite: "strict", - }); - cookies.set("refreshToken", refreshToken, { - path: "/api/auth", - maxAge: env.jwt.refreshExp / 1000, - sameSite: "strict", - }); - - return text("Token upgraded", { headers: { "Content-Type": "text/plain" } }); -}; diff --git a/src/routes/api/client/list/+server.ts b/src/routes/api/client/list/+server.ts index 5354ece..78193ee 100644 --- a/src/routes/api/client/list/+server.ts +++ b/src/routes/api/client/list/+server.ts @@ -1,15 +1,11 @@ -import { error, json } from "@sveltejs/kit"; -import { authenticate } from "$lib/server/modules/auth"; +import { json } from "@sveltejs/kit"; +import { authorize } from "$lib/server/modules/auth"; import { clientListResponse, type ClientListResponse } from "$lib/server/schemas"; import { getUserClientList } from "$lib/server/services/client"; import type { RequestHandler } from "./$types"; -export const GET: RequestHandler = async ({ cookies }) => { - const { userId, clientId } = authenticate(cookies); - if (!clientId) { - error(403, "Forbidden"); - } - +export const GET: RequestHandler = async ({ locals }) => { + const { userId } = await authorize(locals, "anyClient"); const { userClients } = await getUserClientList(userId); return json(clientListResponse.parse({ clients: userClients } satisfies ClientListResponse)); }; diff --git a/src/routes/api/client/register/+server.ts b/src/routes/api/client/register/+server.ts index 0a6e5a0..d6aa4ce 100644 --- a/src/routes/api/client/register/+server.ts +++ b/src/routes/api/client/register/+server.ts @@ -1,5 +1,5 @@ import { error, json } from "@sveltejs/kit"; -import { authenticate } from "$lib/server/modules/auth"; +import { authorize } from "$lib/server/modules/auth"; import { clientRegisterRequest, clientRegisterResponse, @@ -8,16 +8,13 @@ import { import { registerUserClient } from "$lib/server/services/client"; import type { RequestHandler } from "./$types"; -export const POST: RequestHandler = async ({ request, cookies, getClientAddress }) => { - const { userId, clientId } = authenticate(cookies); - if (clientId) { - error(403, "Forbidden"); - } +export const POST: RequestHandler = async ({ locals, request }) => { + const { userId } = await authorize(locals, "notClient"); const zodRes = clientRegisterRequest.safeParse(await request.json()); if (!zodRes.success) error(400, "Invalid request body"); const { encPubKey, sigPubKey } = zodRes.data; - const { challenge } = await registerUserClient(userId, getClientAddress(), encPubKey, sigPubKey); + const { challenge } = await registerUserClient(userId, locals.ip, encPubKey, sigPubKey); return json(clientRegisterResponse.parse({ challenge } satisfies ClientRegisterResponse)); }; diff --git a/src/routes/api/client/register/verify/+server.ts b/src/routes/api/client/register/verify/+server.ts index e48b454..32d5214 100644 --- a/src/routes/api/client/register/verify/+server.ts +++ b/src/routes/api/client/register/verify/+server.ts @@ -1,19 +1,16 @@ import { error, text } from "@sveltejs/kit"; -import { authenticate } from "$lib/server/modules/auth"; +import { authorize } from "$lib/server/modules/auth"; import { clientRegisterVerifyRequest } from "$lib/server/schemas"; import { verifyUserClient } from "$lib/server/services/client"; import type { RequestHandler } from "./$types"; -export const POST: RequestHandler = async ({ request, cookies, getClientAddress }) => { - const { userId, clientId } = authenticate(cookies); - if (clientId) { - error(403, "Forbidden"); - } +export const POST: RequestHandler = async ({ locals, request }) => { + const { userId } = await authorize(locals, "notClient"); const zodRes = clientRegisterVerifyRequest.safeParse(await request.json()); if (!zodRes.success) error(400, "Invalid request body"); const { answer, answerSig } = zodRes.data; - await verifyUserClient(userId, getClientAddress(), answer, answerSig); + await verifyUserClient(userId, locals.ip, answer, answerSig); return text("Client verified", { headers: { "Content-Type": "text/plain" } }); }; diff --git a/src/routes/api/client/status/+server.ts b/src/routes/api/client/status/+server.ts index a1e9cd8..a7ecc82 100644 --- a/src/routes/api/client/status/+server.ts +++ b/src/routes/api/client/status/+server.ts @@ -1,15 +1,11 @@ -import { error, json } from "@sveltejs/kit"; -import { authenticate } from "$lib/server/modules/auth"; +import { json } from "@sveltejs/kit"; +import { authorize } from "$lib/server/modules/auth"; import { clientStatusResponse, type ClientStatusResponse } from "$lib/server/schemas"; import { getUserClientStatus } from "$lib/server/services/client"; import type { RequestHandler } from "./$types"; -export const GET: RequestHandler = async ({ cookies }) => { - const { userId, clientId } = authenticate(cookies); - if (!clientId) { - error(403, "Forbidden"); - } - +export const GET: RequestHandler = async ({ locals }) => { + const { userId, clientId } = await authorize(locals, "anyClient"); const { state, isInitialMekNeeded } = await getUserClientStatus(userId, clientId); return json( clientStatusResponse.parse({ diff --git a/src/routes/api/directory/[id]/+server.ts b/src/routes/api/directory/[id]/+server.ts index 7cd1d09..1b50018 100644 --- a/src/routes/api/directory/[id]/+server.ts +++ b/src/routes/api/directory/[id]/+server.ts @@ -5,8 +5,8 @@ import { directoryInfoResponse, type DirectoryInfoResponse } from "$lib/server/s import { getDirectoryInformation } from "$lib/server/services/directory"; import type { RequestHandler } from "./$types"; -export const GET: RequestHandler = async ({ cookies, params }) => { - const { userId } = await authorize(cookies, "activeClient"); +export const GET: RequestHandler = async ({ locals, params }) => { + const { userId } = await authorize(locals, "activeClient"); const zodRes = z .object({ diff --git a/src/routes/api/directory/[id]/delete/+server.ts b/src/routes/api/directory/[id]/delete/+server.ts index c7777df..4873912 100644 --- a/src/routes/api/directory/[id]/delete/+server.ts +++ b/src/routes/api/directory/[id]/delete/+server.ts @@ -4,8 +4,8 @@ import { authorize } from "$lib/server/modules/auth"; import { deleteDirectory } from "$lib/server/services/directory"; import type { RequestHandler } from "./$types"; -export const POST: RequestHandler = async ({ cookies, params }) => { - const { userId } = await authorize(cookies, "activeClient"); +export const POST: RequestHandler = async ({ locals, params }) => { + const { userId } = await authorize(locals, "activeClient"); const zodRes = z .object({ diff --git a/src/routes/api/directory/[id]/rename/+server.ts b/src/routes/api/directory/[id]/rename/+server.ts index c951a9c..0d95e13 100644 --- a/src/routes/api/directory/[id]/rename/+server.ts +++ b/src/routes/api/directory/[id]/rename/+server.ts @@ -5,8 +5,8 @@ import { directoryRenameRequest } from "$lib/server/schemas"; import { renameDirectory } from "$lib/server/services/directory"; import type { RequestHandler } from "./$types"; -export const POST: RequestHandler = async ({ request, cookies, params }) => { - const { userId } = await authorize(cookies, "activeClient"); +export const POST: RequestHandler = async ({ locals, params, request }) => { + const { userId } = await authorize(locals, "activeClient"); const paramsZodRes = z .object({ diff --git a/src/routes/api/directory/create/+server.ts b/src/routes/api/directory/create/+server.ts index b31d15f..2af0c3c 100644 --- a/src/routes/api/directory/create/+server.ts +++ b/src/routes/api/directory/create/+server.ts @@ -4,8 +4,8 @@ import { directoryCreateRequest } from "$lib/server/schemas"; import { createDirectory } from "$lib/server/services/directory"; import type { RequestHandler } from "./$types"; -export const POST: RequestHandler = async ({ request, cookies }) => { - const { userId } = await authorize(cookies, "activeClient"); +export const POST: RequestHandler = async ({ locals, request }) => { + const { userId } = await authorize(locals, "activeClient"); const zodRes = directoryCreateRequest.safeParse(await request.json()); if (!zodRes.success) error(400, "Invalid request body"); diff --git a/src/routes/api/file/[id]/+server.ts b/src/routes/api/file/[id]/+server.ts index ceb8a0f..6007589 100644 --- a/src/routes/api/file/[id]/+server.ts +++ b/src/routes/api/file/[id]/+server.ts @@ -5,8 +5,8 @@ import { fileInfoResponse, type FileInfoResponse } from "$lib/server/schemas"; import { getFileInformation } from "$lib/server/services/file"; import type { RequestHandler } from "./$types"; -export const GET: RequestHandler = async ({ cookies, params }) => { - const { userId } = await authorize(cookies, "activeClient"); +export const GET: RequestHandler = async ({ locals, params }) => { + const { userId } = await authorize(locals, "activeClient"); const zodRes = z .object({ diff --git a/src/routes/api/file/[id]/delete/+server.ts b/src/routes/api/file/[id]/delete/+server.ts index 4cbf733..7baac25 100644 --- a/src/routes/api/file/[id]/delete/+server.ts +++ b/src/routes/api/file/[id]/delete/+server.ts @@ -4,8 +4,8 @@ import { authorize } from "$lib/server/modules/auth"; import { deleteFile } from "$lib/server/services/file"; import type { RequestHandler } from "./$types"; -export const POST: RequestHandler = async ({ cookies, params }) => { - const { userId } = await authorize(cookies, "activeClient"); +export const POST: RequestHandler = async ({ locals, params }) => { + const { userId } = await authorize(locals, "activeClient"); const zodRes = z .object({ diff --git a/src/routes/api/file/[id]/download/+server.ts b/src/routes/api/file/[id]/download/+server.ts index 58f915d..5040c73 100644 --- a/src/routes/api/file/[id]/download/+server.ts +++ b/src/routes/api/file/[id]/download/+server.ts @@ -4,8 +4,8 @@ import { authorize } from "$lib/server/modules/auth"; import { getFileStream } from "$lib/server/services/file"; import type { RequestHandler } from "./$types"; -export const GET: RequestHandler = async ({ cookies, params }) => { - const { userId } = await authorize(cookies, "activeClient"); +export const GET: RequestHandler = async ({ locals, params }) => { + const { userId } = await authorize(locals, "activeClient"); const zodRes = z .object({ diff --git a/src/routes/api/file/[id]/rename/+server.ts b/src/routes/api/file/[id]/rename/+server.ts index 46fd4b3..c6748a0 100644 --- a/src/routes/api/file/[id]/rename/+server.ts +++ b/src/routes/api/file/[id]/rename/+server.ts @@ -5,8 +5,8 @@ import { fileRenameRequest } from "$lib/server/schemas"; import { renameFile } from "$lib/server/services/file"; import type { RequestHandler } from "./$types"; -export const POST: RequestHandler = async ({ request, cookies, params }) => { - const { userId } = await authorize(cookies, "activeClient"); +export const POST: RequestHandler = async ({ locals, params, request }) => { + const { userId } = await authorize(locals, "activeClient"); const paramsZodRes = z .object({ diff --git a/src/routes/api/file/upload/+server.ts b/src/routes/api/file/upload/+server.ts index 2de4c9a..ac1ac51 100644 --- a/src/routes/api/file/upload/+server.ts +++ b/src/routes/api/file/upload/+server.ts @@ -4,8 +4,8 @@ import { fileUploadRequest } from "$lib/server/schemas"; import { uploadFile } from "$lib/server/services/file"; import type { RequestHandler } from "./$types"; -export const POST: RequestHandler = async ({ request, cookies }) => { - const { userId } = await authorize(cookies, "activeClient"); +export const POST: RequestHandler = async ({ locals, request }) => { + const { userId } = await authorize(locals, "activeClient"); const form = await request.formData(); const metadata = form.get("metadata"); diff --git a/src/routes/api/mek/list/+server.ts b/src/routes/api/mek/list/+server.ts index ccb9fa3..b3df9fe 100644 --- a/src/routes/api/mek/list/+server.ts +++ b/src/routes/api/mek/list/+server.ts @@ -4,8 +4,8 @@ import { masterKeyListResponse, type MasterKeyListResponse } from "$lib/server/s import { getClientMekList } from "$lib/server/services/mek"; import type { RequestHandler } from "./$types"; -export const GET: RequestHandler = async ({ cookies }) => { - const { userId, clientId } = await authorize(cookies, "activeClient"); +export const GET: RequestHandler = async ({ locals }) => { + const { userId, clientId } = await authorize(locals, "activeClient"); const { encMeks } = await getClientMekList(userId, clientId); return json( masterKeyListResponse.parse({ diff --git a/src/routes/api/mek/register/initial/+server.ts b/src/routes/api/mek/register/initial/+server.ts index ba959fb..bb761e2 100644 --- a/src/routes/api/mek/register/initial/+server.ts +++ b/src/routes/api/mek/register/initial/+server.ts @@ -1,14 +1,11 @@ import { error, text } from "@sveltejs/kit"; -import { authenticate } from "$lib/server/modules/auth"; +import { authorize } from "$lib/server/modules/auth"; import { initialMasterKeyRegisterRequest } from "$lib/server/schemas"; import { registerInitialActiveMek } from "$lib/server/services/mek"; import type { RequestHandler } from "./$types"; -export const POST: RequestHandler = async ({ request, cookies }) => { - const { userId, clientId } = authenticate(cookies); - if (!clientId) { - error(403, "Forbidden"); - } +export const POST: RequestHandler = async ({ locals, request }) => { + const { userId, clientId } = await authorize(locals, "pendingClient"); const zodRes = initialMasterKeyRegisterRequest.safeParse(await request.json()); if (!zodRes.success) error(400, "Invalid request body"); From be8587694eb7616e6386281b261ea1215032896b Mon Sep 17 00:00:00 2001 From: static Date: Sun, 12 Jan 2025 07:59:49 +0900 Subject: [PATCH 04/17] =?UTF-8?q?=EC=95=94=ED=98=B8=20=ED=82=A4=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=EC=B1=8C=EB=A6=B0=EC=A7=80=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=A9=EC=8B=9D=EC=9D=84=20=EC=84=B8=EC=85=98=20?= =?UTF-8?q?=EC=97=85=EA=B7=B8=EB=A0=88=EC=9D=B4=EB=93=9C=20=EC=B1=8C?= =?UTF-8?q?=EB=A6=B0=EC=A7=80=20=EC=B2=98=EB=A6=AC=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=EA=B3=BC=20=EB=8F=99=EC=9D=BC=ED=95=98=EA=B2=8C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/db/client.ts | 13 +++------ src/lib/server/db/schema/client.ts | 43 ++++++++++++++++++++---------- src/lib/server/services/client.ts | 11 +++----- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/lib/server/db/client.ts b/src/lib/server/db/client.ts index 7a7b58b..37a1054 100644 --- a/src/lib/server/db/client.ts +++ b/src/lib/server/db/client.ts @@ -119,26 +119,21 @@ export const registerUserClientChallenge = async ( }); }; -export const getUserClientChallenge = async (answer: string, ip: string) => { +export const consumeUserClientChallenge = async (userId: number, answer: string, ip: string) => { const challenges = await db - .select() - .from(userClientChallenge) + .delete(userClientChallenge) .where( and( + eq(userClientChallenge.userId, userId), eq(userClientChallenge.answer, answer), eq(userClientChallenge.allowedIp, ip), gt(userClientChallenge.expiresAt, new Date()), - eq(userClientChallenge.isUsed, false), ), ) - .limit(1); + .returning({ clientId: userClientChallenge.clientId }); return challenges[0] ?? null; }; -export const markUserClientChallengeAsUsed = async (id: number) => { - await db.update(userClientChallenge).set({ isUsed: true }).where(eq(userClientChallenge.id, id)); -}; - export const cleanupExpiredUserClientChallenges = async () => { await db.delete(userClientChallenge).where(lte(userClientChallenge.expiresAt, new Date())); }; diff --git a/src/lib/server/db/schema/client.ts b/src/lib/server/db/schema/client.ts index eacd9c9..1e9eb85 100644 --- a/src/lib/server/db/schema/client.ts +++ b/src/lib/server/db/schema/client.ts @@ -1,4 +1,11 @@ -import { sqliteTable, text, integer, primaryKey, unique } from "drizzle-orm/sqlite-core"; +import { + sqliteTable, + text, + integer, + primaryKey, + foreignKey, + unique, +} from "drizzle-orm/sqlite-core"; import { user } from "./user"; export const client = sqliteTable( @@ -31,16 +38,24 @@ export const userClient = sqliteTable( }), ); -export const userClientChallenge = sqliteTable("user_client_challenge", { - id: integer("id").primaryKey(), - userId: integer("user_id") - .notNull() - .references(() => user.id), - clientId: integer("client_id") - .notNull() - .references(() => client.id), - answer: text("answer").notNull().unique(), // Base64 - allowedIp: text("allowed_ip").notNull(), - expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(), - isUsed: integer("is_used", { mode: "boolean" }).notNull().default(false), -}); +export const userClientChallenge = sqliteTable( + "user_client_challenge", + { + id: integer("id").primaryKey(), + userId: integer("user_id") + .notNull() + .references(() => user.id), + clientId: integer("client_id") + .notNull() + .references(() => client.id), + answer: text("answer").notNull().unique(), // Base64 + allowedIp: text("allowed_ip").notNull(), + expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(), + }, + (t) => ({ + ref: foreignKey({ + columns: [t.userId, t.clientId], + foreignColumns: [userClient.userId, userClient.clientId], + }), + }), +); diff --git a/src/lib/server/services/client.ts b/src/lib/server/services/client.ts index 73973bb..cc706e1 100644 --- a/src/lib/server/services/client.ts +++ b/src/lib/server/services/client.ts @@ -8,8 +8,7 @@ import { getUserClient, setUserClientStateToPending, registerUserClientChallenge, - getUserClientChallenge, - markUserClientChallengeAsUsed, + consumeUserClientChallenge, } from "$lib/server/db/client"; import { IntegrityError } from "$lib/server/db/error"; import { verifyPubKey, verifySignature, generateChallenge } from "$lib/server/modules/crypto"; @@ -81,15 +80,11 @@ export const verifyUserClient = async ( answer: string, answerSig: string, ) => { - const challenge = await getUserClientChallenge(answer, ip); + const challenge = await consumeUserClientChallenge(userId, answer, ip); if (!challenge) { error(403, "Invalid challenge answer"); - } else if (challenge.userId !== userId) { - error(403, "Forbidden"); } - await markUserClientChallengeAsUsed(challenge.id); - const client = await getClient(challenge.clientId); if (!client) { error(500, "Invalid challenge answer"); @@ -97,7 +92,7 @@ export const verifyUserClient = async ( error(403, "Invalid challenge answer signature"); } - await setUserClientStateToPending(userId, challenge.clientId); + await setUserClientStateToPending(userId, client.id); }; export const getUserClientStatus = async (userId: number, clientId: number) => { From 85ebb529ba675fd45e47a10437caae641bc6d672 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 12 Jan 2025 08:31:11 +0900 Subject: [PATCH 05/17] =?UTF-8?q?=ED=94=84=EB=A1=A0=ED=8A=B8=EC=97=94?= =?UTF-8?q?=EB=93=9C=EC=97=90=EC=84=9C=20=EC=84=B8=EC=85=98=20ID=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EC=9D=B8=EC=A6=9D=20=EB=8C=80=EC=9D=91=20?= =?UTF-8?q?=EB=B0=8F=20DB=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=9E=AC?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +- ...rvel.sql => 0000_spooky_lady_bullseye.sql} | 29 ++-- drizzle/meta/0000_snapshot.json | 129 +++++++++++------- drizzle/meta/_journal.json | 4 +- src/lib/hooks/callApi.ts | 40 ++---- src/lib/server/middlewares/authenticate.ts | 8 +- src/lib/server/modules/mek.ts | 2 +- src/lib/server/services/client.ts | 2 +- src/lib/services/auth.ts | 31 ++--- .../(fullscreen)/auth/login/+page.server.ts | 5 +- .../(fullscreen)/auth/login/+page.svelte | 14 +- src/routes/(fullscreen)/auth/login/service.ts | 15 +- .../(fullscreen)/key/export/+page.svelte | 6 +- src/routes/(fullscreen)/key/export/service.ts | 2 +- 14 files changed, 141 insertions(+), 155 deletions(-) rename drizzle/{0000_handy_captain_marvel.sql => 0000_spooky_lady_bullseye.sql} (83%) diff --git a/README.md b/README.md index f2d2028..45f5c5a 100644 --- a/README.md +++ b/README.md @@ -30,12 +30,11 @@ docker compose up --build -d 필수 환경 변수가 아닌 경우, 설정해야 하는 특별한 이유가 없다면 기본값을 사용하는 것이 좋아요. |이름|필수|기본값|설명| -|-:|:-:|:-:|:-| -|`JWT_SECRET`|Y||JWT의 서명을 위해 사용돼요. 안전한 값으로 설정해 주세요.| -|`JWT_ACCESS_TOKEN_EXPIRES`||`5m`|Access Token의 유효 시간이에요.| -|`JWT_REFRESH_TOKEN_EXPIRES`||`14d`|Refresh Token의 유효 시간이에요.| +|:-|:-:|:-:|:-| +|`SESSION_SECRET`|Y||Session ID의 서명을 위해 사용돼요. 안전한 값으로 설정해 주세요.| +|`SESSION_EXPIRES`||`14d`|Session의 유효 시간이에요. Session은 마지막으로 사용된 후 설정된 유효 시간이 지나면 자동으로 삭제돼요.| |`USER_CLIENT_CHALLENGE_EXPIRES`||`5m`|암호 키를 서버에 처음 등록할 때 사용되는 챌린지의 유효 시간이에요.| -|`TOKEN_UPGRADE_CHALLENGE_EXPIRES`||`5m`|암호 키와 함께 로그인할 때 사용되는 챌린지의 유효 시간이에요.| +|`SESSION_UPGRADE_CHALLENGE_EXPIRES`||`5m`|암호 키와 함께 로그인할 때 사용되는 챌린지의 유효 시간이에요.| |`TRUST_PROXY`|||신뢰할 수 있는 리버스 프록시의 수예요. 설정할 경우 1 이상의 정수로 설정해 주세요. 프록시에서 `X-Forwarded-For` HTTP 헤더를 올바르게 설정하도록 구성해 주세요.| |`NODE_ENV`||`production`|ArkVault의 사용 용도예요. `production`인 경우, 컨테이너가 실행될 때마다 DB 마이그레이션이 자동으로 실행돼요.| |`PORT`||`80`|ArkVault 서버의 포트예요.| diff --git a/drizzle/0000_handy_captain_marvel.sql b/drizzle/0000_spooky_lady_bullseye.sql similarity index 83% rename from drizzle/0000_handy_captain_marvel.sql rename to drizzle/0000_spooky_lady_bullseye.sql index 05d5e02..d9b520c 100644 --- a/drizzle/0000_handy_captain_marvel.sql +++ b/drizzle/0000_spooky_lady_bullseye.sql @@ -17,12 +17,12 @@ CREATE TABLE `user_client_challenge` ( `id` integer PRIMARY KEY NOT NULL, `user_id` integer NOT NULL, `client_id` integer NOT NULL, - `challenge` text NOT NULL, + `answer` text NOT NULL, `allowed_ip` text NOT NULL, `expires_at` integer NOT NULL, - `is_used` integer DEFAULT false NOT NULL, FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action + FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`user_id`,`client_id`) REFERENCES `user_client`(`user_id`,`client_id`) ON UPDATE no action ON DELETE no action ); --> statement-breakpoint CREATE TABLE `directory` ( @@ -80,24 +80,26 @@ CREATE TABLE `master_encryption_key` ( FOREIGN KEY (`created_by`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action ); --> statement-breakpoint -CREATE TABLE `refresh_token` ( +CREATE TABLE `session` ( `id` text PRIMARY KEY NOT NULL, `user_id` integer NOT NULL, `client_id` integer, - `expires_at` integer NOT NULL, + `created_at` integer NOT NULL, + `last_used_at` integer NOT NULL, + `last_used_by_ip` text, + `last_used_by_user_agent` text, FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action ); --> statement-breakpoint -CREATE TABLE `token_upgrade_challenge` ( +CREATE TABLE `session_upgrade_challenge` ( `id` integer PRIMARY KEY NOT NULL, - `refresh_token_id` text NOT NULL, + `session_id` text NOT NULL, `client_id` integer NOT NULL, - `challenge` text NOT NULL, + `answer` text NOT NULL, `allowed_ip` text NOT NULL, `expires_at` integer NOT NULL, - `is_used` integer DEFAULT false NOT NULL, - FOREIGN KEY (`refresh_token_id`) REFERENCES `refresh_token`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`session_id`) REFERENCES `session`(`id`) ON UPDATE no action ON DELETE no action, FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action ); --> statement-breakpoint @@ -110,10 +112,11 @@ CREATE TABLE `user` ( CREATE UNIQUE INDEX `client_encryption_public_key_unique` ON `client` (`encryption_public_key`);--> statement-breakpoint CREATE UNIQUE INDEX `client_signature_public_key_unique` ON `client` (`signature_public_key`);--> statement-breakpoint CREATE UNIQUE INDEX `client_encryption_public_key_signature_public_key_unique` ON `client` (`encryption_public_key`,`signature_public_key`);--> statement-breakpoint -CREATE UNIQUE INDEX `user_client_challenge_challenge_unique` ON `user_client_challenge` (`challenge`);--> statement-breakpoint +CREATE UNIQUE INDEX `user_client_challenge_answer_unique` ON `user_client_challenge` (`answer`);--> statement-breakpoint CREATE UNIQUE INDEX `directory_encrypted_data_encryption_key_unique` ON `directory` (`encrypted_data_encryption_key`);--> statement-breakpoint CREATE UNIQUE INDEX `file_path_unique` ON `file` (`path`);--> statement-breakpoint CREATE UNIQUE INDEX `file_encrypted_data_encryption_key_unique` ON `file` (`encrypted_data_encryption_key`);--> statement-breakpoint -CREATE UNIQUE INDEX `refresh_token_user_id_client_id_unique` ON `refresh_token` (`user_id`,`client_id`);--> statement-breakpoint -CREATE UNIQUE INDEX `token_upgrade_challenge_challenge_unique` ON `token_upgrade_challenge` (`challenge`);--> statement-breakpoint +CREATE UNIQUE INDEX `session_user_id_client_id_unique` ON `session` (`user_id`,`client_id`);--> statement-breakpoint +CREATE UNIQUE INDEX `session_upgrade_challenge_session_id_unique` ON `session_upgrade_challenge` (`session_id`);--> statement-breakpoint +CREATE UNIQUE INDEX `session_upgrade_challenge_answer_unique` ON `session_upgrade_challenge` (`answer`);--> statement-breakpoint CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`); \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json index d8c1013..57c4a6a 100644 --- a/drizzle/meta/0000_snapshot.json +++ b/drizzle/meta/0000_snapshot.json @@ -1,7 +1,7 @@ { "version": "6", "dialect": "sqlite", - "id": "929c6bca-d0c0-4899-afc6-a0a498226f28", + "id": "c518e1b4-38f8-4c8e-bdc9-64152ab456d8", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "client": { @@ -147,8 +147,8 @@ "notNull": true, "autoincrement": false }, - "challenge": { - "name": "challenge", + "answer": { + "name": "answer", "type": "text", "primaryKey": false, "notNull": true, @@ -167,21 +167,13 @@ "primaryKey": false, "notNull": true, "autoincrement": false - }, - "is_used": { - "name": "is_used", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false } }, "indexes": { - "user_client_challenge_challenge_unique": { - "name": "user_client_challenge_challenge_unique", + "user_client_challenge_answer_unique": { + "name": "user_client_challenge_answer_unique", "columns": [ - "challenge" + "answer" ], "isUnique": true } @@ -212,6 +204,21 @@ ], "onDelete": "no action", "onUpdate": "no action" + }, + "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk": { + "name": "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk", + "tableFrom": "user_client_challenge", + "tableTo": "user_client", + "columnsFrom": [ + "user_id", + "client_id" + ], + "columnsTo": [ + "user_id", + "client_id" + ], + "onDelete": "no action", + "onUpdate": "no action" } }, "compositePrimaryKeys": {}, @@ -656,8 +663,8 @@ }, "uniqueConstraints": {} }, - "refresh_token": { - "name": "refresh_token", + "session": { + "name": "session", "columns": { "id": { "name": "id", @@ -680,17 +687,38 @@ "notNull": false, "autoincrement": false }, - "expires_at": { - "name": "expires_at", + "created_at": { + "name": "created_at", "type": "integer", "primaryKey": false, "notNull": true, "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_by_ip": { + "name": "last_used_by_ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_by_user_agent": { + "name": "last_used_by_user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false } }, "indexes": { - "refresh_token_user_id_client_id_unique": { - "name": "refresh_token_user_id_client_id_unique", + "session_user_id_client_id_unique": { + "name": "session_user_id_client_id_unique", "columns": [ "user_id", "client_id" @@ -699,9 +727,9 @@ } }, "foreignKeys": { - "refresh_token_user_id_user_id_fk": { - "name": "refresh_token_user_id_user_id_fk", - "tableFrom": "refresh_token", + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", "tableTo": "user", "columnsFrom": [ "user_id" @@ -712,9 +740,9 @@ "onDelete": "no action", "onUpdate": "no action" }, - "refresh_token_client_id_client_id_fk": { - "name": "refresh_token_client_id_client_id_fk", - "tableFrom": "refresh_token", + "session_client_id_client_id_fk": { + "name": "session_client_id_client_id_fk", + "tableFrom": "session", "tableTo": "client", "columnsFrom": [ "client_id" @@ -729,8 +757,8 @@ "compositePrimaryKeys": {}, "uniqueConstraints": {} }, - "token_upgrade_challenge": { - "name": "token_upgrade_challenge", + "session_upgrade_challenge": { + "name": "session_upgrade_challenge", "columns": { "id": { "name": "id", @@ -739,8 +767,8 @@ "notNull": true, "autoincrement": false }, - "refresh_token_id": { - "name": "refresh_token_id", + "session_id": { + "name": "session_id", "type": "text", "primaryKey": false, "notNull": true, @@ -753,8 +781,8 @@ "notNull": true, "autoincrement": false }, - "challenge": { - "name": "challenge", + "answer": { + "name": "answer", "type": "text", "primaryKey": false, "notNull": true, @@ -773,32 +801,31 @@ "primaryKey": false, "notNull": true, "autoincrement": false - }, - "is_used": { - "name": "is_used", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": false } }, "indexes": { - "token_upgrade_challenge_challenge_unique": { - "name": "token_upgrade_challenge_challenge_unique", + "session_upgrade_challenge_session_id_unique": { + "name": "session_upgrade_challenge_session_id_unique", "columns": [ - "challenge" + "session_id" + ], + "isUnique": true + }, + "session_upgrade_challenge_answer_unique": { + "name": "session_upgrade_challenge_answer_unique", + "columns": [ + "answer" ], "isUnique": true } }, "foreignKeys": { - "token_upgrade_challenge_refresh_token_id_refresh_token_id_fk": { - "name": "token_upgrade_challenge_refresh_token_id_refresh_token_id_fk", - "tableFrom": "token_upgrade_challenge", - "tableTo": "refresh_token", + "session_upgrade_challenge_session_id_session_id_fk": { + "name": "session_upgrade_challenge_session_id_session_id_fk", + "tableFrom": "session_upgrade_challenge", + "tableTo": "session", "columnsFrom": [ - "refresh_token_id" + "session_id" ], "columnsTo": [ "id" @@ -806,9 +833,9 @@ "onDelete": "no action", "onUpdate": "no action" }, - "token_upgrade_challenge_client_id_client_id_fk": { - "name": "token_upgrade_challenge_client_id_client_id_fk", - "tableFrom": "token_upgrade_challenge", + "session_upgrade_challenge_client_id_client_id_fk": { + "name": "session_upgrade_challenge_client_id_client_id_fk", + "tableFrom": "session_upgrade_challenge", "tableTo": "client", "columnsFrom": [ "client_id" diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index b2615a0..62c9f38 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "6", - "when": 1736170919561, - "tag": "0000_handy_captain_marvel", + "when": 1736637983139, + "tag": "0000_spooky_lady_bullseye", "breakpoints": true } ] diff --git a/src/lib/hooks/callApi.ts b/src/lib/hooks/callApi.ts index c08b97d..1699ec2 100644 --- a/src/lib/hooks/callApi.ts +++ b/src/lib/hooks/callApi.ts @@ -1,35 +1,11 @@ -export const refreshToken = async (fetchInternal = fetch) => { - return await fetchInternal("/api/auth/refreshToken", { method: "POST" }); +export const callGetApi = async (input: RequestInfo, fetchInternal = fetch) => { + return await fetchInternal(input); }; -const callApi = async (input: RequestInfo, init?: RequestInit, fetchInternal = fetch) => { - let res = await fetchInternal(input, init); - if (res.status === 401) { - res = await refreshToken(); - if (!res.ok) { - return res; - } - res = await fetchInternal(input, init); - } - return res; -}; - -export const callGetApi = async (input: RequestInfo, fetchInternal?: typeof fetch) => { - return await callApi(input, undefined, fetchInternal); -}; - -export const callPostApi = async ( - input: RequestInfo, - payload?: T, - fetchInternal?: typeof fetch, -) => { - return await callApi( - input, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: payload ? JSON.stringify(payload) : undefined, - }, - fetchInternal, - ); +export const callPostApi = async (input: RequestInfo, payload?: T, fetchInternal = fetch) => { + return await fetchInternal(input, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: payload ? JSON.stringify(payload) : undefined, + }); }; diff --git a/src/lib/server/middlewares/authenticate.ts b/src/lib/server/middlewares/authenticate.ts index f484578..8880f1a 100644 --- a/src/lib/server/middlewares/authenticate.ts +++ b/src/lib/server/middlewares/authenticate.ts @@ -1,11 +1,9 @@ import { error, redirect, type Handle } from "@sveltejs/kit"; import { authenticate, AuthenticationError } from "$lib/server/modules/auth"; -const whitelist = ["/auth/login", "/api/auth/login"]; - export const authenticateMiddleware: Handle = async ({ event, resolve }) => { const { pathname, search } = event.url; - if (whitelist.some((path) => pathname.startsWith(path))) { + if (pathname === "/api/auth/login") { return await resolve(event); } @@ -19,7 +17,9 @@ export const authenticateMiddleware: Handle = async ({ event, resolve }) => { event.locals.session = await authenticate(sessionIdSigned, ip, userAgent); } catch (e) { if (e instanceof AuthenticationError) { - if (pathname.startsWith("/api")) { + if (pathname === "/auth/login") { + return await resolve(event); + } else if (pathname.startsWith("/api")) { error(e.status, e.message); } else { redirect(302, "/auth/login?redirect=" + encodeURIComponent(pathname + search)); diff --git a/src/lib/server/modules/mek.ts b/src/lib/server/modules/mek.ts index 0019ce0..d65ef0a 100644 --- a/src/lib/server/modules/mek.ts +++ b/src/lib/server/modules/mek.ts @@ -17,7 +17,7 @@ export const verifyClientEncMekSig = async ( ) => { const userClient = await getUserClientWithDetails(userId, clientId); if (!userClient) { - error(500, "Invalid access token"); + error(500, "Invalid session id"); } const data = JSON.stringify({ version, key: encMek }); diff --git a/src/lib/server/services/client.ts b/src/lib/server/services/client.ts index cc706e1..b5b0209 100644 --- a/src/lib/server/services/client.ts +++ b/src/lib/server/services/client.ts @@ -98,7 +98,7 @@ export const verifyUserClient = async ( export const getUserClientStatus = async (userId: number, clientId: number) => { const userClient = await getUserClient(userId, clientId); if (!userClient) { - error(500, "Invalid access token"); + error(500, "Invalid session id"); } return { diff --git a/src/lib/services/auth.ts b/src/lib/services/auth.ts index f784b03..03d445a 100644 --- a/src/lib/services/auth.ts +++ b/src/lib/services/auth.ts @@ -1,37 +1,30 @@ +import { callPostApi } from "$lib/hooks"; import { encodeToBase64, decryptChallenge, signMessage } from "$lib/modules/crypto"; import type { - TokenUpgradeRequest, - TokenUpgradeResponse, - TokenUpgradeVerifyRequest, + SessionUpgradeRequest, + SessionUpgradeResponse, + SessionUpgradeVerifyRequest, } from "$lib/server/schemas"; -export const requestTokenUpgrade = async ( +export const requestSessionUpgrade = async ( encryptKeyBase64: string, decryptKey: CryptoKey, verifyKeyBase64: string, signKey: CryptoKey, ) => { - let res = await fetch("/api/auth/upgradeToken", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - encPubKey: encryptKeyBase64, - sigPubKey: verifyKeyBase64, - } satisfies TokenUpgradeRequest), + let res = await callPostApi("/api/auth/upgradeSession", { + encPubKey: encryptKeyBase64, + sigPubKey: verifyKeyBase64, }); if (!res.ok) return false; - const { challenge }: TokenUpgradeResponse = await res.json(); + const { challenge }: SessionUpgradeResponse = await res.json(); const answer = await decryptChallenge(challenge, decryptKey); const answerSig = await signMessage(answer, signKey); - res = await fetch("/api/auth/upgradeToken/verify", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - answer: encodeToBase64(answer), - answerSig: encodeToBase64(answerSig), - } satisfies TokenUpgradeVerifyRequest), + res = await callPostApi("/api/auth/upgradeSession/verify", { + answer: encodeToBase64(answer), + answerSig: encodeToBase64(answerSig), }); return res.ok; }; diff --git a/src/routes/(fullscreen)/auth/login/+page.server.ts b/src/routes/(fullscreen)/auth/login/+page.server.ts index da7da9c..935dd9d 100644 --- a/src/routes/(fullscreen)/auth/login/+page.server.ts +++ b/src/routes/(fullscreen)/auth/login/+page.server.ts @@ -1,11 +1,10 @@ import { redirect } from "@sveltejs/kit"; import type { PageServerLoad } from "./$types"; -export const load: PageServerLoad = async ({ url, cookies }) => { +export const load: PageServerLoad = async ({ locals, url }) => { const redirectPath = url.searchParams.get("redirect") || "/home"; - const accessToken = cookies.get("accessToken"); - if (accessToken) { + if (locals.session) { redirect(302, redirectPath); } diff --git a/src/routes/(fullscreen)/auth/login/+page.svelte b/src/routes/(fullscreen)/auth/login/+page.svelte index 1ebb66b..4014368 100644 --- a/src/routes/(fullscreen)/auth/login/+page.svelte +++ b/src/routes/(fullscreen)/auth/login/+page.svelte @@ -1,12 +1,10 @@ diff --git a/src/routes/(fullscreen)/auth/login/service.ts b/src/routes/(fullscreen)/auth/login/service.ts index 4e6145d..2d267e1 100644 --- a/src/routes/(fullscreen)/auth/login/service.ts +++ b/src/routes/(fullscreen)/auth/login/service.ts @@ -1,21 +1,18 @@ +import { callPostApi } from "$lib/hooks"; import { exportRSAKeyToBase64 } from "$lib/modules/crypto"; import type { LoginRequest } from "$lib/server/schemas"; -import { requestTokenUpgrade as requestTokenUpgradeInternal } from "$lib/services/auth"; +import { requestSessionUpgrade as requestSessionUpgradeInternal } from "$lib/services/auth"; import { requestClientRegistration } from "$lib/services/key"; import type { ClientKeys } from "$lib/stores"; export { requestMasterKeyDownload } from "$lib/services/key"; export const requestLogin = async (email: string, password: string) => { - const res = await fetch("/api/auth/login", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ email, password } satisfies LoginRequest), - }); + const res = await callPostApi("/api/auth/login", { email, password }); return res.ok; }; -export const requestTokenUpgrade = async ({ +export const requestSessionUpgrade = async ({ encryptKey, decryptKey, signKey, @@ -23,12 +20,12 @@ export const requestTokenUpgrade = async ({ }: ClientKeys) => { const encryptKeyBase64 = await exportRSAKeyToBase64(encryptKey); const verifyKeyBase64 = await exportRSAKeyToBase64(verifyKey); - if (await requestTokenUpgradeInternal(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) { + if (await requestSessionUpgradeInternal(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) { return true; } if (await requestClientRegistration(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) { - return await requestTokenUpgradeInternal( + return await requestSessionUpgradeInternal( encryptKeyBase64, decryptKey, verifyKeyBase64, diff --git a/src/routes/(fullscreen)/key/export/+page.svelte b/src/routes/(fullscreen)/key/export/+page.svelte index a2bca6d..3ce209f 100644 --- a/src/routes/(fullscreen)/key/export/+page.svelte +++ b/src/routes/(fullscreen)/key/export/+page.svelte @@ -10,7 +10,7 @@ serializeClientKeys, requestClientRegistration, storeClientKeys, - requestTokenUpgrade, + requestSessionUpgrade, requestInitialMasterKeyRegistration, } from "./service"; @@ -59,14 +59,14 @@ await storeClientKeys($clientKeyStore); if ( - !(await requestTokenUpgrade( + !(await requestSessionUpgrade( data.encryptKeyBase64, $clientKeyStore.decryptKey, data.verifyKeyBase64, $clientKeyStore.signKey, )) ) - throw new Error("Failed to upgrade token"); + throw new Error("Failed to upgrade session"); if ( !(await requestInitialMasterKeyRegistration(data.masterKeyWrapped, $clientKeyStore.signKey)) diff --git a/src/routes/(fullscreen)/key/export/service.ts b/src/routes/(fullscreen)/key/export/service.ts index 45de8d9..b02dd09 100644 --- a/src/routes/(fullscreen)/key/export/service.ts +++ b/src/routes/(fullscreen)/key/export/service.ts @@ -4,7 +4,7 @@ import { signMasterKeyWrapped } from "$lib/modules/crypto"; import type { InitialMasterKeyRegisterRequest } from "$lib/server/schemas"; import type { ClientKeys } from "$lib/stores"; -export { requestTokenUpgrade } from "$lib/services/auth"; +export { requestSessionUpgrade } from "$lib/services/auth"; export { requestClientRegistration } from "$lib/services/key"; type SerializedKeyPairs = { From f8115f4f2ef53d01515e129fecb5f663d781d6eb Mon Sep 17 00:00:00 2001 From: static Date: Sun, 12 Jan 2025 19:02:21 +0900 Subject: [PATCH 06/17] =?UTF-8?q?=ED=8C=8C=EC=9D=BC/=EB=94=94=EB=A0=89?= =?UTF-8?q?=ED=84=B0=EB=A6=AC=20=EC=83=9D=EC=84=B1/=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=8B=9C=20=EB=A1=9C=EA=B7=B8=EB=A5=BC=20?= =?UTF-8?q?=EB=82=A8=EA=B8=B0=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/db/file.ts | 74 +++++++++++++++++------- src/lib/server/db/schema/file.ts | 24 +++++++- src/lib/server/schemas/directory.ts | 1 - src/lib/server/schemas/file.ts | 1 - src/lib/server/services/directory.ts | 1 - src/lib/server/services/file.ts | 1 - src/routes/api/directory/[id]/+server.ts | 1 - src/routes/api/file/[id]/+server.ts | 3 +- 8 files changed, 74 insertions(+), 32 deletions(-) diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index 270add3..edf0249 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -1,13 +1,13 @@ import { and, eq, isNull } from "drizzle-orm"; import db from "./drizzle"; import { IntegrityError } from "./error"; -import { directory, file, mek } from "./schema"; +import { directory, directoryLog, file, fileLog, mek } from "./schema"; type DirectoryId = "root" | number; export interface NewDirectoryParams { - userId: number; parentId: DirectoryId; + userId: number; mekVersion: number; encDek: string; dekVersion: Date; @@ -16,9 +16,9 @@ export interface NewDirectoryParams { } export interface NewFileParams { - path: string; parentId: DirectoryId; userId: number; + path: string; mekVersion: number; encDek: string; dekVersion: Date; @@ -40,14 +40,23 @@ export const registerDirectory = async (params: NewDirectoryParams) => { throw new IntegrityError("Inactive MEK version"); } - await tx.insert(directory).values({ - createdAt: new Date(), - parentId: params.parentId === "root" ? null : params.parentId, - userId: params.userId, - mekVersion: params.mekVersion, - encDek: params.encDek, - dekVersion: params.dekVersion, - encName: { ciphertext: params.encName, iv: params.encNameIv }, + const newDirectories = await tx + .insert(directory) + .values({ + parentId: params.parentId === "root" ? null : params.parentId, + userId: params.userId, + mekVersion: params.mekVersion, + encDek: params.encDek, + dekVersion: params.dekVersion, + encName: { ciphertext: params.encName, iv: params.encNameIv }, + }) + .returning({ id: directory.id }); + const { id: directoryId } = newDirectories[0]!; + await tx.insert(directoryLog).values({ + directoryId, + timestamp: new Date(), + action: "create", + newName: { ciphertext: params.encName, iv: params.encNameIv }, }); }, { behavior: "exclusive" }, @@ -99,6 +108,12 @@ export const setDirectoryEncName = async ( .update(directory) .set({ encName: { ciphertext: encName, iv: encNameIv } }) .where(and(eq(directory.userId, userId), eq(directory.id, directoryId))); + await tx.insert(directoryLog).values({ + directoryId, + timestamp: new Date(), + action: "rename", + newName: { ciphertext: encName, iv: encNameIv }, + }); }, { behavior: "exclusive" }, ); @@ -148,17 +163,26 @@ export const registerFile = async (params: NewFileParams) => { throw new IntegrityError("Inactive MEK version"); } - await tx.insert(file).values({ - path: params.path, - parentId: params.parentId === "root" ? null : params.parentId, - createdAt: new Date(), - userId: params.userId, - mekVersion: params.mekVersion, - contentType: params.contentType, - encDek: params.encDek, - dekVersion: params.dekVersion, - encContentIv: params.encContentIv, - encName: { ciphertext: params.encName, iv: params.encNameIv }, + const newFiles = await tx + .insert(file) + .values({ + path: params.path, + parentId: params.parentId === "root" ? null : params.parentId, + userId: params.userId, + mekVersion: params.mekVersion, + contentType: params.contentType, + encDek: params.encDek, + dekVersion: params.dekVersion, + encContentIv: params.encContentIv, + encName: { ciphertext: params.encName, iv: params.encNameIv }, + }) + .returning({ id: file.id }); + const { id: fileId } = newFiles[0]!; + await tx.insert(fileLog).values({ + fileId, + timestamp: new Date(), + action: "create", + newName: { ciphertext: params.encName, iv: params.encNameIv }, }); }, { behavior: "exclusive" }, @@ -210,6 +234,12 @@ export const setFileEncName = async ( .update(file) .set({ encName: { ciphertext: encName, iv: encNameIv } }) .where(and(eq(file.userId, userId), eq(file.id, fileId))); + await tx.insert(fileLog).values({ + fileId, + timestamp: new Date(), + action: "rename", + newName: { ciphertext: encName, iv: encNameIv }, + }); }, { behavior: "exclusive" }, ); diff --git a/src/lib/server/db/schema/file.ts b/src/lib/server/db/schema/file.ts index dbaf944..0bae91a 100644 --- a/src/lib/server/db/schema/file.ts +++ b/src/lib/server/db/schema/file.ts @@ -12,7 +12,6 @@ export const directory = sqliteTable( "directory", { id: integer("id").primaryKey({ autoIncrement: true }), - createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(), parentId: integer("parent_id"), userId: integer("user_id") .notNull() @@ -34,16 +33,25 @@ export const directory = sqliteTable( }), ); +export const directoryLog = sqliteTable("directory_log", { + id: integer("id").primaryKey({ autoIncrement: true }), + directoryId: integer("directory_id") + .notNull() + .references(() => directory.id, { onDelete: "cascade" }), + timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(), + action: text("action", { enum: ["create", "rename"] }).notNull(), + newName: ciphertext("new_name"), +}); + export const file = sqliteTable( "file", { id: integer("id").primaryKey({ autoIncrement: true }), - path: text("path").notNull().unique(), parentId: integer("parent_id").references(() => directory.id), - createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(), userId: integer("user_id") .notNull() .references(() => user.id), + path: text("path").notNull().unique(), mekVersion: integer("master_encryption_key_version").notNull(), encDek: text("encrypted_data_encryption_key").notNull().unique(), // Base64 dekVersion: integer("data_encryption_key_version", { mode: "timestamp_ms" }).notNull(), @@ -58,3 +66,13 @@ export const file = sqliteTable( }), }), ); + +export const fileLog = sqliteTable("file_log", { + id: integer("id").primaryKey({ autoIncrement: true }), + fileId: integer("file_id") + .notNull() + .references(() => file.id, { onDelete: "cascade" }), + timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(), + action: text("action", { enum: ["create", "rename"] }).notNull(), + newName: ciphertext("new_name"), +}); diff --git a/src/lib/server/schemas/directory.ts b/src/lib/server/schemas/directory.ts index eda0ac3..5f526aa 100644 --- a/src/lib/server/schemas/directory.ts +++ b/src/lib/server/schemas/directory.ts @@ -3,7 +3,6 @@ import { z } from "zod"; export const directoryInfoResponse = z.object({ metadata: z .object({ - createdAt: z.string().datetime(), mekVersion: z.number().int().positive(), dek: z.string().base64().nonempty(), dekVersion: z.string().datetime(), diff --git a/src/lib/server/schemas/file.ts b/src/lib/server/schemas/file.ts index 3d3d6f5..0592835 100644 --- a/src/lib/server/schemas/file.ts +++ b/src/lib/server/schemas/file.ts @@ -2,7 +2,6 @@ import mime from "mime"; import { z } from "zod"; export const fileInfoResponse = z.object({ - createdAt: z.string().datetime(), mekVersion: z.number().int().positive(), dek: z.string().base64().nonempty(), dekVersion: z.string().datetime(), diff --git a/src/lib/server/services/directory.ts b/src/lib/server/services/directory.ts index 5dc408f..aba0343 100644 --- a/src/lib/server/services/directory.ts +++ b/src/lib/server/services/directory.ts @@ -22,7 +22,6 @@ export const getDirectoryInformation = async (userId: number, directoryId: "root return { metadata: directory && { - createdAt: directory.createdAt, mekVersion: directory.mekVersion, encDek: directory.encDek, dekVersion: directory.dekVersion, diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index c87414c..b342b90 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -21,7 +21,6 @@ export const getFileInformation = async (userId: number, fileId: number) => { } return { - createdAt: file.createdAt, mekVersion: file.mekVersion, encDek: file.encDek, dekVersion: file.dekVersion, diff --git a/src/routes/api/directory/[id]/+server.ts b/src/routes/api/directory/[id]/+server.ts index 1b50018..b8cea7f 100644 --- a/src/routes/api/directory/[id]/+server.ts +++ b/src/routes/api/directory/[id]/+server.ts @@ -20,7 +20,6 @@ export const GET: RequestHandler = async ({ locals, params }) => { return json( directoryInfoResponse.parse({ metadata: metadata && { - createdAt: metadata.createdAt.toISOString(), mekVersion: metadata.mekVersion, dek: metadata.encDek, dekVersion: metadata.dekVersion.toISOString(), diff --git a/src/routes/api/file/[id]/+server.ts b/src/routes/api/file/[id]/+server.ts index 6007589..cb5d0de 100644 --- a/src/routes/api/file/[id]/+server.ts +++ b/src/routes/api/file/[id]/+server.ts @@ -16,11 +16,10 @@ export const GET: RequestHandler = async ({ locals, params }) => { if (!zodRes.success) error(400, "Invalid path parameters"); const { id } = zodRes.data; - const { createdAt, mekVersion, encDek, dekVersion, contentType, encContentIv, encName } = + const { mekVersion, encDek, dekVersion, contentType, encContentIv, encName } = await getFileInformation(userId, id); return json( fileInfoResponse.parse({ - createdAt: createdAt.toISOString(), mekVersion, dek: encDek, dekVersion: dekVersion.toISOString(), From 004e41b0cf59b2ace2db9a85f173edf68f3cb7ae Mon Sep 17 00:00:00 2001 From: static Date: Sun, 12 Jan 2025 19:22:21 +0900 Subject: [PATCH 07/17] =?UTF-8?q?MEK=20=EB=93=B1=EB=A1=9D=EC=8B=9C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EB=A5=BC=20=EB=82=A8=EA=B8=B0=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/db/mek.ts | 11 ++++++++--- src/lib/server/db/schema/mek.ts | 24 ++++++++++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/lib/server/db/mek.ts b/src/lib/server/db/mek.ts index 237ef59..944636e 100644 --- a/src/lib/server/db/mek.ts +++ b/src/lib/server/db/mek.ts @@ -2,7 +2,7 @@ import { SqliteError } from "better-sqlite3"; import { and, or, eq } from "drizzle-orm"; import db from "./drizzle"; import { IntegrityError } from "./error"; -import { mek, clientMek } from "./schema"; +import { mek, mekLog, clientMek } from "./schema"; export const registerInitialMek = async ( userId: number, @@ -16,8 +16,6 @@ export const registerInitialMek = async ( await tx.insert(mek).values({ userId, version: 1, - createdBy, - createdAt: new Date(), state: "active", }); await tx.insert(clientMek).values({ @@ -27,6 +25,13 @@ export const registerInitialMek = async ( encMek, encMekSig, }); + await tx.insert(mekLog).values({ + userId, + mekVersion: 1, + timestamp: new Date(), + action: "create", + actionBy: createdBy, + }); } catch (e) { if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_PRIMARYKEY") { throw new IntegrityError("MEK already registered"); diff --git a/src/lib/server/db/schema/mek.ts b/src/lib/server/db/schema/mek.ts index 8ab6fb8..e496d9e 100644 --- a/src/lib/server/db/schema/mek.ts +++ b/src/lib/server/db/schema/mek.ts @@ -9,10 +9,6 @@ export const mek = sqliteTable( .notNull() .references(() => user.id), version: integer("version").notNull(), - createdBy: integer("created_by") - .notNull() - .references(() => client.id), - createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(), state: text("state", { enum: ["active", "retired", "dead"] }).notNull(), retiredAt: integer("retired_at", { mode: "timestamp_ms" }), }, @@ -21,6 +17,26 @@ export const mek = sqliteTable( }), ); +export const mekLog = sqliteTable( + "master_encryption_key_log", + { + id: integer("id").primaryKey({ autoIncrement: true }), + userId: integer("user_id") + .notNull() + .references(() => user.id), + mekVersion: integer("master_encryption_key_version").notNull(), + timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(), + action: text("action", { enum: ["create"] }).notNull(), + actionBy: integer("action_by").references(() => client.id), + }, + (t) => ({ + ref: foreignKey({ + columns: [t.userId, t.mekVersion], + foreignColumns: [mek.userId, mek.version], + }), + }), +); + export const clientMek = sqliteTable( "client_master_encryption_key", { From 805d7df182e8acde734e71b596d8b6d1cf447d04 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 12 Jan 2025 20:26:48 +0900 Subject: [PATCH 08/17] =?UTF-8?q?/api/hsk/list,=20/api/hsk/register/initia?= =?UTF-8?q?l=20Endpoint=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/db/error.ts | 3 ++ src/lib/server/db/file.ts | 21 ++++++++- src/lib/server/db/hsk.ts | 45 +++++++++++++++++++ src/lib/server/db/schema/file.ts | 9 +++- src/lib/server/db/schema/hsk.ts | 43 ++++++++++++++++++ src/lib/server/db/schema/index.ts | 1 + src/lib/server/schemas/file.ts | 2 + src/lib/server/schemas/hsk.ts | 19 ++++++++ src/lib/server/schemas/index.ts | 1 + src/lib/server/services/hsk.ts | 31 +++++++++++++ src/routes/api/file/upload/+server.ts | 16 ++++++- src/routes/api/hsk/list/+server.ts | 20 +++++++++ .../api/hsk/register/initial/+server.ts | 16 +++++++ 13 files changed, 223 insertions(+), 4 deletions(-) create mode 100644 src/lib/server/db/hsk.ts create mode 100644 src/lib/server/db/schema/hsk.ts create mode 100644 src/lib/server/schemas/hsk.ts create mode 100644 src/lib/server/services/hsk.ts create mode 100644 src/routes/api/hsk/list/+server.ts create mode 100644 src/routes/api/hsk/register/initial/+server.ts diff --git a/src/lib/server/db/error.ts b/src/lib/server/db/error.ts index beadb6f..547cc6c 100644 --- a/src/lib/server/db/error.ts +++ b/src/lib/server/db/error.ts @@ -8,6 +8,9 @@ type IntegrityErrorMessages = | "Directory not found" | "File not found" | "Invalid DEK version" + // HSK + | "HSK already registered" + | "Inactive HSK version" // MEK | "MEK already registered" | "Inactive MEK version" diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index edf0249..1ce230c 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -1,7 +1,7 @@ import { and, eq, isNull } from "drizzle-orm"; import db from "./drizzle"; import { IntegrityError } from "./error"; -import { directory, directoryLog, file, fileLog, mek } from "./schema"; +import { directory, directoryLog, file, fileLog, hsk, mek } from "./schema"; type DirectoryId = "root" | number; @@ -22,6 +22,8 @@ export interface NewFileParams { mekVersion: number; encDek: string; dekVersion: Date; + hskVersion: number | null; + contentHmac: string | null; contentType: string; encContentIv: string; encName: string; @@ -152,6 +154,10 @@ export const unregisterDirectory = async (userId: number, directoryId: number) = }; export const registerFile = async (params: NewFileParams) => { + if ((params.hskVersion && !params.contentHmac) || (!params.hskVersion && params.contentHmac)) { + throw new Error("Invalid arguments"); + } + await db.transaction( async (tx) => { const meks = await tx @@ -163,6 +169,17 @@ export const registerFile = async (params: NewFileParams) => { throw new IntegrityError("Inactive MEK version"); } + if (params.hskVersion) { + const hsks = await tx + .select({ version: hsk.version }) + .from(hsk) + .where(and(eq(hsk.userId, params.userId), eq(hsk.state, "active"))) + .limit(1); + if (hsks[0]?.version !== params.hskVersion) { + throw new IntegrityError("Inactive HSK version"); + } + } + const newFiles = await tx .insert(file) .values({ @@ -170,6 +187,8 @@ export const registerFile = async (params: NewFileParams) => { parentId: params.parentId === "root" ? null : params.parentId, userId: params.userId, mekVersion: params.mekVersion, + hskVersion: params.hskVersion, + contentHmac: params.contentHmac, contentType: params.contentType, encDek: params.encDek, dekVersion: params.dekVersion, diff --git a/src/lib/server/db/hsk.ts b/src/lib/server/db/hsk.ts new file mode 100644 index 0000000..809ed7b --- /dev/null +++ b/src/lib/server/db/hsk.ts @@ -0,0 +1,45 @@ +import { SqliteError } from "better-sqlite3"; +import { and, eq } from "drizzle-orm"; +import db from "./drizzle"; +import { IntegrityError } from "./error"; +import { hsk, hskLog } from "./schema"; + +export const registerInitialHsk = async ( + userId: number, + createdBy: number, + mekVersion: number, + encHsk: string, +) => { + await db.transaction( + async (tx) => { + try { + await tx.insert(hsk).values({ + userId, + version: 1, + state: "active", + mekVersion, + encHsk, + }); + await tx.insert(hskLog).values({ + userId, + hskVersion: 1, + timestamp: new Date(), + action: "create", + actionBy: createdBy, + }); + } catch (e) { + if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_PRIMARYKEY") { + throw new IntegrityError("HSK already registered"); + } + } + }, + { behavior: "exclusive" }, + ); +}; + +export const getAllValidHsks = async (userId: number) => { + return await db + .select() + .from(hsk) + .where(and(eq(hsk.userId, userId), eq(hsk.state, "active"))); +}; diff --git a/src/lib/server/db/schema/file.ts b/src/lib/server/db/schema/file.ts index 0bae91a..7ac0b77 100644 --- a/src/lib/server/db/schema/file.ts +++ b/src/lib/server/db/schema/file.ts @@ -1,4 +1,5 @@ import { sqliteTable, text, integer, foreignKey } from "drizzle-orm/sqlite-core"; +import { hsk } from "./hsk"; import { mek } from "./mek"; import { user } from "./user"; @@ -55,15 +56,21 @@ export const file = sqliteTable( mekVersion: integer("master_encryption_key_version").notNull(), encDek: text("encrypted_data_encryption_key").notNull().unique(), // Base64 dekVersion: integer("data_encryption_key_version", { mode: "timestamp_ms" }).notNull(), + hskVersion: integer("hmac_secret_key_version"), + contentHmac: text("content_hmac"), // Base64 contentType: text("content_type").notNull(), encContentIv: text("encrypted_content_iv").notNull(), // Base64 encName: ciphertext("encrypted_name").notNull(), }, (t) => ({ - ref: foreignKey({ + ref1: foreignKey({ columns: [t.userId, t.mekVersion], foreignColumns: [mek.userId, mek.version], }), + ref2: foreignKey({ + columns: [t.userId, t.hskVersion], + foreignColumns: [hsk.userId, hsk.version], + }), }), ); diff --git a/src/lib/server/db/schema/hsk.ts b/src/lib/server/db/schema/hsk.ts new file mode 100644 index 0000000..b78c512 --- /dev/null +++ b/src/lib/server/db/schema/hsk.ts @@ -0,0 +1,43 @@ +import { sqliteTable, text, integer, primaryKey, foreignKey } from "drizzle-orm/sqlite-core"; +import { mek } from "./mek"; +import { user } from "./user"; + +export const hsk = sqliteTable( + "hmac_secret_key", + { + userId: integer("user_id") + .notNull() + .references(() => user.id), + version: integer("version").notNull(), + state: text("state", { enum: ["active"] }).notNull(), + mekVersion: integer("master_encryption_key_version").notNull(), + encHsk: text("encrypted_key").notNull().unique(), // Base64 + }, + (t) => ({ + pk: primaryKey({ columns: [t.userId, t.version] }), + ref: foreignKey({ + columns: [t.userId, t.mekVersion], + foreignColumns: [mek.userId, mek.version], + }), + }), +); + +export const hskLog = sqliteTable( + "hmac_secret_key_log", + { + id: integer("id").primaryKey({ autoIncrement: true }), + userId: integer("user_id") + .notNull() + .references(() => user.id), + hskVersion: integer("hmac_secret_key_version").notNull(), + timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(), + action: text("action", { enum: ["create"] }).notNull(), + actionBy: integer("action_by").references(() => user.id), + }, + (t) => ({ + ref: foreignKey({ + columns: [t.userId, t.hskVersion], + foreignColumns: [hsk.userId, hsk.version], + }), + }), +); diff --git a/src/lib/server/db/schema/index.ts b/src/lib/server/db/schema/index.ts index 41fd4fe..40cb9be 100644 --- a/src/lib/server/db/schema/index.ts +++ b/src/lib/server/db/schema/index.ts @@ -1,5 +1,6 @@ export * from "./client"; export * from "./file"; +export * from "./hsk"; export * from "./mek"; export * from "./session"; export * from "./user"; diff --git a/src/lib/server/schemas/file.ts b/src/lib/server/schemas/file.ts index 0592835..958c4d7 100644 --- a/src/lib/server/schemas/file.ts +++ b/src/lib/server/schemas/file.ts @@ -27,6 +27,8 @@ export const fileUploadRequest = z.object({ mekVersion: z.number().int().positive(), dek: z.string().base64().nonempty(), dekVersion: z.string().datetime(), + hskVersion: z.number().int().positive(), + contentHmac: z.string().base64().nonempty(), contentType: z .string() .nonempty() diff --git a/src/lib/server/schemas/hsk.ts b/src/lib/server/schemas/hsk.ts new file mode 100644 index 0000000..bcea3cd --- /dev/null +++ b/src/lib/server/schemas/hsk.ts @@ -0,0 +1,19 @@ +import { z } from "zod"; + +export const hmacSecretListResponse = z.object({ + hsks: z.array( + z.object({ + version: z.number().int().positive(), + state: z.enum(["active"]), + mekVersion: z.number().int().positive(), + hsk: z.string().base64().nonempty(), + }), + ), +}); +export type HmacSecretListResponse = z.infer; + +export const initialHmacSecretRegisterRequest = z.object({ + mekVersion: z.number().int().positive(), + hsk: z.string().base64().nonempty(), +}); +export type InitialHmacSecretRegisterRequest = z.infer; diff --git a/src/lib/server/schemas/index.ts b/src/lib/server/schemas/index.ts index cd2a366..615e2bc 100644 --- a/src/lib/server/schemas/index.ts +++ b/src/lib/server/schemas/index.ts @@ -2,4 +2,5 @@ export * from "./auth"; export * from "./client"; export * from "./directory"; export * from "./file"; +export * from "./hsk"; export * from "./mek"; diff --git a/src/lib/server/services/hsk.ts b/src/lib/server/services/hsk.ts new file mode 100644 index 0000000..c381c51 --- /dev/null +++ b/src/lib/server/services/hsk.ts @@ -0,0 +1,31 @@ +import { error } from "@sveltejs/kit"; +import { IntegrityError } from "$lib/server/db/error"; +import { registerInitialHsk, getAllValidHsks } from "$lib/server/db/hsk"; + +export const getHskList = async (userId: number) => { + const hsks = await getAllValidHsks(userId); + return { + encHsks: hsks.map(({ version, state, mekVersion, encHsk }) => ({ + version, + state, + mekVersion, + encHsk, + })), + }; +}; + +export const registerInitialActiveHsk = async ( + userId: number, + createdBy: number, + mekVersion: number, + encHsk: string, +) => { + try { + await registerInitialHsk(userId, createdBy, mekVersion, encHsk); + } catch (e) { + if (e instanceof IntegrityError && e.message === "HSK already registered") { + error(409, "Initial HSK already registered"); + } + throw e; + } +}; diff --git a/src/routes/api/file/upload/+server.ts b/src/routes/api/file/upload/+server.ts index ac1ac51..1bc9ce2 100644 --- a/src/routes/api/file/upload/+server.ts +++ b/src/routes/api/file/upload/+server.ts @@ -16,8 +16,18 @@ export const POST: RequestHandler = async ({ locals, request }) => { const zodRes = fileUploadRequest.safeParse(JSON.parse(metadata)); if (!zodRes.success) error(400, "Invalid request body"); - const { parentId, mekVersion, dek, dekVersion, contentType, contentIv, name, nameIv } = - zodRes.data; + const { + parentId, + mekVersion, + dek, + dekVersion, + hskVersion, + contentHmac, + contentType, + contentIv, + name, + nameIv, + } = zodRes.data; await uploadFile( { @@ -26,6 +36,8 @@ export const POST: RequestHandler = async ({ locals, request }) => { mekVersion, encDek: dek, dekVersion: new Date(dekVersion), + hskVersion, + contentHmac, contentType, encContentIv: contentIv, encName: name, diff --git a/src/routes/api/hsk/list/+server.ts b/src/routes/api/hsk/list/+server.ts new file mode 100644 index 0000000..50957a3 --- /dev/null +++ b/src/routes/api/hsk/list/+server.ts @@ -0,0 +1,20 @@ +import { json } from "@sveltejs/kit"; +import { authorize } from "$lib/server/modules/auth"; +import { hmacSecretListResponse, type HmacSecretListResponse } from "$lib/server/schemas"; +import { getHskList } from "$lib/server/services/hsk"; +import type { RequestHandler } from "./$types"; + +export const GET: RequestHandler = async ({ locals }) => { + const { userId } = await authorize(locals, "activeClient"); + const { encHsks } = await getHskList(userId); + return json( + hmacSecretListResponse.parse({ + hsks: encHsks.map(({ version, state, mekVersion, encHsk }) => ({ + version, + state, + mekVersion, + hsk: encHsk, + })), + } satisfies HmacSecretListResponse), + ); +}; diff --git a/src/routes/api/hsk/register/initial/+server.ts b/src/routes/api/hsk/register/initial/+server.ts new file mode 100644 index 0000000..8b32952 --- /dev/null +++ b/src/routes/api/hsk/register/initial/+server.ts @@ -0,0 +1,16 @@ +import { error, text } from "@sveltejs/kit"; +import { authorize } from "$lib/server/modules/auth"; +import { initialHmacSecretRegisterRequest } from "$lib/server/schemas"; +import { registerInitialActiveHsk } from "$lib/server/services/hsk"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ locals, request }) => { + const { userId, clientId } = await authorize(locals, "activeClient"); + + const zodRes = initialHmacSecretRegisterRequest.safeParse(await request.json()); + if (!zodRes.success) error(400, "Invalid request body"); + const { mekVersion, hsk } = zodRes.data; + + await registerInitialActiveHsk(userId, clientId, mekVersion, hsk); + return text("HSK registered", { headers: { "Content-Type": "text/plain" } }); +}; From 59c8523e25d10f1d0202ef60f72340e3a24c4e67 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 12 Jan 2025 21:52:41 +0900 Subject: [PATCH 09/17] =?UTF-8?q?=EC=95=94=ED=98=B8=20=ED=82=A4=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EB=93=B1=EB=A1=9D=EC=8B=9C=20?= =?UTF-8?q?HSK=EB=8F=84=20=ED=95=A8=EA=BB=98=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=B0=8F=20=EB=93=B1=EB=A1=9D=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks.client.ts | 15 +++++-- src/lib/hooks/gotoStateful.ts | 1 + src/lib/indexedDB.ts | 23 ++++++++-- src/lib/modules/crypto/aes.ts | 21 +++++++++ src/lib/modules/crypto/rsa.ts | 8 ++-- src/lib/modules/crypto/sha.ts | 17 +++++++ src/lib/services/auth.ts | 4 +- src/lib/services/key.ts | 4 +- src/lib/stores/key.ts | 8 ++++ .../(fullscreen)/key/export/+page.svelte | 10 +++-- src/routes/(fullscreen)/key/export/service.ts | 20 +++++++-- .../(fullscreen)/key/generate/+page.svelte | 10 ++++- .../(fullscreen)/key/generate/service.ts | 13 +++++- .../(main)/directory/[[id]]/+page.svelte | 18 ++++++-- src/routes/(main)/directory/[[id]]/service.ts | 44 +++++++++++++++++-- 15 files changed, 183 insertions(+), 33 deletions(-) diff --git a/src/hooks.client.ts b/src/hooks.client.ts index 217d7ea..3f0ccfb 100644 --- a/src/hooks.client.ts +++ b/src/hooks.client.ts @@ -1,6 +1,6 @@ import type { ClientInit } from "@sveltejs/kit"; -import { getClientKey, getMasterKeys } from "$lib/indexedDB"; -import { clientKeyStore, masterKeyStore } from "$lib/stores"; +import { getClientKey, getMasterKeys, getHmacSecrets } from "$lib/indexedDB"; +import { clientKeyStore, masterKeyStore, hmacSecretStore } from "$lib/stores"; const prepareClientKeyStore = async () => { const [encryptKey, decryptKey, signKey, verifyKey] = await Promise.all([ @@ -21,6 +21,13 @@ const prepareMasterKeyStore = async () => { } }; -export const init: ClientInit = async () => { - await Promise.all([prepareClientKeyStore(), prepareMasterKeyStore()]); +const prepareHmacSecretStore = async () => { + const hmacSecrets = await getHmacSecrets(); + if (hmacSecrets.length > 0) { + hmacSecretStore.set(new Map(hmacSecrets.map((hmacSecret) => [hmacSecret.version, hmacSecret]))); + } +}; + +export const init: ClientInit = async () => { + await Promise.all([prepareClientKeyStore(), prepareMasterKeyStore(), prepareHmacSecretStore()]); }; diff --git a/src/lib/hooks/gotoStateful.ts b/src/lib/hooks/gotoStateful.ts index fffc95f..4bcbebf 100644 --- a/src/lib/hooks/gotoStateful.ts +++ b/src/lib/hooks/gotoStateful.ts @@ -11,6 +11,7 @@ interface KeyExportState { verifyKeyBase64: string; masterKeyWrapped: string; + hmacSecretWrapped: string; } const useAutoNull = (value: T | null) => { diff --git a/src/lib/indexedDB.ts b/src/lib/indexedDB.ts index b2fbd22..7a4c89e 100644 --- a/src/lib/indexedDB.ts +++ b/src/lib/indexedDB.ts @@ -7,22 +7,28 @@ interface ClientKey { key: CryptoKey; } -type MasterKeyState = "active" | "retired"; - interface MasterKey { version: number; - state: MasterKeyState; + state: "active" | "retired"; key: CryptoKey; } +interface HmacSecret { + version: number; + state: "active"; + secret: CryptoKey; +} + const keyStore = new Dexie("keyStore") as Dexie & { clientKey: EntityTable; masterKey: EntityTable; + hmacSecret: EntityTable; }; keyStore.version(1).stores({ clientKey: "usage", masterKey: "version", + hmacSecret: "version", }); export const getClientKey = async (usage: ClientKeyUsage) => { @@ -62,3 +68,14 @@ export const storeMasterKeys = async (keys: MasterKey[]) => { } await keyStore.masterKey.bulkPut(keys); }; + +export const getHmacSecrets = async () => { + return await keyStore.hmacSecret.toArray(); +}; + +export const storeHmacSecrets = async (secrets: HmacSecret[]) => { + if (secrets.some(({ secret }) => secret.extractable)) { + throw new Error("Hmac secrets must be nonextractable"); + } + await keyStore.hmacSecret.bulkPut(secrets); +}; diff --git a/src/lib/modules/crypto/aes.ts b/src/lib/modules/crypto/aes.ts index df04851..3c096ba 100644 --- a/src/lib/modules/crypto/aes.ts +++ b/src/lib/modules/crypto/aes.ts @@ -55,6 +55,27 @@ export const unwrapDataKey = async (dataKeyWrapped: string, masterKey: CryptoKey }; }; +export const wrapHmacSecret = async (hmacSecret: CryptoKey, masterKey: CryptoKey) => { + return encodeToBase64(await window.crypto.subtle.wrapKey("raw", hmacSecret, masterKey, "AES-KW")); +}; + +export const unwrapHmacSecret = async (hmacSecretWrapped: string, masterKey: CryptoKey) => { + return { + hmacSecret: await window.crypto.subtle.unwrapKey( + "raw", + decodeFromBase64(hmacSecretWrapped), + masterKey, + "AES-KW", + { + name: "HMAC", + hash: "SHA-256", + } satisfies HmacImportParams, + false, // Nonextractable + ["sign", "verify"], + ), + }; +}; + export const encryptData = async (data: BufferSource, dataKey: CryptoKey) => { const iv = window.crypto.getRandomValues(new Uint8Array(12)); const ciphertext = await window.crypto.subtle.encrypt( diff --git a/src/lib/modules/crypto/rsa.ts b/src/lib/modules/crypto/rsa.ts index 9eb81c0..4df8f9e 100644 --- a/src/lib/modules/crypto/rsa.ts +++ b/src/lib/modules/crypto/rsa.ts @@ -95,7 +95,7 @@ export const unwrapMasterKey = async ( }; }; -export const signMessage = async (message: BufferSource, signKey: CryptoKey) => { +export const signMessageRSA = async (message: BufferSource, signKey: CryptoKey) => { return await window.crypto.subtle.sign( { name: "RSA-PSS", @@ -106,7 +106,7 @@ export const signMessage = async (message: BufferSource, signKey: CryptoKey) => ); }; -export const verifySignature = async ( +export const verifySignatureRSA = async ( message: BufferSource, signature: BufferSource, verifyKey: CryptoKey, @@ -131,7 +131,7 @@ export const signMasterKeyWrapped = async ( version: masterKeyVersion, key: masterKeyWrapped, }); - return encodeToBase64(await signMessage(encodeString(serialized), signKey)); + return encodeToBase64(await signMessageRSA(encodeString(serialized), signKey)); }; export const verifyMasterKeyWrapped = async ( @@ -144,7 +144,7 @@ export const verifyMasterKeyWrapped = async ( version: masterKeyVersion, key: masterKeyWrapped, }); - return await verifySignature( + return await verifySignatureRSA( encodeString(serialized), decodeFromBase64(masterKeyWrappedSig), verifyKey, diff --git a/src/lib/modules/crypto/sha.ts b/src/lib/modules/crypto/sha.ts index e79f706..3acb258 100644 --- a/src/lib/modules/crypto/sha.ts +++ b/src/lib/modules/crypto/sha.ts @@ -1,3 +1,20 @@ export const digestMessage = async (message: BufferSource) => { return await window.crypto.subtle.digest("SHA-256", message); }; + +export const generateHmacSecret = async () => { + return { + hmacSecret: await window.crypto.subtle.generateKey( + { + name: "HMAC", + hash: "SHA-256", + } satisfies HmacKeyGenParams, + true, + ["sign", "verify"], + ), + }; +}; + +export const signMessageHmac = async (message: BufferSource, hmacSecret: CryptoKey) => { + return await window.crypto.subtle.sign("HMAC", hmacSecret, message); +}; diff --git a/src/lib/services/auth.ts b/src/lib/services/auth.ts index 03d445a..498c794 100644 --- a/src/lib/services/auth.ts +++ b/src/lib/services/auth.ts @@ -1,5 +1,5 @@ import { callPostApi } from "$lib/hooks"; -import { encodeToBase64, decryptChallenge, signMessage } from "$lib/modules/crypto"; +import { encodeToBase64, decryptChallenge, signMessageRSA } from "$lib/modules/crypto"; import type { SessionUpgradeRequest, SessionUpgradeResponse, @@ -20,7 +20,7 @@ export const requestSessionUpgrade = async ( const { challenge }: SessionUpgradeResponse = await res.json(); const answer = await decryptChallenge(challenge, decryptKey); - const answerSig = await signMessage(answer, signKey); + const answerSig = await signMessageRSA(answer, signKey); res = await callPostApi("/api/auth/upgradeSession/verify", { answer: encodeToBase64(answer), diff --git a/src/lib/services/key.ts b/src/lib/services/key.ts index 79a4390..fb368dd 100644 --- a/src/lib/services/key.ts +++ b/src/lib/services/key.ts @@ -3,7 +3,7 @@ import { storeMasterKeys } from "$lib/indexedDB"; import { encodeToBase64, decryptChallenge, - signMessage, + signMessageRSA, unwrapMasterKey, verifyMasterKeyWrapped, } from "$lib/modules/crypto"; @@ -29,7 +29,7 @@ export const requestClientRegistration = async ( const { challenge }: ClientRegisterResponse = await res.json(); const answer = await decryptChallenge(challenge, decryptKey); - const answerSig = await signMessage(answer, signKey); + const answerSig = await signMessageRSA(answer, signKey); res = await callPostApi("/api/client/register/verify", { answer: encodeToBase64(answer), diff --git a/src/lib/stores/key.ts b/src/lib/stores/key.ts index d742634..77d1268 100644 --- a/src/lib/stores/key.ts +++ b/src/lib/stores/key.ts @@ -13,6 +13,14 @@ export interface MasterKey { key: CryptoKey; } +export interface HmacSecret { + version: number; + state: "active"; + secret: CryptoKey; +} + export const clientKeyStore = writable(null); export const masterKeyStore = writable | null>(null); + +export const hmacSecretStore = writable | null>(null); diff --git a/src/routes/(fullscreen)/key/export/+page.svelte b/src/routes/(fullscreen)/key/export/+page.svelte index 3ce209f..297d91d 100644 --- a/src/routes/(fullscreen)/key/export/+page.svelte +++ b/src/routes/(fullscreen)/key/export/+page.svelte @@ -11,7 +11,7 @@ requestClientRegistration, storeClientKeys, requestSessionUpgrade, - requestInitialMasterKeyRegistration, + requestInitialMasterKeyAndHmacSecretRegistration, } from "./service"; import IconKey from "~icons/material-symbols/key"; @@ -69,9 +69,13 @@ throw new Error("Failed to upgrade session"); if ( - !(await requestInitialMasterKeyRegistration(data.masterKeyWrapped, $clientKeyStore.signKey)) + !(await requestInitialMasterKeyAndHmacSecretRegistration( + data.masterKeyWrapped, + data.hmacSecretWrapped, + $clientKeyStore.signKey, + )) ) - throw new Error("Failed to register initial MEK"); + throw new Error("Failed to register initial MEK and HSK"); await goto("/client/pending?redirect=" + encodeURIComponent(data.redirectPath)); } catch (e) { diff --git a/src/routes/(fullscreen)/key/export/service.ts b/src/routes/(fullscreen)/key/export/service.ts index b02dd09..a9aaaee 100644 --- a/src/routes/(fullscreen)/key/export/service.ts +++ b/src/routes/(fullscreen)/key/export/service.ts @@ -1,7 +1,10 @@ import { callPostApi } from "$lib/hooks"; import { storeClientKey } from "$lib/indexedDB"; import { signMasterKeyWrapped } from "$lib/modules/crypto"; -import type { InitialMasterKeyRegisterRequest } from "$lib/server/schemas"; +import type { + InitialMasterKeyRegisterRequest, + InitialHmacSecretRegisterRequest, +} from "$lib/server/schemas"; import type { ClientKeys } from "$lib/stores"; export { requestSessionUpgrade } from "$lib/services/auth"; @@ -44,13 +47,22 @@ export const storeClientKeys = async (clientKeys: ClientKeys) => { ]); }; -export const requestInitialMasterKeyRegistration = async ( +export const requestInitialMasterKeyAndHmacSecretRegistration = async ( masterKeyWrapped: string, + hmacSecretWrapped: string, signKey: CryptoKey, ) => { - const res = await callPostApi("/api/mek/register/initial", { + let res = await callPostApi("/api/mek/register/initial", { mek: masterKeyWrapped, mekSig: await signMasterKeyWrapped(masterKeyWrapped, 1, signKey), }); - return res.ok || res.status === 409; + if (!res.ok) { + return res.status === 409; + } + + res = await callPostApi("/api/hsk/register/initial", { + mekVersion: 1, + hsk: hmacSecretWrapped, + }); + return res.ok; }; diff --git a/src/routes/(fullscreen)/key/generate/+page.svelte b/src/routes/(fullscreen)/key/generate/+page.svelte index 81c6180..330a39e 100644 --- a/src/routes/(fullscreen)/key/generate/+page.svelte +++ b/src/routes/(fullscreen)/key/generate/+page.svelte @@ -6,7 +6,11 @@ import { gotoStateful } from "$lib/hooks"; import { clientKeyStore } from "$lib/stores"; import Order from "./Order.svelte"; - import { generateClientKeys, generateInitialMasterKey } from "./service"; + import { + generateClientKeys, + generateInitialMasterKey, + generateInitialHmacSecret, + } from "./service"; import IconKey from "~icons/material-symbols/key"; @@ -36,12 +40,14 @@ // TODO: Loading indicator const { encryptKey, ...clientKeys } = await generateClientKeys(); - const { masterKeyWrapped } = await generateInitialMasterKey(encryptKey); + const { masterKey, masterKeyWrapped } = await generateInitialMasterKey(encryptKey); + const { hmacSecretWrapped } = await generateInitialHmacSecret(masterKey); await gotoStateful("/key/export", { ...clientKeys, redirectPath: data.redirectPath, masterKeyWrapped, + hmacSecretWrapped, }); }; diff --git a/src/routes/(fullscreen)/key/generate/service.ts b/src/routes/(fullscreen)/key/generate/service.ts index b63da21..f970f46 100644 --- a/src/routes/(fullscreen)/key/generate/service.ts +++ b/src/routes/(fullscreen)/key/generate/service.ts @@ -3,8 +3,11 @@ import { generateSigningKeyPair, exportRSAKeyToBase64, makeRSAKeyNonextractable, - generateMasterKey, wrapMasterKey, + generateMasterKey, + makeAESKeyNonextractable, + wrapHmacSecret, + generateHmacSecret, } from "$lib/modules/crypto"; import { clientKeyStore } from "$lib/stores"; @@ -31,6 +34,14 @@ export const generateClientKeys = async () => { export const generateInitialMasterKey = async (encryptKey: CryptoKey) => { const { masterKey } = await generateMasterKey(); return { + masterKey: await makeAESKeyNonextractable(masterKey), masterKeyWrapped: await wrapMasterKey(masterKey, encryptKey), }; }; + +export const generateInitialHmacSecret = async (masterKey: CryptoKey) => { + const { hmacSecret } = await generateHmacSecret(); + return { + hmacSecretWrapped: await wrapHmacSecret(hmacSecret, masterKey), + }; +}; diff --git a/src/routes/(main)/directory/[[id]]/+page.svelte b/src/routes/(main)/directory/[[id]]/+page.svelte index f7fc3d4..a868e8a 100644 --- a/src/routes/(main)/directory/[[id]]/+page.svelte +++ b/src/routes/(main)/directory/[[id]]/+page.svelte @@ -1,10 +1,11 @@ + + +
+
+

이미 업로드된 파일이에요.

+

그래도 업로드할까요?

+
+
+ + +
+
+
diff --git a/src/routes/(main)/directory/[[id]]/service.ts b/src/routes/(main)/directory/[[id]]/service.ts index b237ae5..32575fb 100644 --- a/src/routes/(main)/directory/[[id]]/service.ts +++ b/src/routes/(main)/directory/[[id]]/service.ts @@ -15,6 +15,8 @@ import type { FileRenameRequest, FileUploadRequest, HmacSecretListResponse, + DuplicateFileScanRequest, + DuplicateFileScanResponse, } from "$lib/server/schemas"; import { hmacSecretStore, type MasterKey, type HmacSecret } from "$lib/stores"; @@ -63,17 +65,33 @@ export const requestDirectoryCreation = async ( }); }; +export const requestDuplicateFileScan = async (file: File, hmacSecret: HmacSecret) => { + const fileBuffer = await file.arrayBuffer(); + const fileSigned = encodeToBase64(await signMessageHmac(fileBuffer, hmacSecret.secret)); + const res = await callPostApi("/api/file/scanDuplicates", { + hskVersion: hmacSecret.version, + contentHmac: fileSigned, + }); + if (!res.ok) return null; + + const { files }: DuplicateFileScanResponse = await res.json(); + return { + fileBuffer, + fileSigned, + isDuplicate: files.length > 0, + }; +}; + export const requestFileUpload = async ( file: File, + fileBuffer: ArrayBuffer, + fileSigned: string, parentId: "root" | number, masterKey: MasterKey, hmacSecret: HmacSecret, ) => { const { dataKey, dataKeyVersion } = await generateDataKey(); const nameEncrypted = await encryptString(file.name, dataKey); - - const fileBuffer = await file.arrayBuffer(); - const fileSigned = await signMessageHmac(fileBuffer, hmacSecret.secret); const fileEncrypted = await encryptData(fileBuffer, dataKey); const form = new FormData(); @@ -85,7 +103,7 @@ export const requestFileUpload = async ( dek: await wrapDataKey(dataKey, masterKey.key), dekVersion: dataKeyVersion.toISOString(), hskVersion: hmacSecret.version, - contentHmac: encodeToBase64(fileSigned), + contentHmac: fileSigned, contentType: file.type, contentIv: fileEncrypted.iv, name: nameEncrypted.ciphertext, diff --git a/src/routes/api/file/scanDuplicates/+server.ts b/src/routes/api/file/scanDuplicates/+server.ts new file mode 100644 index 0000000..fb41b43 --- /dev/null +++ b/src/routes/api/file/scanDuplicates/+server.ts @@ -0,0 +1,20 @@ +import { error, json } from "@sveltejs/kit"; +import { authorize } from "$lib/server/modules/auth"; +import { + duplicateFileScanRequest, + duplicateFileScanResponse, + type DuplicateFileScanResponse, +} from "$lib/server/schemas"; +import { scanDuplicateFiles } from "$lib/server/services/file"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ locals, request }) => { + const { userId } = await authorize(locals, "activeClient"); + + const zodRes = duplicateFileScanRequest.safeParse(await request.json()); + if (!zodRes.success) error(400, "Invalid request body"); + const { hskVersion, contentHmac } = zodRes.data; + + const { files } = await scanDuplicateFiles(userId, hskVersion, contentHmac); + return json(duplicateFileScanResponse.parse({ files } satisfies DuplicateFileScanResponse)); +}; From e887fcf1372bf2315a465a9879f4ee5e3e5b8933 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 13 Jan 2025 00:48:08 +0900 Subject: [PATCH 11/17] =?UTF-8?q?DB=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=AC=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ..._bullseye.sql => 0000_lush_black_bolt.sql} | 64 ++- drizzle/meta/0000_snapshot.json | 473 ++++++++++++++++-- drizzle/meta/_journal.json | 4 +- 3 files changed, 486 insertions(+), 55 deletions(-) rename drizzle/{0000_spooky_lady_bullseye.sql => 0000_lush_black_bolt.sql} (68%) diff --git a/drizzle/0000_spooky_lady_bullseye.sql b/drizzle/0000_lush_black_bolt.sql similarity index 68% rename from drizzle/0000_spooky_lady_bullseye.sql rename to drizzle/0000_lush_black_bolt.sql index d9b520c..4012e91 100644 --- a/drizzle/0000_spooky_lady_bullseye.sql +++ b/drizzle/0000_lush_black_bolt.sql @@ -27,7 +27,6 @@ CREATE TABLE `user_client_challenge` ( --> statement-breakpoint CREATE TABLE `directory` ( `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `created_at` integer NOT NULL, `parent_id` integer, `user_id` integer NOT NULL, `master_encryption_key_version` integer NOT NULL, @@ -39,23 +38,66 @@ CREATE TABLE `directory` ( FOREIGN KEY (`user_id`,`master_encryption_key_version`) REFERENCES `master_encryption_key`(`user_id`,`version`) ON UPDATE no action ON DELETE no action ); --> statement-breakpoint +CREATE TABLE `directory_log` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `directory_id` integer NOT NULL, + `timestamp` integer NOT NULL, + `action` text NOT NULL, + `new_name` text, + FOREIGN KEY (`directory_id`) REFERENCES `directory`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint CREATE TABLE `file` ( `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - `path` text NOT NULL, `parent_id` integer, - `created_at` integer NOT NULL, `user_id` integer NOT NULL, + `path` text NOT NULL, `master_encryption_key_version` integer NOT NULL, `encrypted_data_encryption_key` text NOT NULL, `data_encryption_key_version` integer NOT NULL, + `hmac_secret_key_version` integer, + `content_hmac` text, `content_type` text NOT NULL, `encrypted_content_iv` text NOT NULL, `encrypted_name` text NOT NULL, FOREIGN KEY (`parent_id`) REFERENCES `directory`(`id`) ON UPDATE no action ON DELETE no action, FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`user_id`,`master_encryption_key_version`) REFERENCES `master_encryption_key`(`user_id`,`version`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`user_id`,`hmac_secret_key_version`) REFERENCES `hmac_secret_key`(`user_id`,`version`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE TABLE `file_log` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `file_id` integer NOT NULL, + `timestamp` integer NOT NULL, + `action` text NOT NULL, + `new_name` text, + FOREIGN KEY (`file_id`) REFERENCES `file`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE TABLE `hmac_secret_key` ( + `user_id` integer NOT NULL, + `version` integer NOT NULL, + `state` text NOT NULL, + `master_encryption_key_version` integer NOT NULL, + `encrypted_key` text NOT NULL, + PRIMARY KEY(`user_id`, `version`), + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, FOREIGN KEY (`user_id`,`master_encryption_key_version`) REFERENCES `master_encryption_key`(`user_id`,`version`) ON UPDATE no action ON DELETE no action ); --> statement-breakpoint +CREATE TABLE `hmac_secret_key_log` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `user_id` integer NOT NULL, + `hmac_secret_key_version` integer NOT NULL, + `timestamp` integer NOT NULL, + `action` text NOT NULL, + `action_by` integer, + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`action_by`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`user_id`,`hmac_secret_key_version`) REFERENCES `hmac_secret_key`(`user_id`,`version`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint CREATE TABLE `client_master_encryption_key` ( `user_id` integer NOT NULL, `client_id` integer NOT NULL, @@ -71,13 +113,22 @@ CREATE TABLE `client_master_encryption_key` ( CREATE TABLE `master_encryption_key` ( `user_id` integer NOT NULL, `version` integer NOT NULL, - `created_by` integer NOT NULL, - `created_at` integer NOT NULL, `state` text NOT NULL, `retired_at` integer, PRIMARY KEY(`user_id`, `version`), + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE TABLE `master_encryption_key_log` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `user_id` integer NOT NULL, + `master_encryption_key_version` integer NOT NULL, + `timestamp` integer NOT NULL, + `action` text NOT NULL, + `action_by` integer, FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`created_by`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action + FOREIGN KEY (`action_by`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`user_id`,`master_encryption_key_version`) REFERENCES `master_encryption_key`(`user_id`,`version`) ON UPDATE no action ON DELETE no action ); --> statement-breakpoint CREATE TABLE `session` ( @@ -116,6 +167,7 @@ CREATE UNIQUE INDEX `user_client_challenge_answer_unique` ON `user_client_challe CREATE UNIQUE INDEX `directory_encrypted_data_encryption_key_unique` ON `directory` (`encrypted_data_encryption_key`);--> statement-breakpoint CREATE UNIQUE INDEX `file_path_unique` ON `file` (`path`);--> statement-breakpoint CREATE UNIQUE INDEX `file_encrypted_data_encryption_key_unique` ON `file` (`encrypted_data_encryption_key`);--> statement-breakpoint +CREATE UNIQUE INDEX `hmac_secret_key_encrypted_key_unique` ON `hmac_secret_key` (`encrypted_key`);--> statement-breakpoint CREATE UNIQUE INDEX `session_user_id_client_id_unique` ON `session` (`user_id`,`client_id`);--> statement-breakpoint CREATE UNIQUE INDEX `session_upgrade_challenge_session_id_unique` ON `session_upgrade_challenge` (`session_id`);--> statement-breakpoint CREATE UNIQUE INDEX `session_upgrade_challenge_answer_unique` ON `session_upgrade_challenge` (`answer`);--> statement-breakpoint diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json index 57c4a6a..4905c11 100644 --- a/drizzle/meta/0000_snapshot.json +++ b/drizzle/meta/0000_snapshot.json @@ -1,7 +1,7 @@ { "version": "6", "dialect": "sqlite", - "id": "c518e1b4-38f8-4c8e-bdc9-64152ab456d8", + "id": "f2fbe45c-1f1d-4dd8-92ab-dd057c0e668b", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "client": { @@ -234,13 +234,6 @@ "notNull": true, "autoincrement": true }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, "parent_id": { "name": "parent_id", "type": "integer", @@ -339,6 +332,64 @@ "compositePrimaryKeys": {}, "uniqueConstraints": {} }, + "directory_log": { + "name": "directory_log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "directory_id": { + "name": "directory_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "new_name": { + "name": "new_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "directory_log_directory_id_directory_id_fk": { + "name": "directory_log_directory_id_directory_id_fk", + "tableFrom": "directory_log", + "tableTo": "directory", + "columnsFrom": [ + "directory_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, "file": { "name": "file", "columns": { @@ -349,13 +400,6 @@ "notNull": true, "autoincrement": true }, - "path": { - "name": "path", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, "parent_id": { "name": "parent_id", "type": "integer", @@ -363,16 +407,16 @@ "notNull": false, "autoincrement": false }, - "created_at": { - "name": "created_at", + "user_id": { + "name": "user_id", "type": "integer", "primaryKey": false, "notNull": true, "autoincrement": false }, - "user_id": { - "name": "user_id", - "type": "integer", + "path": { + "name": "path", + "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false @@ -398,6 +442,20 @@ "notNull": true, "autoincrement": false }, + "hmac_secret_key_version": { + "name": "hmac_secret_key_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hmac": { + "name": "content_hmac", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, "content_type": { "name": "content_type", "type": "text", @@ -477,6 +535,261 @@ ], "onDelete": "no action", "onUpdate": "no action" + }, + "file_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk": { + "name": "file_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk", + "tableFrom": "file", + "tableTo": "hmac_secret_key", + "columnsFrom": [ + "user_id", + "hmac_secret_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "file_log": { + "name": "file_log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "file_id": { + "name": "file_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "new_name": { + "name": "new_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "file_log_file_id_file_id_fk": { + "name": "file_log_file_id_file_id_fk", + "tableFrom": "file_log", + "tableTo": "file", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "hmac_secret_key": { + "name": "hmac_secret_key", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "master_encryption_key_version": { + "name": "master_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "hmac_secret_key_encrypted_key_unique": { + "name": "hmac_secret_key_encrypted_key_unique", + "columns": [ + "encrypted_key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "hmac_secret_key_user_id_user_id_fk": { + "name": "hmac_secret_key_user_id_user_id_fk", + "tableFrom": "hmac_secret_key", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hmac_secret_key_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { + "name": "hmac_secret_key_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", + "tableFrom": "hmac_secret_key", + "tableTo": "master_encryption_key", + "columnsFrom": [ + "user_id", + "master_encryption_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "hmac_secret_key_user_id_version_pk": { + "columns": [ + "user_id", + "version" + ], + "name": "hmac_secret_key_user_id_version_pk" + } + }, + "uniqueConstraints": {} + }, + "hmac_secret_key_log": { + "name": "hmac_secret_key_log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "hmac_secret_key_version": { + "name": "hmac_secret_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action_by": { + "name": "action_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "hmac_secret_key_log_user_id_user_id_fk": { + "name": "hmac_secret_key_log_user_id_user_id_fk", + "tableFrom": "hmac_secret_key_log", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hmac_secret_key_log_action_by_user_id_fk": { + "name": "hmac_secret_key_log_action_by_user_id_fk", + "tableFrom": "hmac_secret_key_log", + "tableTo": "user", + "columnsFrom": [ + "action_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hmac_secret_key_log_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk": { + "name": "hmac_secret_key_log_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk", + "tableFrom": "hmac_secret_key_log", + "tableTo": "hmac_secret_key", + "columnsFrom": [ + "user_id", + "hmac_secret_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" } }, "compositePrimaryKeys": {}, @@ -594,20 +907,6 @@ "notNull": true, "autoincrement": false }, - "created_by": { - "name": "created_by", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, "state": { "name": "state", "type": "text", @@ -637,19 +936,6 @@ ], "onDelete": "no action", "onUpdate": "no action" - }, - "master_encryption_key_created_by_client_id_fk": { - "name": "master_encryption_key_created_by_client_id_fk", - "tableFrom": "master_encryption_key", - "tableTo": "client", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" } }, "compositePrimaryKeys": { @@ -663,6 +949,99 @@ }, "uniqueConstraints": {} }, + "master_encryption_key_log": { + "name": "master_encryption_key_log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "master_encryption_key_version": { + "name": "master_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action_by": { + "name": "action_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "master_encryption_key_log_user_id_user_id_fk": { + "name": "master_encryption_key_log_user_id_user_id_fk", + "tableFrom": "master_encryption_key_log", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "master_encryption_key_log_action_by_client_id_fk": { + "name": "master_encryption_key_log_action_by_client_id_fk", + "tableFrom": "master_encryption_key_log", + "tableTo": "client", + "columnsFrom": [ + "action_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "master_encryption_key_log_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { + "name": "master_encryption_key_log_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", + "tableFrom": "master_encryption_key_log", + "tableTo": "master_encryption_key", + "columnsFrom": [ + "user_id", + "master_encryption_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, "session": { "name": "session", "columns": { diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 62c9f38..723ede5 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "6", - "when": 1736637983139, - "tag": "0000_spooky_lady_bullseye", + "when": 1736696839327, + "tag": "0000_lush_black_bolt", "breakpoints": true } ] From 299787537e90297579dfbe092d61d75c13e00c8d Mon Sep 17 00:00:00 2001 From: static Date: Mon, 13 Jan 2025 01:57:07 +0900 Subject: [PATCH 12/17] =?UTF-8?q?/api/auth/changePassword,=20/api/user,=20?= =?UTF-8?q?/api/user/changeNickname=20Endpoint=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/db/schema/user.ts | 1 + src/lib/server/db/session.ts | 6 +++- src/lib/server/db/user.ts | 13 ++++++++ src/lib/server/schemas/auth.ts | 6 ++++ src/lib/server/schemas/index.ts | 1 + src/lib/server/schemas/user.ts | 12 ++++++++ src/lib/server/services/auth.ts | 30 ++++++++++++++++++- src/lib/server/services/user.ts | 15 ++++++++++ src/routes/api/auth/changePassword/+server.ts | 16 ++++++++++ src/routes/api/user/+server.ts | 11 +++++++ src/routes/api/user/changeNickname/+server.ts | 16 ++++++++++ 11 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 src/lib/server/schemas/user.ts create mode 100644 src/lib/server/services/user.ts create mode 100644 src/routes/api/auth/changePassword/+server.ts create mode 100644 src/routes/api/user/+server.ts create mode 100644 src/routes/api/user/changeNickname/+server.ts diff --git a/src/lib/server/db/schema/user.ts b/src/lib/server/db/schema/user.ts index 5d70e00..c98fa01 100644 --- a/src/lib/server/db/schema/user.ts +++ b/src/lib/server/db/schema/user.ts @@ -4,4 +4,5 @@ export const user = sqliteTable("user", { id: integer("id").primaryKey({ autoIncrement: true }), email: text("email").notNull().unique(), password: text("password").notNull(), + nickname: text("nickname").notNull(), }); diff --git a/src/lib/server/db/session.ts b/src/lib/server/db/session.ts index 276090a..b51bf85 100644 --- a/src/lib/server/db/session.ts +++ b/src/lib/server/db/session.ts @@ -1,5 +1,5 @@ import { SqliteError } from "better-sqlite3"; -import { and, eq, gt, lte, isNull } from "drizzle-orm"; +import { and, eq, ne, gt, lte, isNull } from "drizzle-orm"; import env from "$lib/server/loadenv"; import db from "./drizzle"; import { IntegrityError } from "./error"; @@ -71,6 +71,10 @@ export const deleteSession = async (sessionId: string) => { await db.delete(session).where(eq(session.id, sessionId)); }; +export const deleteAllOtherSessions = async (userId: number, sessionId: string) => { + await db.delete(session).where(and(eq(session.userId, userId), ne(session.id, sessionId))); +}; + export const cleanupExpiredSessions = async () => { await db.delete(session).where(lte(session.lastUsedAt, new Date(Date.now() - env.session.exp))); }; diff --git a/src/lib/server/db/user.ts b/src/lib/server/db/user.ts index 1efe43a..d970438 100644 --- a/src/lib/server/db/user.ts +++ b/src/lib/server/db/user.ts @@ -2,7 +2,20 @@ import { eq } from "drizzle-orm"; import db from "./drizzle"; import { user } from "./schema"; +export const getUser = async (userId: number) => { + const users = await db.select().from(user).where(eq(user.id, userId)).limit(1); + return users[0] ?? null; +}; + export const getUserByEmail = async (email: string) => { const users = await db.select().from(user).where(eq(user.email, email)).limit(1); return users[0] ?? null; }; + +export const setUserPassword = async (userId: number, password: string) => { + await db.update(user).set({ password }).where(eq(user.id, userId)); +}; + +export const setUserNickname = async (userId: number, nickname: string) => { + await db.update(user).set({ nickname }).where(eq(user.id, userId)); +}; diff --git a/src/lib/server/schemas/auth.ts b/src/lib/server/schemas/auth.ts index 91858c9..2d800d0 100644 --- a/src/lib/server/schemas/auth.ts +++ b/src/lib/server/schemas/auth.ts @@ -1,5 +1,11 @@ import { z } from "zod"; +export const changePasswordRequest = z.object({ + oldPassword: z.string().trim().nonempty(), + newPassword: z.string().trim().nonempty(), +}); +export type ChangePasswordRequest = z.infer; + export const loginRequest = z.object({ email: z.string().email().nonempty(), password: z.string().trim().nonempty(), diff --git a/src/lib/server/schemas/index.ts b/src/lib/server/schemas/index.ts index 615e2bc..6f8270b 100644 --- a/src/lib/server/schemas/index.ts +++ b/src/lib/server/schemas/index.ts @@ -4,3 +4,4 @@ export * from "./directory"; export * from "./file"; export * from "./hsk"; export * from "./mek"; +export * from "./user"; diff --git a/src/lib/server/schemas/user.ts b/src/lib/server/schemas/user.ts new file mode 100644 index 0000000..9841fba --- /dev/null +++ b/src/lib/server/schemas/user.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; + +export const userInfoResponse = z.object({ + email: z.string().email().nonempty(), + nickname: z.string().nonempty(), +}); +export type UserInfoResponse = z.infer; + +export const changeNicknameRequest = z.object({ + newNickname: z.string().min(2).max(8), +}); +export type ChangeNicknameRequest = z.infer; diff --git a/src/lib/server/services/auth.ts b/src/lib/server/services/auth.ts index c3fee31..81f0333 100644 --- a/src/lib/server/services/auth.ts +++ b/src/lib/server/services/auth.ts @@ -5,18 +5,46 @@ import { IntegrityError } from "$lib/server/db/error"; import { upgradeSession, deleteSession, + deleteAllOtherSessions, registerSessionUpgradeChallenge, consumeSessionUpgradeChallenge, } from "$lib/server/db/session"; -import { getUserByEmail } from "$lib/server/db/user"; +import { getUser, getUserByEmail, setUserPassword } from "$lib/server/db/user"; import env from "$lib/server/loadenv"; import { startSession } from "$lib/server/modules/auth"; import { verifySignature, generateChallenge } from "$lib/server/modules/crypto"; +const hashPassword = async (password: string) => { + return await argon2.hash(password); +}; + const verifyPassword = async (hash: string, password: string) => { return await argon2.verify(hash, password); }; +export const changePassword = async ( + userId: number, + sessionId: string, + oldPassword: string, + newPassword: string, +) => { + if (oldPassword === newPassword) { + error(400, "Same passwords"); + } else if (newPassword.length < 8) { + error(400, "Too short password"); + } + + const user = await getUser(userId); + if (!user) { + error(500, "Invalid session id"); + } else if (!(await verifyPassword(user.password, oldPassword))) { + error(403, "Invalid password"); + } + + await setUserPassword(userId, await hashPassword(newPassword)); + await deleteAllOtherSessions(userId, sessionId); +}; + export const login = async (email: string, password: string, ip: string, userAgent: string) => { const user = await getUserByEmail(email); if (!user || !(await verifyPassword(user.password, password))) { diff --git a/src/lib/server/services/user.ts b/src/lib/server/services/user.ts new file mode 100644 index 0000000..5c06458 --- /dev/null +++ b/src/lib/server/services/user.ts @@ -0,0 +1,15 @@ +import { error } from "@sveltejs/kit"; +import { getUser, setUserNickname } from "$lib/server/db/user"; + +export const getUserInformation = async (userId: number) => { + const user = await getUser(userId); + if (!user) { + error(500, "Invalid session id"); + } + + return { email: user.email, nickname: user.nickname }; +}; + +export const changeNickname = async (userId: number, nickname: string) => { + await setUserNickname(userId, nickname); +}; diff --git a/src/routes/api/auth/changePassword/+server.ts b/src/routes/api/auth/changePassword/+server.ts new file mode 100644 index 0000000..a0fbb8d --- /dev/null +++ b/src/routes/api/auth/changePassword/+server.ts @@ -0,0 +1,16 @@ +import { error, text } from "@sveltejs/kit"; +import { authorize } from "$lib/server/modules/auth"; +import { changePasswordRequest } from "$lib/server/schemas"; +import { changePassword } from "$lib/server/services/auth"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ locals, request }) => { + const { sessionId, userId } = await authorize(locals, "any"); + + const zodRes = changePasswordRequest.safeParse(await request.json()); + if (!zodRes.success) error(400, "Invalid request body"); + const { oldPassword, newPassword } = zodRes.data; + + await changePassword(userId, sessionId, oldPassword, newPassword); + return text("Password changed", { headers: { "Content-Type": "text/plain" } }); +}; diff --git a/src/routes/api/user/+server.ts b/src/routes/api/user/+server.ts new file mode 100644 index 0000000..b10b13e --- /dev/null +++ b/src/routes/api/user/+server.ts @@ -0,0 +1,11 @@ +import { json } from "@sveltejs/kit"; +import { authorize } from "$lib/server/modules/auth"; +import { userInfoResponse, type UserInfoResponse } from "$lib/server/schemas"; +import { getUserInformation } from "$lib/server/services/user"; +import type { RequestHandler } from "./$types"; + +export const GET: RequestHandler = async ({ locals }) => { + const { userId } = await authorize(locals, "any"); + const { email, nickname } = await getUserInformation(userId); + return json(userInfoResponse.parse({ email, nickname } satisfies UserInfoResponse)); +}; diff --git a/src/routes/api/user/changeNickname/+server.ts b/src/routes/api/user/changeNickname/+server.ts new file mode 100644 index 0000000..ab4f887 --- /dev/null +++ b/src/routes/api/user/changeNickname/+server.ts @@ -0,0 +1,16 @@ +import { error, text } from "@sveltejs/kit"; +import { authorize } from "$lib/server/modules/auth"; +import { changeNicknameRequest } from "$lib/server/schemas"; +import { changeNickname } from "$lib/server/services/user"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ locals, request }) => { + const { userId } = await authorize(locals, "any"); + + const zodRes = changeNicknameRequest.safeParse(await request.json()); + if (!zodRes.success) error(400, "Invalid request body"); + const { newNickname } = zodRes.data; + + await changeNickname(userId, newNickname); + return text("Nickname changed", { headers: { "Content-Type": "text/plain" } }); +}; From 8bb4d70fa51b4bbd704e1d1f2e81bc2d0c77a079 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 13 Jan 2025 02:53:32 +0900 Subject: [PATCH 13/17] =?UTF-8?q?=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/components/divs/TitleDiv.svelte | 7 ++-- src/lib/server/db/session.ts | 4 +- src/lib/server/middlewares/setAgentInfo.ts | 4 +- src/lib/server/schemas/auth.ts | 4 +- .../auth/changePassword/+page.svelte | 38 +++++++++++++++++++ .../auth/changePassword/service.ts | 10 +++++ src/routes/(main)/menu/+page.svelte | 26 ++++++++++++- src/routes/(main)/menu/+page.ts | 14 +++++++ src/routes/+layout.svelte | 2 +- src/routes/api/auth/changePassword/+server.ts | 4 +- 10 files changed, 99 insertions(+), 14 deletions(-) create mode 100644 src/routes/(fullscreen)/auth/changePassword/+page.svelte create mode 100644 src/routes/(fullscreen)/auth/changePassword/service.ts create mode 100644 src/routes/(main)/menu/+page.ts diff --git a/src/lib/components/divs/TitleDiv.svelte b/src/lib/components/divs/TitleDiv.svelte index e622e6e..d6c4414 100644 --- a/src/lib/components/divs/TitleDiv.svelte +++ b/src/lib/components/divs/TitleDiv.svelte @@ -3,15 +3,16 @@ import type { SvelteHTMLElements } from "svelte/elements"; interface Props { - icon?: Component; children: Snippet; + icon?: Component; + topPadding?: boolean; } - let { icon: Icon, children }: Props = $props(); + let { topPadding = true, children, icon: Icon }: Props = $props();
-
+
{#if Icon} {/if} diff --git a/src/lib/server/db/session.ts b/src/lib/server/db/session.ts index b51bf85..971e086 100644 --- a/src/lib/server/db/session.ts +++ b/src/lib/server/db/session.ts @@ -41,8 +41,8 @@ export const refreshSession = async ( .update(session) .set({ lastUsedAt: now, - lastUsedByIp: ip, - lastUsedByUserAgent: userAgent, + lastUsedByIp: ip ?? undefined, + lastUsedByUserAgent: userAgent ?? undefined, }) .where( and( diff --git a/src/lib/server/middlewares/setAgentInfo.ts b/src/lib/server/middlewares/setAgentInfo.ts index d272f2a..8e3f570 100644 --- a/src/lib/server/middlewares/setAgentInfo.ts +++ b/src/lib/server/middlewares/setAgentInfo.ts @@ -5,12 +5,12 @@ export const setAgentInfoMiddleware: Handle = async ({ event, resolve }) => { const userAgent = event.request.headers.get("User-Agent"); if (!ip) { error(500, "IP address not found"); - } else if (!userAgent) { + } else if (!userAgent && !event.isSubRequest) { error(400, "User agent not found"); } event.locals.ip = ip; - event.locals.userAgent = userAgent; + event.locals.userAgent = userAgent ?? ""; return await resolve(event); }; diff --git a/src/lib/server/schemas/auth.ts b/src/lib/server/schemas/auth.ts index 2d800d0..eae349f 100644 --- a/src/lib/server/schemas/auth.ts +++ b/src/lib/server/schemas/auth.ts @@ -1,10 +1,10 @@ import { z } from "zod"; -export const changePasswordRequest = z.object({ +export const passwordChangeRequest = z.object({ oldPassword: z.string().trim().nonempty(), newPassword: z.string().trim().nonempty(), }); -export type ChangePasswordRequest = z.infer; +export type PasswordChangeRequest = z.infer; export const loginRequest = z.object({ email: z.string().email().nonempty(), diff --git a/src/routes/(fullscreen)/auth/changePassword/+page.svelte b/src/routes/(fullscreen)/auth/changePassword/+page.svelte new file mode 100644 index 0000000..2b4875f --- /dev/null +++ b/src/routes/(fullscreen)/auth/changePassword/+page.svelte @@ -0,0 +1,38 @@ + + + + 비밀번호 바꾸기 + + +
+ + +
+

기존 비밀번호와 새 비밀번호를 입력해 주세요.

+

새 비밀번호는 8자 이상이어야 해요. 다른 사람들이 알 수 없도록 안전하게 설정해 주세요.

+
+
+ + +
+
+
+ + + diff --git a/src/routes/(fullscreen)/auth/changePassword/service.ts b/src/routes/(fullscreen)/auth/changePassword/service.ts new file mode 100644 index 0000000..37380a4 --- /dev/null +++ b/src/routes/(fullscreen)/auth/changePassword/service.ts @@ -0,0 +1,10 @@ +import { callPostApi } from "$lib/hooks"; +import type { PasswordChangeRequest } from "$lib/server/schemas"; + +export const requestPasswordChange = async (oldPassword: string, newPassword: string) => { + const res = await callPostApi("/api/auth/changePassword", { + oldPassword, + newPassword, + }); + return res.ok; +}; diff --git a/src/routes/(main)/menu/+page.svelte b/src/routes/(main)/menu/+page.svelte index 73d68b7..7b62e41 100644 --- a/src/routes/(main)/menu/+page.svelte +++ b/src/routes/(main)/menu/+page.svelte @@ -1,3 +1,25 @@ -
-

아직 개발 중이에요.

+ + +
+

{data.nickname}

+
+
+
+

보안

+ goto("/auth/changePassword")}> +
+
+ +
+

비밀번호 바꾸기

+
+
+
diff --git a/src/routes/(main)/menu/+page.ts b/src/routes/(main)/menu/+page.ts new file mode 100644 index 0000000..30a265a --- /dev/null +++ b/src/routes/(main)/menu/+page.ts @@ -0,0 +1,14 @@ +import { error } from "@sveltejs/kit"; +import { callGetApi } from "$lib/hooks"; +import type { UserInfoResponse } from "$lib/server/schemas"; +import type { PageLoad } from "./$types"; + +export const load: PageLoad = async ({ fetch }) => { + const res = await callGetApi("/api/user", fetch); + if (!res.ok) { + error(500, "Internal server error"); + } + + const { nickname }: UserInfoResponse = await res.json(); + return { nickname }; +}; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index d682821..091c034 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -8,7 +8,7 @@ onMount(async () => { const goto = async (url: string) => { - const whitelist = ["/auth", "/key", "/client/pending"]; + const whitelist = ["/auth/login", "/key", "/client/pending"]; if (!whitelist.some((path) => location.pathname.startsWith(path))) { await svelteGoto( `${url}?redirect=${encodeURIComponent(location.pathname + location.search)}`, diff --git a/src/routes/api/auth/changePassword/+server.ts b/src/routes/api/auth/changePassword/+server.ts index a0fbb8d..59129b8 100644 --- a/src/routes/api/auth/changePassword/+server.ts +++ b/src/routes/api/auth/changePassword/+server.ts @@ -1,13 +1,13 @@ import { error, text } from "@sveltejs/kit"; import { authorize } from "$lib/server/modules/auth"; -import { changePasswordRequest } from "$lib/server/schemas"; +import { passwordChangeRequest } from "$lib/server/schemas"; import { changePassword } from "$lib/server/services/auth"; import type { RequestHandler } from "./$types"; export const POST: RequestHandler = async ({ locals, request }) => { const { sessionId, userId } = await authorize(locals, "any"); - const zodRes = changePasswordRequest.safeParse(await request.json()); + const zodRes = passwordChangeRequest.safeParse(await request.json()); if (!zodRes.success) error(400, "Invalid request body"); const { oldPassword, newPassword } = zodRes.data; From 68a764bf280b0d1bc85594d38329b0e497ee2f47 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 13 Jan 2025 02:54:28 +0900 Subject: [PATCH 14/17] =?UTF-8?q?DB=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=AC=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ..._black_bolt.sql => 0000_unknown_stark_industries.sql} | 3 ++- drizzle/meta/0000_snapshot.json | 9 ++++++++- drizzle/meta/_journal.json | 4 ++-- package.json | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) rename drizzle/{0000_lush_black_bolt.sql => 0000_unknown_stark_industries.sql} (99%) diff --git a/drizzle/0000_lush_black_bolt.sql b/drizzle/0000_unknown_stark_industries.sql similarity index 99% rename from drizzle/0000_lush_black_bolt.sql rename to drizzle/0000_unknown_stark_industries.sql index 4012e91..28a9787 100644 --- a/drizzle/0000_lush_black_bolt.sql +++ b/drizzle/0000_unknown_stark_industries.sql @@ -157,7 +157,8 @@ CREATE TABLE `session_upgrade_challenge` ( CREATE TABLE `user` ( `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `email` text NOT NULL, - `password` text NOT NULL + `password` text NOT NULL, + `nickname` text NOT NULL ); --> statement-breakpoint CREATE UNIQUE INDEX `client_encryption_public_key_unique` ON `client` (`encryption_public_key`);--> statement-breakpoint diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json index 4905c11..42641f1 100644 --- a/drizzle/meta/0000_snapshot.json +++ b/drizzle/meta/0000_snapshot.json @@ -1,7 +1,7 @@ { "version": "6", "dialect": "sqlite", - "id": "f2fbe45c-1f1d-4dd8-92ab-dd057c0e668b", + "id": "928e5669-81cf-486c-9122-8ee64fc9f457", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "client": { @@ -1252,6 +1252,13 @@ "primaryKey": false, "notNull": true, "autoincrement": false + }, + "nickname": { + "name": "nickname", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false } }, "indexes": { diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 723ede5..e77a385 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "6", - "when": 1736696839327, - "tag": "0000_lush_black_bolt", + "when": 1736704436996, + "tag": "0000_unknown_stark_industries", "breakpoints": true } ] diff --git a/package.json b/package.json index 41cbbde..996e9f8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "arkvault", "private": true, - "version": "0.1.0", + "version": "0.2.0", "type": "module", "scripts": { "dev": "vite dev", From 6a64bb45f22b89e99488fe84aad830e403f72682 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 13 Jan 2025 03:17:54 +0900 Subject: [PATCH 15/17] =?UTF-8?q?=EB=A7=88=EC=A7=80=EB=A7=89=20=EC=A0=91?= =?UTF-8?q?=EC=86=8D=20IP=EC=99=80=20User=20Agent=EA=B0=80=20=EB=B9=88=20?= =?UTF-8?q?=EA=B0=92=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20DB=EC=97=90=20?= =?UTF-8?q?=ED=95=B4=EB=8B=B9=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/db/session.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/server/db/session.ts b/src/lib/server/db/session.ts index 971e086..819dd86 100644 --- a/src/lib/server/db/session.ts +++ b/src/lib/server/db/session.ts @@ -20,8 +20,8 @@ export const createSession = async ( clientId, createdAt: now, lastUsedAt: now, - lastUsedByIp: ip, - lastUsedByUserAgent: userAgent, + lastUsedByIp: ip || null, + lastUsedByUserAgent: userAgent || null, }); } catch (e) { if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { @@ -41,8 +41,8 @@ export const refreshSession = async ( .update(session) .set({ lastUsedAt: now, - lastUsedByIp: ip ?? undefined, - lastUsedByUserAgent: userAgent ?? undefined, + lastUsedByIp: ip || undefined, + lastUsedByUserAgent: userAgent || undefined, }) .where( and( From b8c7cda4d51e57a53cfeb9f99f910d0da9f26f21 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 13 Jan 2025 03:33:01 +0900 Subject: [PATCH 16/17] =?UTF-8?q?=EC=82=AC=EC=86=8C=ED=95=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/schemas/user.ts | 4 ++-- src/routes/(main)/menu/+page.svelte | 4 ++++ src/routes/api/user/changeNickname/+server.ts | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/lib/server/schemas/user.ts b/src/lib/server/schemas/user.ts index 9841fba..b359467 100644 --- a/src/lib/server/schemas/user.ts +++ b/src/lib/server/schemas/user.ts @@ -6,7 +6,7 @@ export const userInfoResponse = z.object({ }); export type UserInfoResponse = z.infer; -export const changeNicknameRequest = z.object({ +export const nicknameChangeRequest = z.object({ newNickname: z.string().min(2).max(8), }); -export type ChangeNicknameRequest = z.infer; +export type NicknameChangeRequest = z.infer; diff --git a/src/routes/(main)/menu/+page.svelte b/src/routes/(main)/menu/+page.svelte index 7b62e41..efb7a9e 100644 --- a/src/routes/(main)/menu/+page.svelte +++ b/src/routes/(main)/menu/+page.svelte @@ -7,6 +7,10 @@ let { data } = $props(); + + 메뉴 + +

{data.nickname}

diff --git a/src/routes/api/user/changeNickname/+server.ts b/src/routes/api/user/changeNickname/+server.ts index ab4f887..ad651ac 100644 --- a/src/routes/api/user/changeNickname/+server.ts +++ b/src/routes/api/user/changeNickname/+server.ts @@ -1,13 +1,13 @@ import { error, text } from "@sveltejs/kit"; import { authorize } from "$lib/server/modules/auth"; -import { changeNicknameRequest } from "$lib/server/schemas"; +import { nicknameChangeRequest } from "$lib/server/schemas"; import { changeNickname } from "$lib/server/services/user"; import type { RequestHandler } from "./$types"; export const POST: RequestHandler = async ({ locals, request }) => { const { userId } = await authorize(locals, "any"); - const zodRes = changeNicknameRequest.safeParse(await request.json()); + const zodRes = nicknameChangeRequest.safeParse(await request.json()); if (!zodRes.success) error(400, "Invalid request body"); const { newNickname } = zodRes.data; From 8a620fac783fe402f6f8c0b350209ff37359266b Mon Sep 17 00:00:00 2001 From: static Date: Mon, 13 Jan 2025 03:44:09 +0900 Subject: [PATCH 17/17] =?UTF-8?q?=EB=88=84=EB=9D=BD=EB=90=9C=20throw=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/db/hsk.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/server/db/hsk.ts b/src/lib/server/db/hsk.ts index 809ed7b..faf7dc6 100644 --- a/src/lib/server/db/hsk.ts +++ b/src/lib/server/db/hsk.ts @@ -31,6 +31,7 @@ export const registerInitialHsk = async ( if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_PRIMARYKEY") { throw new IntegrityError("HSK already registered"); } + throw e; } }, { behavior: "exclusive" },