Compare commits
8 Commits
feature/pr
...
develop
Author | SHA1 | Date | |
---|---|---|---|
bb80776776 | |||
230fcf79df | |||
4300729638 | |||
73ef39770e | |||
12a3001aec | |||
06a7f1bd51 | |||
d430f1db17 | |||
0e23c8e21c |
10
.editorconfig
Normal file
10
.editorconfig
Normal file
@ -0,0 +1,10 @@
|
||||
[*]
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
end_of_line = lf
|
||||
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
max_line_length = 100
|
||||
quote_type = single
|
||||
trim_trailing_whitespace = true
|
17
.env.example
17
.env.example
@ -1,12 +1,17 @@
|
||||
DATABASE_URL=file:local.db
|
||||
|
||||
AUTH_DOMAIN=auth.lab.cazzzer.com
|
||||
AUTH_CLIENT_ID=
|
||||
AUTH_CLIENT_SECRET=
|
||||
GOOGLE_CLIENT_ID=
|
||||
GOOGLE_CLIENT_SECRET=
|
||||
PUBLIC_AUTH_AUTHENTIK_ENABLE=1
|
||||
AUTH_AUTHENTIK_REQUIRE_INVITE=0
|
||||
AUTH_AUTHENTIK_DOMAIN=auth.lab.cazzzer.com
|
||||
AUTH_AUTHENTIK_CLIENT_ID=
|
||||
AUTH_AUTHENTIK_CLIENT_SECRET=
|
||||
|
||||
INVITE_TOKEN=GUjdsz9aREFTEBYDrA3AajUE8oVys2xW
|
||||
PUBLIC_AUTH_GOOGLE_ENABLE=1
|
||||
AUTH_GOOGLE_REQUIRE_INVITE=1
|
||||
AUTH_GOOGLE_CLIENT_ID=
|
||||
AUTH_GOOGLE_CLIENT_SECRET=
|
||||
|
||||
AUTH_INVITE_TOKEN=GUjdsz9aREFTEBYDrA3AajUE8oVys2xW
|
||||
|
||||
OPNSENSE_API_URL=https://opnsense.cazzzer.com
|
||||
OPNSENSE_API_KEY=
|
||||
|
15
.woodpecker/build-image.yml
Normal file
15
.woodpecker/build-image.yml
Normal file
@ -0,0 +1,15 @@
|
||||
when:
|
||||
- event: [push]
|
||||
steps:
|
||||
- name: build
|
||||
image: woodpeckerci/plugin-kaniko
|
||||
settings:
|
||||
registry: gitea.cazzzer.com
|
||||
repo: ${CI_REPO,,}
|
||||
# replace '/' in branch name
|
||||
tags: ${CI_COMMIT_BRANCH/\//-}
|
||||
cache: true
|
||||
username:
|
||||
from_secret: registry-username
|
||||
password:
|
||||
from_secret: registry-password
|
7
.zed/settings.json
Normal file
7
.zed/settings.json
Normal file
@ -0,0 +1,7 @@
|
||||
// Folder-specific settings
|
||||
//
|
||||
// For a full list of overridable settings, and general information on folder-specific settings,
|
||||
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
|
||||
{
|
||||
"formatter": "prettier"
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
# see all versions at https://hub.docker.com/r/oven/bun/tags
|
||||
FROM oven/bun:1-alpine AS base
|
||||
WORKDIR /app
|
||||
COPY package.json bun.lockb /app/
|
||||
COPY package.json bun.lock /app/
|
||||
|
||||
# install dependencies into temp directory
|
||||
# this will cache them and speed up future builds
|
||||
@ -14,7 +14,7 @@ RUN cd /temp/dev && bun install --frozen-lockfile
|
||||
# install with --production (exclude devDependencies)
|
||||
RUN mkdir -p /temp/prod
|
||||
COPY package.json bun.lock /temp/prod/
|
||||
RUN cd /temp/prod && bun install --frozen-lockfile --production
|
||||
RUN cd /temp/prod && bun install --frozen-lockfile --production --ignore-scripts
|
||||
|
||||
# copy node_modules from temp directory
|
||||
# then copy all (non-ignored) project files into the image
|
||||
|
196
bun.lock
196
bun.lock
@ -5,48 +5,48 @@
|
||||
"name": "vpgen",
|
||||
"dependencies": {
|
||||
"@libsql/client": "^0.14.0",
|
||||
"drizzle-kit": "^0.30.5",
|
||||
"drizzle-kit": "^0.30.6",
|
||||
"drizzle-orm": "^0.38.4",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lucide/svelte": "^0.487.0",
|
||||
"@oslojs/crypto": "^1.0.1",
|
||||
"@oslojs/encoding": "^1.1.0",
|
||||
"@ryoppippi/unplugin-typia": "npm:@jsr/ryoppippi__unplugin-typia",
|
||||
"@sveltejs/adapter-auto": "^3.3.1",
|
||||
"@sveltejs/adapter-node": "^5.2.12",
|
||||
"@sveltejs/kit": "^2.19.1",
|
||||
"@sveltejs/kit": "^2.20.7",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/better-sqlite3": "^7.6.12",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"@types/qrcode-svg": "^1.1.5",
|
||||
"arctic": "^2.3.4",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"bits-ui": "^0.22.0",
|
||||
"bits-ui": "^1.3.19",
|
||||
"clsx": "^2.1.1",
|
||||
"eslint": "^9.22.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.46.1",
|
||||
"globals": "^15.15.0",
|
||||
"ip-address": "^10.0.1",
|
||||
"lucide-svelte": "^0.469.0",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"qrcode-svg": "^1.1.0",
|
||||
"svelte": "^5.23.0",
|
||||
"svelte-check": "^4.1.5",
|
||||
"svelte": "^5.28.1",
|
||||
"svelte-check": "^4.1.6",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwind-variants": "^0.3.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"ts-patch": "^3.3.0",
|
||||
"typescript": "~5.8.2",
|
||||
"typescript-eslint": "^8.26.1",
|
||||
"typia": "^8.0.2",
|
||||
"vite": "^6.2.2",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.31.0",
|
||||
"typia": "^8.2.0",
|
||||
"vite": "^6.3.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -115,19 +115,19 @@
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
|
||||
|
||||
"@eslint/config-array": ["@eslint/config-array@0.19.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w=="],
|
||||
"@eslint/config-array": ["@eslint/config-array@0.20.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ=="],
|
||||
|
||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.1.0", "", {}, "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA=="],
|
||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.2.1", "", {}, "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw=="],
|
||||
|
||||
"@eslint/core": ["@eslint/core@0.12.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg=="],
|
||||
"@eslint/core": ["@eslint/core@0.13.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw=="],
|
||||
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.0", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ=="],
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
|
||||
|
||||
"@eslint/js": ["@eslint/js@9.22.0", "", {}, "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ=="],
|
||||
"@eslint/js": ["@eslint/js@9.25.1", "", {}, "sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg=="],
|
||||
|
||||
"@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="],
|
||||
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.2.7", "", { "dependencies": { "@eslint/core": "^0.12.0", "levn": "^0.4.1" } }, "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g=="],
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.2.8", "", { "dependencies": { "@eslint/core": "^0.13.0", "levn": "^0.4.1" } }, "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA=="],
|
||||
|
||||
"@floating-ui/core": ["@floating-ui/core@1.6.8", "", { "dependencies": { "@floating-ui/utils": "^0.2.8" } }, "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA=="],
|
||||
|
||||
@ -181,7 +181,7 @@
|
||||
|
||||
"@libsql/win32-x64-msvc": ["@libsql/win32-x64-msvc@0.4.7", "", { "os": "win32", "cpu": "x64" }, "sha512-7pJzOWzPm6oJUxml+PCDRzYQ4A1hTMHAciTAHfFK4fkbDZX33nWPVG7Y3vqdKtslcwAzwmrNDc6sXy2nwWnbiw=="],
|
||||
|
||||
"@melt-ui/svelte": ["@melt-ui/svelte@0.76.2", "", { "dependencies": { "@floating-ui/core": "^1.3.1", "@floating-ui/dom": "^1.4.5", "@internationalized/date": "^3.5.0", "dequal": "^2.0.3", "focus-trap": "^7.5.2", "nanoid": "^5.0.4" }, "peerDependencies": { "svelte": ">=3 <5" } }, "sha512-7SbOa11tXUS95T3fReL+dwDs5FyJtCEqrqG3inRziDws346SYLsxOQ6HmX+4BkIsQh1R8U3XNa+EMmdMt38lMA=="],
|
||||
"@lucide/svelte": ["@lucide/svelte@0.487.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-27b/wUzWrqDJu97+1iSV2X8L2JGRWH/mAWAjHgazWxhGxVu/kS0p3SbNu6w3skNmQNEku33EKU1v44IVwULzbw=="],
|
||||
|
||||
"@neon-rs/load": ["@neon-rs/load@0.0.4", "", {}, "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw=="],
|
||||
|
||||
@ -241,6 +241,8 @@
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.29.1", "", { "os": "linux", "cpu": "none" }, "sha512-G5pn0NChlbRM8OJWpJFMX4/i8OEU538uiSv0P6roZcbpe/WfhEO+AT8SHVKfp8qhDQzaz7Q+1/ixMy7hBRidnQ=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.40.0", "", { "os": "linux", "cpu": "none" }, "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.29.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-WM9lIkNdkhVwiArmLxFXpWndFGuOka4oJOZh8EP3Vb8q5lzdSCBuhjavJsw68Q9AKDGeOOIHYzYm4ZFvmWez5g=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.29.1", "", { "os": "linux", "cpu": "x64" }, "sha512-87xYCwb0cPGZFoGiErT1eDcssByaLX4fc0z2nRM6eMtV9njAfEE6OW3UniAoDhX4Iq5xQVpE6qO9aJbCFumKYQ=="],
|
||||
@ -255,7 +257,7 @@
|
||||
|
||||
"@ryoppippi/unplugin-typia": ["@jsr/ryoppippi__unplugin-typia@1.2.0", "https://npm.jsr.io/~/11/@jsr/ryoppippi__unplugin-typia/1.2.0.tgz", { "dependencies": { "@rollup/pluginutils": "^5.1.3", "consola": "^3.2.3", "defu": "^6.1.4", "diff-match-patch": "^1.0.5", "find-cache-dir": "^5.0.0", "magic-string": "^0.30.14", "pathe": "^1.1.2", "pkg-types": "^1.2.1", "type-fest": "^4.30.0", "typescript": "~5.6.3", "typia": "^7.6.0", "unplugin": "^1.16.0", "vite": "^6.0.2" } }, "sha512-WslO8hg5nbTRn1LD+fYpHub1CnwOg1y3+ezjrRH6gdAXdamr9n4Vr3COe5ejE8EjM6Ai+5WgTlOT0NJMu7w3MQ=="],
|
||||
|
||||
"@samchon/openapi": ["@samchon/openapi@3.0.0", "", {}, "sha512-eVQlyKRYv1/C2Mikc1xZr7c0jMjg1vjPkeY/gheKB4c5WOOWyTNZ1uvnXR+ETpPHwaQ54I9NrQZhoNk6BEGuuw=="],
|
||||
"@samchon/openapi": ["@samchon/openapi@3.3.0", "", {}, "sha512-f8HMS0PVAKTGR0TwgiBX4PKlZRPfdtH5fJbtpUth3fMUZmikjZ7CBey8gX7HZgY4n/aYHyNQKcf/W/E2267oYw=="],
|
||||
|
||||
"@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.5", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ=="],
|
||||
|
||||
@ -263,7 +265,7 @@
|
||||
|
||||
"@sveltejs/adapter-node": ["@sveltejs/adapter-node@5.2.12", "", { "dependencies": { "@rollup/plugin-commonjs": "^28.0.1", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "rollup": "^4.9.5" }, "peerDependencies": { "@sveltejs/kit": "^2.4.0" } }, "sha512-0bp4Yb3jKIEcZWVcJC/L1xXp9zzJS4hDwfb4VITAkfT4OVdkspSHsx7YhqJDbb2hgLl6R9Vs7VQR+fqIVOxPUQ=="],
|
||||
|
||||
"@sveltejs/kit": ["@sveltejs/kit@2.19.1", "", { "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^0.6.0", "devalue": "^5.1.0", "esm-env": "^1.2.2", "import-meta-resolve": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "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" }, "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-1u8FGWtT+V0eBSQpDtsjINhQd6RTjadvEgVApdNVxbUeOXmQTDENTeRiZw2i1lNUx1/5Wa65CHzxtMh3P3yviw=="],
|
||||
"@sveltejs/kit": ["@sveltejs/kit@2.20.7", "", { "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^0.6.0", "devalue": "^5.1.0", "esm-env": "^1.2.2", "import-meta-resolve": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "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" }, "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-dVbLMubpJJSLI4OYB+yWYNHGAhgc2bVevWuBjDj8jFUXIJOAnLwYP3vsmtcgoxNGUXoq0rHS5f7MFCsryb6nzg=="],
|
||||
|
||||
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@5.0.3", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.0", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.15", "vitefu": "^1.0.4" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw=="],
|
||||
|
||||
@ -277,7 +279,7 @@
|
||||
|
||||
"@tailwindcss/typography": ["@tailwindcss/typography@0.5.16", "", { "dependencies": { "lodash.castarray": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.merge": "^4.6.2", "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA=="],
|
||||
|
||||
"@types/better-sqlite3": ["@types/better-sqlite3@7.6.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-fnQmj8lELIj7BSrZQAdBMHEHX8OZLYIHXqAKT1O7tDfLxaINzf00PMjw22r3N/xXh0w/sGHlO6SVaCQ2mj78lg=="],
|
||||
"@types/better-sqlite3": ["@types/better-sqlite3@7.6.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA=="],
|
||||
|
||||
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
||||
|
||||
@ -295,21 +297,21 @@
|
||||
|
||||
"@types/ws": ["@types/ws@8.5.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.26.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.26.1", "@typescript-eslint/type-utils": "8.26.1", "@typescript-eslint/utils": "8.26.1", "@typescript-eslint/visitor-keys": "8.26.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-2X3mwqsj9Bd3Ciz508ZUtoQQYpOhU/kWoUqIf49H8Z0+Vbh6UF/y0OEYp0Q0axOGzaBGs7QxRwq0knSQ8khQNA=="],
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.31.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/type-utils": "8.31.0", "@typescript-eslint/utils": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.26.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.26.1", "@typescript-eslint/types": "8.26.1", "@typescript-eslint/typescript-estree": "8.26.1", "@typescript-eslint/visitor-keys": "8.26.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ=="],
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.31.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/types": "8.31.0", "@typescript-eslint/typescript-estree": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw=="],
|
||||
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.26.1", "", { "dependencies": { "@typescript-eslint/types": "8.26.1", "@typescript-eslint/visitor-keys": "8.26.1" } }, "sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg=="],
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.31.0", "", { "dependencies": { "@typescript-eslint/types": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.0" } }, "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw=="],
|
||||
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.26.1", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.26.1", "@typescript-eslint/utils": "8.26.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Kcj/TagJLwoY/5w9JGEFV0dclQdyqw9+VMndxOJKtoFSjfZhLXhYjzsQEeyza03rwHx2vFEGvrJWJBXKleRvZg=="],
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.31.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.31.0", "@typescript-eslint/utils": "8.31.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg=="],
|
||||
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.26.1", "", {}, "sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ=="],
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.31.0", "", {}, "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.26.1", "", { "dependencies": { "@typescript-eslint/types": "8.26.1", "@typescript-eslint/visitor-keys": "8.26.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA=="],
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.31.0", "", { "dependencies": { "@typescript-eslint/types": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ=="],
|
||||
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.26.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.26.1", "@typescript-eslint/types": "8.26.1", "@typescript-eslint/typescript-estree": "8.26.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg=="],
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.31.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/types": "8.31.0", "@typescript-eslint/typescript-estree": "8.31.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.26.1", "", { "dependencies": { "@typescript-eslint/types": "8.26.1", "eslint-visitor-keys": "^4.2.0" } }, "sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg=="],
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.31.0", "", { "dependencies": { "@typescript-eslint/types": "8.31.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ=="],
|
||||
|
||||
"acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
|
||||
|
||||
@ -347,7 +349,7 @@
|
||||
|
||||
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
||||
|
||||
"bits-ui": ["bits-ui@0.22.0", "", { "dependencies": { "@internationalized/date": "^3.5.1", "@melt-ui/svelte": "0.76.2", "nanoid": "^5.0.5" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0" } }, "sha512-r7Fw1HNgA4YxZBRcozl7oP0bheQ8EHh+kfMBZJgyFISix8t4p/nqDcHLmBgIiJ3T5XjYnJRorYDjIWaCfhb5fw=="],
|
||||
"bits-ui": ["bits-ui@1.3.19", "", { "dependencies": { "@floating-ui/core": "^1.6.4", "@floating-ui/dom": "^1.6.7", "@internationalized/date": "^3.5.6", "esm-env": "^1.1.2", "runed": "^0.23.2", "svelte-toolbelt": "^0.7.1", "tabbable": "^6.2.0" }, "peerDependencies": { "svelte": "^5.11.0" } }, "sha512-2blb6dkgedHUsDXqCjvmtUi4Advgd9MhaJDT8r7bEWDzHI8HGsOoYsLeh8CxpEWWEYPrlGN+7k+kpxRhIDdFrQ=="],
|
||||
|
||||
"bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
|
||||
|
||||
@ -421,8 +423,6 @@
|
||||
|
||||
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="],
|
||||
|
||||
"devalue": ["devalue@5.1.1", "", {}, "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw=="],
|
||||
@ -435,7 +435,7 @@
|
||||
|
||||
"drange": ["drange@1.1.1", "", {}, "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA=="],
|
||||
|
||||
"drizzle-kit": ["drizzle-kit@0.30.5", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0", "gel": "^2.0.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-l6dMSE100u7sDaTbLczibrQZjA35jLsHNqIV+jmhNVO3O8jzM6kywMOmV9uOz9ZVSCMPQhAZEFjL/qDPVrqpUA=="],
|
||||
"drizzle-kit": ["drizzle-kit@0.30.6", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0", "gel": "^2.0.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g=="],
|
||||
|
||||
"drizzle-orm": ["drizzle-orm@0.38.4", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/react": ">=18", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "react": ">=18", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/react", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "knex", "kysely", "mysql2", "pg", "postgres", "react", "sql.js", "sqlite3"] }, "sha512-s7/5BpLKO+WJRHspvpqTydxFob8i1vo2rEx4pY6TGY7QSMuUfWUuzaY0DIpXCkgHOo37BaFC+SJQb99dDUXT3Q=="],
|
||||
|
||||
@ -455,7 +455,7 @@
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@9.22.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.2", "@eslint/config-helpers": "^0.1.0", "@eslint/core": "^0.12.0", "@eslint/eslintrc": "^3.3.0", "@eslint/js": "9.22.0", "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "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.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ=="],
|
||||
"eslint": ["eslint@9.25.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.20.0", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.13.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.25.1", "@eslint/plugin-kit": "^0.2.8", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "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.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ=="],
|
||||
|
||||
"eslint-compat-utils": ["eslint-compat-utils@0.5.1", "", { "dependencies": { "semver": "^7.5.4" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q=="],
|
||||
|
||||
@ -475,7 +475,7 @@
|
||||
|
||||
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
|
||||
|
||||
"esrap": ["esrap@1.4.5", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-CjNMjkBWWZeHn+VX+gS8YvFwJ5+NDhg8aWZBSFJPR8qQduDNjbJodA2WcwCm7uQa5Rjqj+nZvVmceg1RbHFB9g=="],
|
||||
"esrap": ["esrap@1.4.6", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw=="],
|
||||
|
||||
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
|
||||
|
||||
@ -515,8 +515,6 @@
|
||||
|
||||
"flatted": ["flatted@3.3.2", "", {}, "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA=="],
|
||||
|
||||
"focus-trap": ["focus-trap@7.6.2", "", { "dependencies": { "tabbable": "^6.2.0" } }, "sha512-9FhUxK1hVju2+AiQIDJ5Dd//9R2n2RAfJ0qfhF4IHGHgcoEUTMpbTeG/zbEuwaiYXfuAH6XE0/aCyxDdRM+W5w=="],
|
||||
|
||||
"foreground-child": ["foreground-child@3.3.0", "", { "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" } }, "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg=="],
|
||||
|
||||
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
|
||||
@ -563,6 +561,8 @@
|
||||
|
||||
"ini": ["ini@4.1.3", "", {}, "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg=="],
|
||||
|
||||
"inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="],
|
||||
|
||||
"inquirer": ["inquirer@8.2.6", "", { "dependencies": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", "cli-cursor": "^3.1.0", "cli-width": "^3.0.0", "external-editor": "^3.0.3", "figures": "^3.0.0", "lodash": "^4.17.21", "mute-stream": "0.0.8", "ora": "^5.4.1", "run-async": "^2.4.0", "rxjs": "^7.5.5", "string-width": "^4.1.0", "strip-ansi": "^6.0.0", "through": "^2.3.6", "wrap-ansi": "^6.0.1" } }, "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg=="],
|
||||
|
||||
"ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="],
|
||||
@ -635,8 +635,6 @@
|
||||
|
||||
"lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"lucide-svelte": ["lucide-svelte@0.469.0", "", { "peerDependencies": { "svelte": "^3 || ^4 || ^5.0.0-next.42" } }, "sha512-PMIJ8jrFqVUsXJz4d1yfAQplaGhNOahwwkzbunha8DhpiD73xqX24n8dE1dPpUk3vcrdWVsHc1y/liHHotOnGQ=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
|
||||
|
||||
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
||||
@ -665,7 +663,7 @@
|
||||
|
||||
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
|
||||
|
||||
"nanoid": ["nanoid@5.0.9", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q=="],
|
||||
"nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
|
||||
@ -787,6 +785,8 @@
|
||||
|
||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||
|
||||
"runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="],
|
||||
|
||||
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
|
||||
|
||||
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
|
||||
@ -827,18 +827,22 @@
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"style-to-object": ["style-to-object@1.0.8", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g=="],
|
||||
|
||||
"sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"svelte": ["svelte@5.23.0", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.3", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-v0lL3NuKontiCxholEiAXCB+BYbndlKbwlDMK0DS86WgGELMJSpyqCSbJeMEMBDwOglnS7Ar2Rq0wwa/z2L8Vg=="],
|
||||
"svelte": ["svelte@5.28.1", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.6", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-iOa9WmfNG95lSOSJdMhdjJ4Afok7IRAQYXpbnxhd5EINnXseG0GVa9j6WPght4eX78XfFez45Fi+uRglGKPV/Q=="],
|
||||
|
||||
"svelte-check": ["svelte-check@4.1.5", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-Gb0T2IqBNe1tLB9EB1Qh+LOe+JB8wt2/rNBDGvkxQVvk8vNeAoG+vZgFB/3P5+zC7RWlyBlzm9dVjZFph/maIg=="],
|
||||
"svelte-check": ["svelte-check@4.1.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-P7w/6tdSfk3zEVvfsgrp3h3DFC75jCdZjTQvgGJtjPORs1n7/v2VMPIoty3PWv7jnfEm3x0G/p9wH4pecTb0Wg=="],
|
||||
|
||||
"svelte-eslint-parser": ["svelte-eslint-parser@0.43.0", "", { "dependencies": { "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "postcss": "^8.4.39", "postcss-scss": "^4.0.9" }, "peerDependencies": { "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA=="],
|
||||
|
||||
"svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
|
||||
|
||||
"tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="],
|
||||
|
||||
"tailwind-merge": ["tailwind-merge@2.6.0", "", {}, "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA=="],
|
||||
@ -855,6 +859,8 @@
|
||||
|
||||
"through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.13", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw=="],
|
||||
|
||||
"tmp": ["tmp@0.0.33", "", { "dependencies": { "os-tmpdir": "~1.0.2" } }, "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
@ -873,11 +879,11 @@
|
||||
|
||||
"type-fest": ["type-fest@4.37.0", "", {}, "sha512-S/5/0kFftkq27FPNye0XM1e2NsnoD/3FS+pBmbjmmtLT6I+i344KoOf7pvXreaFsDamWeaJX55nczA1m5PsBDg=="],
|
||||
|
||||
"typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
|
||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||
|
||||
"typescript-eslint": ["typescript-eslint@8.26.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.26.1", "@typescript-eslint/parser": "8.26.1", "@typescript-eslint/utils": "8.26.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-t/oIs9mYyrwZGRpDv3g+3K6nZ5uhKEMt2oNmAPwaY4/ye0+EH4nXIPYNtkYFS6QHm+1DFg34DbglYBz5P9Xysg=="],
|
||||
"typescript-eslint": ["typescript-eslint@8.31.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.31.0", "@typescript-eslint/parser": "8.31.0", "@typescript-eslint/utils": "8.31.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-u+93F0sB0An8WEAPtwxVhFby573E8ckdjwUUQUj9QA4v8JAvgtoDdIyYR3XFwFHq2W1KJ1AurwJCO+w+Y1ixyQ=="],
|
||||
|
||||
"typia": ["typia@8.0.2", "", { "dependencies": { "@samchon/openapi": "^3.0.0", "commander": "^10.0.0", "comment-json": "^4.2.3", "inquirer": "^8.2.5", "package-manager-detector": "^0.2.0", "randexp": "^0.5.3" }, "peerDependencies": { "typescript": ">=4.8.0 <5.9.0" }, "bin": { "typia": "lib/executable/typia.js" } }, "sha512-xDacELBticuXx17TtPWhSd1thyE2MF74LZhhDoCFkpCKhiElnB40X/Jb3zMLRx86WUkqCH60rwm3ghaJdBs7hA=="],
|
||||
"typia": ["typia@8.2.0", "", { "dependencies": { "@samchon/openapi": "^3.3.0", "commander": "^10.0.0", "comment-json": "^4.2.3", "inquirer": "^8.2.5", "package-manager-detector": "^0.2.0", "randexp": "^0.5.3" }, "peerDependencies": { "typescript": ">=4.8.0 <5.9.0" }, "bin": { "typia": "lib/executable/typia.js" } }, "sha512-DAQl9CLItDwMd4k4HnaExxv6wjBFLW04WqGBQEnOpylq2YmZsM6HTj4Bfxxp3wtgAsMXZTugAdQRPsFOxMiRrQ=="],
|
||||
|
||||
"ufo": ["ufo@1.5.4", "", {}, "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ=="],
|
||||
|
||||
@ -891,7 +897,7 @@
|
||||
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
|
||||
"vite": ["vite@6.2.2", "", { "dependencies": { "esbuild": "^0.25.0", "postcss": "^8.5.3", "rollup": "^4.30.1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ=="],
|
||||
"vite": ["vite@6.3.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.3", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.12" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg=="],
|
||||
|
||||
"vitefu": ["vitefu@1.0.4", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "optionalPeers": ["vite"] }, "sha512-y6zEE3PQf6uu/Mt6DTJ9ih+kyJLr4XcSgHR2zUkM8SWDhuixEJxfJ6CZGMHh1Ec3vPLoEA0IHU5oWzVqw8ulow=="],
|
||||
|
||||
@ -939,6 +945,8 @@
|
||||
|
||||
"@ryoppippi/unplugin-typia/typia": ["typia@7.6.4", "", { "dependencies": { "@samchon/openapi": "^2.4.2", "commander": "^10.0.0", "comment-json": "^4.2.3", "inquirer": "^8.2.5", "package-manager-detector": "^0.2.0", "randexp": "^0.5.3" }, "peerDependencies": { "typescript": ">=4.8.0 <5.8.0" }, "bin": { "typia": "lib/executable/typia.js" } }, "sha512-Z3AcvGBjS7Dc7+iAG2VhsT0NIv3HMoXVTJ3F7Pgth8z7rhQu1JnyS8GGqqWdKk5ROvOgALEVEWmWv3Lym2eBIg=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite": ["vite@6.2.2", "", { "dependencies": { "esbuild": "^0.25.0", "postcss": "^8.5.3", "rollup": "^4.30.1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
"ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
|
||||
@ -969,8 +977,6 @@
|
||||
|
||||
"pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"postcss/nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="],
|
||||
|
||||
"postcss-load-config/lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="],
|
||||
|
||||
"postcss-nested/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
|
||||
@ -997,7 +1003,11 @@
|
||||
|
||||
"tailwindcss/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
|
||||
|
||||
"vite/rollup": ["rollup@4.34.8", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.34.8", "@rollup/rollup-android-arm64": "4.34.8", "@rollup/rollup-darwin-arm64": "4.34.8", "@rollup/rollup-darwin-x64": "4.34.8", "@rollup/rollup-freebsd-arm64": "4.34.8", "@rollup/rollup-freebsd-x64": "4.34.8", "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", "@rollup/rollup-linux-arm-musleabihf": "4.34.8", "@rollup/rollup-linux-arm64-gnu": "4.34.8", "@rollup/rollup-linux-arm64-musl": "4.34.8", "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", "@rollup/rollup-linux-riscv64-gnu": "4.34.8", "@rollup/rollup-linux-s390x-gnu": "4.34.8", "@rollup/rollup-linux-x64-gnu": "4.34.8", "@rollup/rollup-linux-x64-musl": "4.34.8", "@rollup/rollup-win32-arm64-msvc": "4.34.8", "@rollup/rollup-win32-ia32-msvc": "4.34.8", "@rollup/rollup-win32-x64-msvc": "4.34.8", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ=="],
|
||||
"tinyglobby/fdir": ["fdir@6.4.4", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="],
|
||||
|
||||
"vite/fdir": ["fdir@6.4.4", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="],
|
||||
|
||||
"vite/rollup": ["rollup@4.40.0", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.40.0", "@rollup/rollup-android-arm64": "4.40.0", "@rollup/rollup-darwin-arm64": "4.40.0", "@rollup/rollup-darwin-x64": "4.40.0", "@rollup/rollup-freebsd-arm64": "4.40.0", "@rollup/rollup-freebsd-x64": "4.40.0", "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", "@rollup/rollup-linux-arm-musleabihf": "4.40.0", "@rollup/rollup-linux-arm64-gnu": "4.40.0", "@rollup/rollup-linux-arm64-musl": "4.40.0", "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", "@rollup/rollup-linux-riscv64-gnu": "4.40.0", "@rollup/rollup-linux-riscv64-musl": "4.40.0", "@rollup/rollup-linux-s390x-gnu": "4.40.0", "@rollup/rollup-linux-x64-gnu": "4.40.0", "@rollup/rollup-linux-x64-musl": "4.40.0", "@rollup/rollup-win32-arm64-msvc": "4.40.0", "@rollup/rollup-win32-ia32-msvc": "4.40.0", "@rollup/rollup-win32-x64-msvc": "4.40.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
||||
|
||||
@ -1051,6 +1061,8 @@
|
||||
|
||||
"@ryoppippi/unplugin-typia/typia/@samchon/openapi": ["@samchon/openapi@2.5.3", "", {}, "sha512-gLcGqPGsr/V43yNrhC+5E8ZR8y1JdLZDgkC6ULg+B03uvmdS2C7qIXpIWUe19MUPvXt5SCpjhvOykomEspinaw=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup": ["rollup@4.34.8", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.34.8", "@rollup/rollup-android-arm64": "4.34.8", "@rollup/rollup-darwin-arm64": "4.34.8", "@rollup/rollup-darwin-x64": "4.34.8", "@rollup/rollup-freebsd-arm64": "4.34.8", "@rollup/rollup-freebsd-x64": "4.34.8", "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", "@rollup/rollup-linux-arm-musleabihf": "4.34.8", "@rollup/rollup-linux-arm64-gnu": "4.34.8", "@rollup/rollup-linux-arm64-musl": "4.34.8", "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", "@rollup/rollup-linux-riscv64-gnu": "4.34.8", "@rollup/rollup-linux-s390x-gnu": "4.34.8", "@rollup/rollup-linux-x64-gnu": "4.34.8", "@rollup/rollup-linux-x64-musl": "4.34.8", "@rollup/rollup-win32-arm64-msvc": "4.34.8", "@rollup/rollup-win32-ia32-msvc": "4.34.8", "@rollup/rollup-win32-x64-msvc": "4.34.8", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
||||
|
||||
"drizzle-kit/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="],
|
||||
@ -1099,8 +1111,6 @@
|
||||
|
||||
"drizzle-kit/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="],
|
||||
|
||||
"eslint-plugin-svelte/postcss/nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="],
|
||||
|
||||
"gel/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
|
||||
|
||||
"glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
||||
@ -1111,53 +1121,89 @@
|
||||
|
||||
"pkg-dir/find-up/path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="],
|
||||
|
||||
"svelte-eslint-parser/postcss/nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="],
|
||||
|
||||
"tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
"tailwindcss/postcss/nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="],
|
||||
|
||||
"tailwindcss/postcss-load-config/yaml": ["yaml@2.6.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.34.8", "", { "os": "android", "cpu": "arm" }, "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw=="],
|
||||
"vite/rollup/@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.40.0", "", { "os": "android", "cpu": "arm" }, "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.34.8", "", { "os": "android", "cpu": "arm64" }, "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q=="],
|
||||
"vite/rollup/@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.40.0", "", { "os": "android", "cpu": "arm64" }, "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.34.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q=="],
|
||||
"vite/rollup/@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.40.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.34.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw=="],
|
||||
"vite/rollup/@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.40.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.34.8", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA=="],
|
||||
"vite/rollup/@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.40.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.34.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q=="],
|
||||
"vite/rollup/@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.40.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.34.8", "", { "os": "linux", "cpu": "arm" }, "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g=="],
|
||||
"vite/rollup/@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.40.0", "", { "os": "linux", "cpu": "arm" }, "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.34.8", "", { "os": "linux", "cpu": "arm" }, "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA=="],
|
||||
"vite/rollup/@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.40.0", "", { "os": "linux", "cpu": "arm" }, "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.34.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A=="],
|
||||
"vite/rollup/@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.40.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.34.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q=="],
|
||||
"vite/rollup/@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.40.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.34.8", "", { "os": "linux", "cpu": "none" }, "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ=="],
|
||||
"vite/rollup/@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.40.0", "", { "os": "linux", "cpu": "none" }, "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.34.8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw=="],
|
||||
"vite/rollup/@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.40.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.34.8", "", { "os": "linux", "cpu": "none" }, "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw=="],
|
||||
"vite/rollup/@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.40.0", "", { "os": "linux", "cpu": "none" }, "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.34.8", "", { "os": "linux", "cpu": "s390x" }, "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA=="],
|
||||
"vite/rollup/@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.40.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.34.8", "", { "os": "linux", "cpu": "x64" }, "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA=="],
|
||||
"vite/rollup/@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.40.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.34.8", "", { "os": "linux", "cpu": "x64" }, "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ=="],
|
||||
"vite/rollup/@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.40.0", "", { "os": "linux", "cpu": "x64" }, "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.34.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ=="],
|
||||
"vite/rollup/@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.40.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.34.8", "", { "os": "win32", "cpu": "ia32" }, "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w=="],
|
||||
"vite/rollup/@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.40.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA=="],
|
||||
|
||||
"vite/rollup/@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.34.8", "", { "os": "win32", "cpu": "x64" }, "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g=="],
|
||||
"vite/rollup/@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.40.0", "", { "os": "win32", "cpu": "x64" }, "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ=="],
|
||||
|
||||
"vite/rollup/@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.34.8", "", { "os": "android", "cpu": "arm" }, "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.34.8", "", { "os": "android", "cpu": "arm64" }, "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.34.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.34.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.34.8", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.34.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.34.8", "", { "os": "linux", "cpu": "arm" }, "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.34.8", "", { "os": "linux", "cpu": "arm" }, "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.34.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.34.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.34.8", "", { "os": "linux", "cpu": "none" }, "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.34.8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.34.8", "", { "os": "linux", "cpu": "none" }, "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.34.8", "", { "os": "linux", "cpu": "s390x" }, "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.34.8", "", { "os": "linux", "cpu": "x64" }, "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.34.8", "", { "os": "linux", "cpu": "x64" }, "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.34.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.34.8", "", { "os": "win32", "cpu": "ia32" }, "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w=="],
|
||||
|
||||
"@ryoppippi/unplugin-typia/vite/rollup/@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.34.8", "", { "os": "win32", "cpu": "x64" }, "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g=="],
|
||||
|
||||
"pkg-dir/find-up/locate-path/p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="],
|
||||
|
||||
|
1
drizzle/0002_minor_black_panther.sql
Normal file
1
drizzle/0002_minor_black_panther.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE `devices` DROP COLUMN `opnsense_id`;
|
222
drizzle/meta/0002_snapshot.json
Normal file
222
drizzle/meta/0002_snapshot.json
Normal file
@ -0,0 +1,222 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "0b364191-58d0-46a3-8372-4a30b0b88d85",
|
||||
"prevId": "cc1fa973-1e9c-4bd6-b082-d7cf36f7342c",
|
||||
"tables": {
|
||||
"devices": {
|
||||
"name": "devices",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"public_key": {
|
||||
"name": "public_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"private_key": {
|
||||
"name": "private_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"pre_shared_key": {
|
||||
"name": "pre_shared_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"devices_public_key_unique": {
|
||||
"name": "devices_public_key_unique",
|
||||
"columns": [
|
||||
"public_key"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"devices_user_id_users_id_fk": {
|
||||
"name": "devices_user_id_users_id_fk",
|
||||
"tableFrom": "devices",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"ip_allocations": {
|
||||
"name": "ip_allocations",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"device_id": {
|
||||
"name": "device_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"ip_allocations_device_id_unique": {
|
||||
"name": "ip_allocations_device_id_unique",
|
||||
"columns": [
|
||||
"device_id"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"ip_allocations_device_id_devices_id_fk": {
|
||||
"name": "ip_allocations_device_id_devices_id_fk",
|
||||
"tableFrom": "ip_allocations",
|
||||
"tableTo": "devices",
|
||||
"columnsFrom": [
|
||||
"device_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"sessions": {
|
||||
"name": "sessions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"sessions_user_id_users_id_fk": {
|
||||
"name": "sessions_user_id_users_id_fk",
|
||||
"tableFrom": "sessions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"users": {
|
||||
"name": "users",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"auth_source": {
|
||||
"name": "auth_source",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'authentik'"
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
@ -15,6 +15,13 @@
|
||||
"when": 1741936760967,
|
||||
"tag": "0001_equal_unicorn",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "6",
|
||||
"when": 1743389268079,
|
||||
"tag": "0002_minor_black_panther",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
25
package.json
25
package.json
@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "vpgen",
|
||||
"version": "0.0.1",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@ -18,48 +19,48 @@
|
||||
"prepare": "ts-patch install"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lucide/svelte": "^0.487.0",
|
||||
"@oslojs/crypto": "^1.0.1",
|
||||
"@oslojs/encoding": "^1.1.0",
|
||||
"@ryoppippi/unplugin-typia": "npm:@jsr/ryoppippi__unplugin-typia",
|
||||
"@sveltejs/adapter-auto": "^3.3.1",
|
||||
"@sveltejs/adapter-node": "^5.2.12",
|
||||
"@sveltejs/kit": "^2.19.1",
|
||||
"@sveltejs/kit": "^2.20.7",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/better-sqlite3": "^7.6.12",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"@types/qrcode-svg": "^1.1.5",
|
||||
"arctic": "^2.3.4",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"bits-ui": "^0.22.0",
|
||||
"bits-ui": "^1.3.19",
|
||||
"clsx": "^2.1.1",
|
||||
"eslint": "^9.22.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.46.1",
|
||||
"globals": "^15.15.0",
|
||||
"ip-address": "^10.0.1",
|
||||
"lucide-svelte": "^0.469.0",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"qrcode-svg": "^1.1.0",
|
||||
"svelte": "^5.23.0",
|
||||
"svelte-check": "^4.1.5",
|
||||
"svelte": "^5.28.1",
|
||||
"svelte-check": "^4.1.6",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwind-variants": "^0.3.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"ts-patch": "^3.3.0",
|
||||
"typescript": "~5.8.2",
|
||||
"typescript-eslint": "^8.26.1",
|
||||
"typia": "^8.0.2",
|
||||
"vite": "^6.2.2"
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.31.0",
|
||||
"typia": "^8.2.0",
|
||||
"vite": "^6.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@libsql/client": "^0.14.0",
|
||||
"drizzle-kit": "^0.30.5",
|
||||
"drizzle-kit": "^0.30.6",
|
||||
"drizzle-orm": "^0.38.4"
|
||||
}
|
||||
}
|
@ -2,10 +2,9 @@ import { type Handle, redirect } from '@sveltejs/kit';
|
||||
import { sequence } from '@sveltejs/kit/hooks';
|
||||
import { dev } from '$app/environment';
|
||||
import * as auth from '$lib/server/auth';
|
||||
import { fetchOpnsenseServer } from '$lib/server/opnsense';
|
||||
import wgProvider from '$lib/server/wg-provider';
|
||||
|
||||
// fetch opnsense server info on startup
|
||||
await fetchOpnsenseServer();
|
||||
await wgProvider.init();
|
||||
|
||||
const handleAuth: Handle = async ({ event, resolve }) => {
|
||||
const sessionId = event.cookies.get(auth.sessionCookieName);
|
||||
|
9
src/lib/auth.ts
Normal file
9
src/lib/auth.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { envToBool } from '$lib/utils';
|
||||
import { env } from '$env/dynamic/public';
|
||||
|
||||
export type AuthProvider = 'authentik' | 'google';
|
||||
|
||||
export const enabledAuthProviders: Record<AuthProvider, boolean> = {
|
||||
authentik: envToBool(env.PUBLIC_AUTH_AUTHENTIK_ENABLE),
|
||||
google: envToBool(env.PUBLIC_AUTH_GOOGLE_ENABLE),
|
||||
};
|
28
src/lib/components/app/auth-form/auth-button.svelte
Normal file
28
src/lib/components/app/auth-form/auth-button.svelte
Normal file
@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { LucideLoaderCircle } from '@lucide/svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
|
||||
interface Props {
|
||||
providerName: string;
|
||||
displayName: string;
|
||||
iconSrc: string;
|
||||
inviteToken?: string;
|
||||
}
|
||||
let { providerName, displayName, inviteToken, iconSrc }: Props = $props();
|
||||
|
||||
let submitted = $state(false);
|
||||
</script>
|
||||
|
||||
<form method="get" onsubmit={() => (submitted = true)} action="/auth/{providerName}">
|
||||
{#if inviteToken}
|
||||
<input type="hidden" value={inviteToken} name="invite" />
|
||||
{/if}
|
||||
<Button type="submit" disabled={submitted}>
|
||||
{#if submitted}
|
||||
<LucideLoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<img class="mr-2 h-4 w-4" alt="{displayName} Logo" src={iconSrc} />
|
||||
{/if}
|
||||
Sign {inviteToken ? 'up' : 'in'} with {displayName}
|
||||
</Button>
|
||||
</form>
|
@ -1,53 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { LucideLoaderCircle } from 'lucide-svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import googleIcon from '$lib/assets/google.svg';
|
||||
import { enabledAuthProviders } from '$lib/auth';
|
||||
import AuthButton from './auth-button.svelte';
|
||||
|
||||
let { inviteToken, class: className, ...rest }: { inviteToken?: string; class?: string; rest?: { [p: string]: unknown } } = $props();
|
||||
|
||||
let isLoading = $state(false);
|
||||
interface Props {
|
||||
inviteToken?: string;
|
||||
class?: string;
|
||||
}
|
||||
let { inviteToken, class: className }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class={cn('flex gap-6', className)} {...rest}>
|
||||
<form method="get" action="/auth/authentik{inviteToken ? `?invite=${inviteToken}` : ''}">
|
||||
<input type="hidden" value={inviteToken} name="invite" />
|
||||
<Button
|
||||
type="submit"
|
||||
onclick={() => {
|
||||
isLoading = true;
|
||||
}}
|
||||
>
|
||||
{#if isLoading}
|
||||
<LucideLoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<img
|
||||
class="mr-2 h-4 w-4"
|
||||
alt="Authentik Logo"
|
||||
src="https://auth.cazzzer.com/static/dist/assets/icons/icon.svg"
|
||||
<div class={cn('flex gap-6', className)}>
|
||||
{#if enabledAuthProviders.authentik}
|
||||
<AuthButton
|
||||
providerName="authentik"
|
||||
displayName="Authentik"
|
||||
iconSrc="https://auth.cazzzer.com/static/dist/assets/icons/icon.svg"
|
||||
{inviteToken}
|
||||
/>
|
||||
{/if}
|
||||
Sign in with Authentik
|
||||
</Button>
|
||||
</form>
|
||||
<form method="get" action="/auth/google">
|
||||
<input type="hidden" value={inviteToken} name="invite" />
|
||||
<Button
|
||||
type="submit"
|
||||
onclick={() => {
|
||||
isLoading = true;
|
||||
}}
|
||||
>
|
||||
{#if isLoading}
|
||||
<LucideLoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<img
|
||||
class="mr-2 h-4 w-4"
|
||||
alt="Google Logo"
|
||||
src={googleIcon}
|
||||
/>
|
||||
{#if enabledAuthProviders.google}
|
||||
<AuthButton providerName="google" displayName="Google" iconSrc={googleIcon} {inviteToken} />
|
||||
{/if}
|
||||
Sign in with Google
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { LucideClipboardCopy, LucideDownload } from 'lucide-svelte';
|
||||
import { LucideClipboardCopy, LucideDownload } from '@lucide/svelte';
|
||||
|
||||
const {
|
||||
data,
|
||||
|
@ -43,7 +43,7 @@
|
||||
this={href ? "a" : "span"}
|
||||
bind:this={ref}
|
||||
{href}
|
||||
class={cn(badgeVariants({ variant, className }))}
|
||||
class={cn(badgeVariants({ variant }), className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
@ -56,7 +56,7 @@
|
||||
{#if href}
|
||||
<a
|
||||
bind:this={ref}
|
||||
class={cn(buttonVariants({ variant, size, className }))}
|
||||
class={cn(buttonVariants({ variant, size }), className)}
|
||||
{href}
|
||||
{...restProps}
|
||||
>
|
||||
@ -65,7 +65,7 @@
|
||||
{:else}
|
||||
<button
|
||||
bind:this={ref}
|
||||
class={cn(buttonVariants({ variant, size, className }))}
|
||||
class={cn(buttonVariants({ variant, size }), className)}
|
||||
{type}
|
||||
{...restProps}
|
||||
>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Checkbox as CheckboxPrimitive, type WithoutChildrenOrChild } from "bits-ui";
|
||||
import Check from "lucide-svelte/icons/check";
|
||||
import Minus from "lucide-svelte/icons/minus";
|
||||
import Check from "@lucide/svelte/icons/check";
|
||||
import Minus from "@lucide/svelte/icons/minus";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive, type WithoutChildrenOrChild } from "bits-ui";
|
||||
import X from "lucide-svelte/icons/x";
|
||||
import X from "@lucide/svelte/icons/x";
|
||||
import type { Snippet } from "svelte";
|
||||
import * as Dialog from "./index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
@ -17,7 +17,7 @@
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<Dialog.Portal class="absolute" {...portalProps}>
|
||||
<Dialog.Portal {...portalProps}>
|
||||
<Dialog.Overlay />
|
||||
<DialogPrimitive.Content
|
||||
bind:ref
|
||||
|
@ -5,7 +5,6 @@
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: DialogPrimitive.DescriptionProps = $props();
|
||||
</script>
|
||||
@ -14,6 +13,4 @@
|
||||
bind:ref
|
||||
class={cn("text-muted-foreground text-sm", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</DialogPrimitive.Description>
|
||||
/>
|
||||
|
@ -5,7 +5,6 @@
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: DialogPrimitive.OverlayProps = $props();
|
||||
</script>
|
||||
@ -17,6 +16,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</DialogPrimitive.Overlay>
|
||||
/>
|
||||
|
@ -5,7 +5,6 @@
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: DialogPrimitive.TitleProps = $props();
|
||||
</script>
|
||||
@ -14,6 +13,4 @@
|
||||
bind:ref
|
||||
class={cn("text-lg font-semibold leading-none tracking-tight", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</DialogPrimitive.Title>
|
||||
/>
|
||||
|
@ -1,22 +1,46 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLInputAttributes } from "svelte/elements";
|
||||
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from "svelte/elements";
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
type InputType = Exclude<HTMLInputTypeAttribute, "file">;
|
||||
|
||||
type Props = WithElementRef<
|
||||
Omit<HTMLInputAttributes, "type"> &
|
||||
({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined })
|
||||
>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
type,
|
||||
files = $bindable(),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLInputAttributes> = $props();
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<input
|
||||
{#if type === "file"}
|
||||
<input
|
||||
bind:this={ref}
|
||||
class={cn(
|
||||
"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-base file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
className
|
||||
)}
|
||||
type="file"
|
||||
bind:files
|
||||
bind:value
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
{:else}
|
||||
<input
|
||||
bind:this={ref}
|
||||
class={cn(
|
||||
"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-base file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
className
|
||||
)}
|
||||
{type}
|
||||
bind:value
|
||||
{...restProps}
|
||||
/>
|
||||
{/if}
|
||||
|
@ -5,7 +5,6 @@
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: LabelPrimitive.RootProps = $props();
|
||||
</script>
|
||||
@ -17,6 +16,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</LabelPrimitive.Root>
|
||||
/>
|
||||
|
@ -5,7 +5,6 @@
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: TabsPrimitive.ContentProps = $props();
|
||||
</script>
|
||||
@ -17,6 +16,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</TabsPrimitive.Content>
|
||||
/>
|
||||
|
@ -5,7 +5,6 @@
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: TabsPrimitive.ListProps = $props();
|
||||
</script>
|
||||
@ -17,6 +16,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</TabsPrimitive.List>
|
||||
/>
|
||||
|
@ -5,7 +5,6 @@
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: TabsPrimitive.TriggerProps = $props();
|
||||
</script>
|
||||
@ -17,6 +16,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</TabsPrimitive.Trigger>
|
||||
/>
|
||||
|
@ -1,3 +0,0 @@
|
||||
export function opnsenseSanitezedUsername(username: string) {
|
||||
return username.slice(0, 63).replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||
}
|
@ -3,7 +3,7 @@ import { sha256 } from '@oslojs/crypto/sha2';
|
||||
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding';
|
||||
import { db } from '$lib/server/db';
|
||||
import * as table from '$lib/server/db/schema';
|
||||
import type { RequestEvent } from '@sveltejs/kit';
|
||||
import type { Cookies } from '@sveltejs/kit';
|
||||
import { dev } from '$app/environment';
|
||||
import { env } from '$env/dynamic/private';
|
||||
|
||||
@ -22,14 +22,14 @@ export async function createSession(userId: string): Promise<table.Session> {
|
||||
const session: table.Session = {
|
||||
id: sessionId,
|
||||
userId,
|
||||
expiresAt: new Date(Date.now() + DAY_IN_MS * 30)
|
||||
expiresAt: new Date(Date.now() + DAY_IN_MS * 30),
|
||||
};
|
||||
await db.insert(table.sessions).values(session);
|
||||
return session;
|
||||
}
|
||||
|
||||
export function setSessionTokenCookie(event: RequestEvent, sessionId: string, expiresAt: Date) {
|
||||
event.cookies.set(sessionCookieName, sessionId, {
|
||||
export function setSessionTokenCookie(cookies: Cookies, sessionId: string, expiresAt: Date) {
|
||||
cookies.set(sessionCookieName, sessionId, {
|
||||
path: '/',
|
||||
sameSite: 'lax',
|
||||
httpOnly: true,
|
||||
@ -42,16 +42,21 @@ export async function invalidateSession(sessionId: string): Promise<void> {
|
||||
await db.delete(table.sessions).where(eq(table.sessions.id, sessionId));
|
||||
}
|
||||
|
||||
export function deleteSessionTokenCookie(event: RequestEvent) {
|
||||
event.cookies.delete(sessionCookieName, { path: '/' });
|
||||
export function deleteSessionTokenCookie(cookies: Cookies) {
|
||||
cookies.delete(sessionCookieName, { path: '/' });
|
||||
}
|
||||
|
||||
export async function validateSession(sessionId: string) {
|
||||
const [result] = await db
|
||||
.select({
|
||||
// Adjust user table here to tweak returned data
|
||||
user: { id: table.users.id, username: table.users.username, name: table.users.name },
|
||||
session: table.sessions
|
||||
user: {
|
||||
id: table.users.id,
|
||||
authSource: table.users.authSource,
|
||||
username: table.users.username,
|
||||
name: table.users.name,
|
||||
},
|
||||
session: table.sessions,
|
||||
})
|
||||
.from(table.sessions)
|
||||
.innerJoin(table.users, eq(table.sessions.userId, table.users.id))
|
||||
@ -81,7 +86,7 @@ export async function validateSession(sessionId: string) {
|
||||
}
|
||||
|
||||
export function isValidInviteToken(inviteToken: string) {
|
||||
return inviteToken === env.INVITE_TOKEN;
|
||||
return inviteToken === env.AUTH_INVITE_TOKEN;
|
||||
}
|
||||
|
||||
export type SessionValidationResult = Awaited<ReturnType<typeof validateSession>>;
|
||||
|
@ -36,8 +36,6 @@ export const devices = sqliteTable('devices', {
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
name: text('name').notNull(),
|
||||
// questioning whether this should be nullable
|
||||
opnsenseId: text('opnsense_id'),
|
||||
publicKey: text('public_key').notNull().unique(),
|
||||
// nullable for the possibility of a user supplying their own private key
|
||||
privateKey: text('private_key'),
|
||||
|
@ -3,9 +3,8 @@ import { err, ok, type Result } from '$lib/types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { count, eq, isNull } from 'drizzle-orm';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { opnsenseAuth, opnsenseUrl, serverUuid } from '$lib/server/opnsense';
|
||||
import { opnsenseSanitezedUsername } from '$lib/opnsense';
|
||||
import { getIpsFromIndex } from './utils';
|
||||
import wgProvider from '$lib/server/wg-provider';
|
||||
|
||||
export async function createDevice(params: {
|
||||
name: string;
|
||||
@ -19,17 +18,15 @@ export async function createDevice(params: {
|
||||
if (deviceCount >= parseInt(env.MAX_CLIENTS_PER_USER))
|
||||
return err([400, 'Maximum number of devices reached'] as [400, string]);
|
||||
|
||||
// this is going to be quite long
|
||||
// 1. fetch params for new device from opnsense api
|
||||
// 1. fetch params for new device from provider
|
||||
// 2.1 get an allocation for the device
|
||||
// 2.2. insert new device into db
|
||||
// 2.3. update the allocation with the device id
|
||||
// 3. create the client in opnsense
|
||||
// 4. reconfigure opnsense to enable the new client
|
||||
// 3. create the client in provider
|
||||
return await db.transaction(async (tx) => {
|
||||
const [keys, availableAllocation, lastAllocation] = await Promise.all([
|
||||
// fetch params for new device from opnsense api
|
||||
getKeys(),
|
||||
const [keysResult, availableAllocation, lastAllocation] = await Promise.all([
|
||||
// fetch params for new device from provider
|
||||
wgProvider.generateKeys(),
|
||||
// find first unallocated IP
|
||||
await tx.query.ipAllocations.findFirst({
|
||||
columns: {
|
||||
@ -46,9 +43,12 @@ export async function createDevice(params: {
|
||||
}),
|
||||
]);
|
||||
|
||||
if (keysResult?._tag === 'err') return err([500, 'Failed to get keys']);
|
||||
const keys = keysResult.value;
|
||||
|
||||
// check for existing allocation or if we have any IPs left
|
||||
if (!availableAllocation && lastAllocation && lastAllocation.id >= parseInt(env.IP_MAX_INDEX)) {
|
||||
return err([500, 'No more IP addresses available'] as [500, string]);
|
||||
return err([500, 'No more IP addresses available']);
|
||||
}
|
||||
|
||||
// use existing allocation or create a new one
|
||||
@ -65,9 +65,9 @@ export async function createDevice(params: {
|
||||
.values({
|
||||
userId: params.user.id,
|
||||
name: params.name,
|
||||
publicKey: keys.pubkey,
|
||||
privateKey: keys.privkey,
|
||||
preSharedKey: keys.psk,
|
||||
publicKey: keys.publicKey,
|
||||
privateKey: keys.privateKey,
|
||||
preSharedKey: keys.preSharedKey,
|
||||
})
|
||||
.returning({ id: devices.id });
|
||||
|
||||
@ -77,80 +77,18 @@ export async function createDevice(params: {
|
||||
.set({ deviceId: newDevice.id })
|
||||
.where(eq(ipAllocations.id, ipAllocationId));
|
||||
|
||||
// create client in opnsense
|
||||
const opnsenseRes = await opnsenseCreateClient({
|
||||
username: params.user.username,
|
||||
pubkey: keys.pubkey,
|
||||
psk: keys.psk,
|
||||
// create client in provider
|
||||
const providerRes = await wgProvider.createClient({
|
||||
user: params.user,
|
||||
publicKey: keys.publicKey,
|
||||
preSharedKey: keys.preSharedKey,
|
||||
allowedIps: getIpsFromIndex(ipAllocationId).join(','),
|
||||
});
|
||||
const opnsenseResJson = await opnsenseRes.json();
|
||||
if (opnsenseResJson['result'] !== 'saved') {
|
||||
if (providerRes._tag === 'err') {
|
||||
tx2.rollback();
|
||||
console.error(`Error creating client in OPNsense: \n${opnsenseResJson}`);
|
||||
return err([500, 'Error creating client in OPNsense'] as [500, string]);
|
||||
return err([500, 'Failed to create client in provider']);
|
||||
}
|
||||
|
||||
// reconfigure opnsense
|
||||
await opnsenseReconfigure();
|
||||
return ok(newDevice.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function getKeys() {
|
||||
// fetch key pair from opnsense
|
||||
const options: RequestInit = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: opnsenseAuth,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
};
|
||||
const resKeyPair = await fetch(`${opnsenseUrl}/api/wireguard/server/key_pair`, options);
|
||||
const resPsk = await fetch(`${opnsenseUrl}/api/wireguard/client/psk`, options);
|
||||
const keyPair = await resKeyPair.json();
|
||||
const psk = await resPsk.json();
|
||||
return {
|
||||
pubkey: keyPair['pubkey'] as string,
|
||||
privkey: keyPair['privkey'] as string,
|
||||
psk: psk['psk'] as string,
|
||||
};
|
||||
}
|
||||
|
||||
async function opnsenseCreateClient(params: {
|
||||
username: string;
|
||||
pubkey: string;
|
||||
psk: string;
|
||||
allowedIps: string;
|
||||
}) {
|
||||
return fetch(`${opnsenseUrl}/api/wireguard/client/addClientBuilder`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: opnsenseAuth,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
configbuilder: {
|
||||
enabled: '1',
|
||||
name: `vpgen-${opnsenseSanitezedUsername(params.username)}`,
|
||||
pubkey: params.pubkey,
|
||||
psk: params.psk,
|
||||
tunneladdress: params.allowedIps,
|
||||
server: serverUuid,
|
||||
endpoint: env.VPN_ENDPOINT,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
async function opnsenseReconfigure() {
|
||||
return fetch(`${opnsenseUrl}/api/wireguard/service/reconfigure`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: opnsenseAuth,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { and, eq } from 'drizzle-orm';
|
||||
import { db } from '$lib/server/db';
|
||||
import { devices } from '$lib/server/db/schema';
|
||||
import { err, ok, type Result } from '$lib/types';
|
||||
import { opnsenseAuth, opnsenseUrl } from '$lib/server/opnsense';
|
||||
import wgProvider from '$lib/server/wg-provider';
|
||||
|
||||
export async function deleteDevice(
|
||||
userId: string,
|
||||
@ -15,59 +15,13 @@ export async function deleteDevice(
|
||||
where: and(eq(devices.userId, userId), eq(devices.id, deviceId)),
|
||||
});
|
||||
if (!device) return err([400, 'Device not found']);
|
||||
const opnsenseClientUuid = (await opnsenseFindClient(device.publicKey))?.['uuid'];
|
||||
if (typeof opnsenseClientUuid !== 'string') {
|
||||
console.error(
|
||||
'failed to get OPNsense client for deletion for device',
|
||||
deviceId,
|
||||
device.publicKey,
|
||||
);
|
||||
return err([500, 'Error getting client from OPNsense']);
|
||||
}
|
||||
|
||||
const opnsenseDeletionResult = await opnsenseDeleteClient(opnsenseClientUuid);
|
||||
if (opnsenseDeletionResult?.['result'] !== 'deleted') {
|
||||
console.error(
|
||||
'failed to delete OPNsense client for device',
|
||||
deviceId,
|
||||
device.publicKey,
|
||||
'\n',
|
||||
'OPNsense client uuid:',
|
||||
opnsenseClientUuid,
|
||||
);
|
||||
return err([500, 'Error deleting client in OPNsense']);
|
||||
const providerDeletionResult = await wgProvider.deleteClient(device.publicKey);
|
||||
if (providerDeletionResult._tag === 'err') {
|
||||
console.error('failed to delete provider client for device', deviceId, device.publicKey);
|
||||
return err([500, 'Error deleting client in provider']);
|
||||
}
|
||||
|
||||
await db.delete(devices).where(eq(devices.id, deviceId));
|
||||
return ok(null);
|
||||
}
|
||||
|
||||
async function opnsenseFindClient(pubkey: string) {
|
||||
const res = await fetch(`${opnsenseUrl}/api/wireguard/client/searchClient`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: opnsenseAuth,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
current: 1,
|
||||
// "rowCount": 7,
|
||||
sort: {},
|
||||
searchPhrase: pubkey,
|
||||
type: ['peer'],
|
||||
}),
|
||||
});
|
||||
return (await res.json())?.rows?.[0] ?? null;
|
||||
}
|
||||
|
||||
async function opnsenseDeleteClient(clientUuid: string) {
|
||||
const res = await fetch(`${opnsenseUrl}/api/wireguard/client/delClient/${clientUuid}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: opnsenseAuth,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
return res.json();
|
||||
}
|
||||
|
@ -2,9 +2,9 @@ import { db } from '$lib/server/db';
|
||||
import { and, eq } from 'drizzle-orm';
|
||||
import { devices } from '$lib/server/db/schema';
|
||||
import type { DeviceDetails } from '$lib/devices';
|
||||
import { serverPublicKey } from '$lib/server/opnsense';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { getIpsFromIndex } from '$lib/server/devices/index';
|
||||
import wgProvider from '$lib/server/wg-provider';
|
||||
|
||||
export async function findDevices(userId: string) {
|
||||
return db.query.devices.findMany({
|
||||
@ -49,7 +49,7 @@ export function mapDeviceToDetails(
|
||||
privateKey: device.privateKey,
|
||||
preSharedKey: device.preSharedKey,
|
||||
ips,
|
||||
vpnPublicKey: serverPublicKey,
|
||||
vpnPublicKey: wgProvider.getServerPublicKey(),
|
||||
vpnEndpoint: env.VPN_ENDPOINT,
|
||||
vpnDns: env.VPN_DNS,
|
||||
};
|
||||
|
34
src/lib/server/oauth-providers/authentik.ts
Normal file
34
src/lib/server/oauth-providers/authentik.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { envToBool } from '$lib/utils';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { Authentik, decodeIdToken } from 'arctic';
|
||||
import { assertGuard } from 'typia';
|
||||
import type { IOAuthProvider } from '$lib/server/oauth';
|
||||
|
||||
const authentikProvider = new Authentik(
|
||||
env.AUTH_AUTHENTIK_DOMAIN,
|
||||
env.AUTH_AUTHENTIK_CLIENT_ID,
|
||||
env.AUTH_AUTHENTIK_CLIENT_SECRET,
|
||||
`${env.ORIGIN}/auth/authentik/callback`,
|
||||
);
|
||||
|
||||
export const authentik: IOAuthProvider = {
|
||||
requireInvite: envToBool(env.AUTH_AUTHENTIK_REQUIRE_INVITE, true),
|
||||
createAuthorizationURL: (state: string, codeVerifier: string) => {
|
||||
const scopes = ['openid', 'profile'];
|
||||
return authentikProvider.createAuthorizationURL(state, codeVerifier, scopes);
|
||||
},
|
||||
validateAuthorizationCode: async (code: string, codeVerifier: string) => {
|
||||
const tokens = await authentikProvider.validateAuthorizationCode(code, codeVerifier);
|
||||
const claims = decodeIdToken(tokens.idToken());
|
||||
assertGuard<{
|
||||
sub: string;
|
||||
name: string;
|
||||
preferred_username: string;
|
||||
}>(claims);
|
||||
return {
|
||||
sub: claims.sub,
|
||||
name: claims.name,
|
||||
username: claims.preferred_username,
|
||||
};
|
||||
},
|
||||
};
|
33
src/lib/server/oauth-providers/google.ts
Normal file
33
src/lib/server/oauth-providers/google.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { decodeIdToken, Google } from 'arctic';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { envToBool } from '$lib/utils';
|
||||
import { assertGuard } from 'typia';
|
||||
import type { IOAuthProvider } from '$lib/server/oauth';
|
||||
|
||||
const googleProvider = new Google(
|
||||
env.AUTH_GOOGLE_CLIENT_ID,
|
||||
env.AUTH_GOOGLE_CLIENT_SECRET,
|
||||
`${env.ORIGIN}/auth/google/callback`,
|
||||
);
|
||||
|
||||
export const google: IOAuthProvider = {
|
||||
requireInvite: envToBool(env.AUTH_GOOGLE_REQUIRE_INVITE, true),
|
||||
createAuthorizationURL: (state: string, codeVerifier: string) => {
|
||||
const scopes = ['openid', 'profile', 'email'];
|
||||
return googleProvider.createAuthorizationURL(state, codeVerifier, scopes);
|
||||
},
|
||||
validateAuthorizationCode: async (code: string, codeVerifier: string) => {
|
||||
const tokens = await googleProvider.validateAuthorizationCode(code, codeVerifier);
|
||||
const claims = decodeIdToken(tokens.idToken());
|
||||
assertGuard<{
|
||||
sub: string;
|
||||
email: string;
|
||||
name: string;
|
||||
}>(claims);
|
||||
return {
|
||||
sub: claims.sub,
|
||||
name: claims.name,
|
||||
username: claims.email,
|
||||
};
|
||||
},
|
||||
};
|
2
src/lib/server/oauth-providers/index.ts
Normal file
2
src/lib/server/oauth-providers/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { authentik } from './authentik';
|
||||
export { google } from './google';
|
@ -1,15 +1,19 @@
|
||||
import { Authentik, Google } from 'arctic';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import type { AuthProvider } from '$lib/auth';
|
||||
import { authentik, google } from '$lib/server/oauth-providers';
|
||||
|
||||
export const authentik = new Authentik(
|
||||
env.AUTH_DOMAIN,
|
||||
env.AUTH_CLIENT_ID,
|
||||
env.AUTH_CLIENT_SECRET,
|
||||
`${env.ORIGIN}/auth/authentik/callback`,
|
||||
);
|
||||
export interface IOAuthClaims {
|
||||
sub: string;
|
||||
name: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
export const google = new Google(
|
||||
env.GOOGLE_CLIENT_ID,
|
||||
env.GOOGLE_CLIENT_SECRET,
|
||||
`${env.ORIGIN}/auth/google/callback`,
|
||||
);
|
||||
export interface IOAuthProvider {
|
||||
readonly requireInvite: boolean;
|
||||
createAuthorizationURL(state: string, codeVerifier: string): URL;
|
||||
validateAuthorizationCode(code: string, codeVerifier: string): Promise<IOAuthClaims>;
|
||||
}
|
||||
|
||||
export const oauthProviders: Record<AuthProvider, IOAuthProvider> = {
|
||||
authentik,
|
||||
google,
|
||||
};
|
||||
|
@ -1,49 +0,0 @@
|
||||
import { env } from '$env/dynamic/private';
|
||||
import assert from 'node:assert';
|
||||
import { encodeBasicCredentials } from 'arctic/dist/request';
|
||||
import { dev } from '$app/environment';
|
||||
import type { OpnsenseWgServers } from '$lib/opnsense/wg';
|
||||
|
||||
export const opnsenseUrl = env.OPNSENSE_API_URL;
|
||||
export const opnsenseAuth =
|
||||
'Basic ' + encodeBasicCredentials(env.OPNSENSE_API_KEY, env.OPNSENSE_API_SECRET);
|
||||
export const opnsenseIfname = env.OPNSENSE_WG_IFNAME;
|
||||
|
||||
// unset secret for security
|
||||
if (!dev) env.OPNSENSE_API_SECRET = '';
|
||||
|
||||
export let serverUuid: string, serverPublicKey: string;
|
||||
|
||||
export async function fetchOpnsenseServer() {
|
||||
// this might be pretty bad if the server is down and in a bunch of other cases
|
||||
// TODO: write a retry loop later
|
||||
const resServers = await fetch(`${opnsenseUrl}/api/wireguard/client/list_servers`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: opnsenseAuth,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
assert(resServers.ok, 'Failed to fetch OPNsense WireGuard servers');
|
||||
const servers = (await resServers.json()) as OpnsenseWgServers;
|
||||
assert.equal(servers.status, 'ok', 'Failed to fetch OPNsense WireGuard servers');
|
||||
const uuid = servers.rows.find((server) => server.name === opnsenseIfname)?.uuid;
|
||||
assert(uuid, 'Failed to find server UUID for OPNsense WireGuard server');
|
||||
serverUuid = uuid;
|
||||
console.log('OPNsense WireGuard server UUID:', serverUuid);
|
||||
|
||||
const resServerInfo = await fetch(
|
||||
`${opnsenseUrl}/api/wireguard/client/get_server_info/${serverUuid}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: opnsenseAuth,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
},
|
||||
);
|
||||
assert(resServerInfo.ok, 'Failed to fetch OPNsense WireGuard server info');
|
||||
const serverInfo = await resServerInfo.json();
|
||||
assert.equal(serverInfo.status, 'ok', 'Failed to fetch OPNsense WireGuard server info');
|
||||
serverPublicKey = serverInfo['pubkey'];
|
||||
}
|
38
src/lib/server/types/index.ts
Normal file
38
src/lib/server/types/index.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import type { Result } from '$lib/types';
|
||||
import type { User } from '$lib/server/db/schema';
|
||||
|
||||
export interface IWgProvider {
|
||||
init(): Promise<Result<null, Error>>;
|
||||
|
||||
getServerPublicKey(): string;
|
||||
|
||||
generateKeys(): Promise<Result<WgKeys, Error>>;
|
||||
|
||||
createClient(params: CreateClientParams): Promise<Result<null, Error>>;
|
||||
|
||||
findConnections(user: User): Promise<Result<ClientConnection[], Error>>;
|
||||
|
||||
deleteClient(publicKey: string): Promise<Result<null, Error>>;
|
||||
}
|
||||
|
||||
export type WgKeys = {
|
||||
publicKey: string;
|
||||
privateKey: string;
|
||||
preSharedKey: string;
|
||||
};
|
||||
|
||||
export type CreateClientParams = {
|
||||
user: User;
|
||||
publicKey: string;
|
||||
preSharedKey: string;
|
||||
allowedIps: string;
|
||||
}
|
||||
|
||||
export type ClientConnection = {
|
||||
publicKey: string;
|
||||
endpoint: string;
|
||||
allowedIps: string;
|
||||
transferRx: number;
|
||||
transferTx: number;
|
||||
latestHandshake: number;
|
||||
}
|
12
src/lib/server/wg-provider.ts
Normal file
12
src/lib/server/wg-provider.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { WgProviderOpnsense } from '$lib/server/wg-providers/opnsense';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import type { IWgProvider } from '$lib/server/types';
|
||||
|
||||
const wgProvider: IWgProvider = new WgProviderOpnsense({
|
||||
opnsenseUrl: env.OPNSENSE_API_URL,
|
||||
opnsenseApiKey: env.OPNSENSE_API_KEY,
|
||||
opnsenseApiSecret: env.OPNSENSE_API_SECRET,
|
||||
opnsenseWgIfname: env.OPNSENSE_WG_IFNAME,
|
||||
});
|
||||
|
||||
export default wgProvider;
|
244
src/lib/server/wg-providers/opnsense/index.ts
Normal file
244
src/lib/server/wg-providers/opnsense/index.ts
Normal file
@ -0,0 +1,244 @@
|
||||
import type { ClientConnection, CreateClientParams, IWgProvider, WgKeys } from '$lib/server/types';
|
||||
import { encodeBasicCredentials } from 'arctic/dist/request';
|
||||
import { is } from 'typia';
|
||||
import type { OpnsenseWgPeers, OpnsenseWgServers } from '$lib/server/wg-providers/opnsense/types';
|
||||
import { err, ok, type Result } from '$lib/types';
|
||||
import assert from 'node:assert';
|
||||
import type { User } from '$lib/server/db/schema';
|
||||
|
||||
export class WgProviderOpnsense implements IWgProvider {
|
||||
private opnsenseUrl: string;
|
||||
private opnsenseAuth: string;
|
||||
private opnsenseIfname: string;
|
||||
private opnsenseWgServerUuid: string | undefined;
|
||||
private opnsenseWgServerPublicKey: string | undefined;
|
||||
|
||||
public constructor(params: OpnsenseParams) {
|
||||
this.opnsenseUrl = params.opnsenseUrl;
|
||||
this.opnsenseAuth =
|
||||
'Basic ' + encodeBasicCredentials(params.opnsenseApiKey, params.opnsenseApiSecret);
|
||||
this.opnsenseIfname = params.opnsenseWgIfname;
|
||||
}
|
||||
|
||||
public async init(): Promise<Result<null, Error>> {
|
||||
const resServers = await fetch(`${this.opnsenseUrl}/api/wireguard/client/list_servers`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: this.opnsenseAuth,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const servers = await resServers.json();
|
||||
if (!is<OpnsenseWgServers>(servers)) {
|
||||
console.error('Unexpected response for OPNsense WireGuard servers', servers);
|
||||
return err(new Error('Failed to fetch OPNsense WireGuard servers'));
|
||||
}
|
||||
|
||||
const uuid = servers.rows.find((server) => server.name === this.opnsenseIfname)?.uuid;
|
||||
if (!uuid) {
|
||||
console.error('OPNsense WireGuard servers', servers);
|
||||
return err(new Error('Failed to find server UUID for OPNsense WireGuard server'));
|
||||
}
|
||||
|
||||
const resServerInfo = await fetch(
|
||||
`${this.opnsenseUrl}/api/wireguard/client/get_server_info/${uuid}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: this.opnsenseAuth,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
},
|
||||
);
|
||||
const serverInfo = await resServerInfo.json();
|
||||
const serverPublicKey = serverInfo['pubkey'];
|
||||
if (serverInfo['status'] !== 'ok' || typeof serverPublicKey !== 'string') {
|
||||
console.error('Failed to fetch OPNsense WireGuard server info', serverInfo);
|
||||
return err(new Error('Failed to fetch OPNsense WireGuard server info'));
|
||||
}
|
||||
|
||||
console.debug('OPNsense WireGuard server UUID:', uuid);
|
||||
console.debug('OPNsense WireGuard server public key:', serverPublicKey);
|
||||
this.opnsenseWgServerUuid = uuid;
|
||||
this.opnsenseWgServerPublicKey = serverPublicKey;
|
||||
return ok(null);
|
||||
}
|
||||
|
||||
getServerPublicKey(): string {
|
||||
assert(
|
||||
this.opnsenseWgServerPublicKey,
|
||||
'OPNsense server public key not set, init() must be called first',
|
||||
);
|
||||
return this.opnsenseWgServerPublicKey;
|
||||
}
|
||||
|
||||
public async generateKeys(): Promise<Result<WgKeys, Error>> {
|
||||
const options: RequestInit = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: this.opnsenseAuth,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
};
|
||||
const resKeyPair = await fetch(`${this.opnsenseUrl}/api/wireguard/server/key_pair`, options);
|
||||
const resPsk = await fetch(`${this.opnsenseUrl}/api/wireguard/client/psk`, options);
|
||||
const keyPair = await resKeyPair.json();
|
||||
const psk = await resPsk.json();
|
||||
|
||||
if (!is<{ pubkey: string; privkey: string }>(keyPair)) {
|
||||
console.error('Unexpected response for OPNsense key pair', keyPair);
|
||||
return err(new Error('Failed to fetch OPNsense key pair'));
|
||||
}
|
||||
if (!is<{ psk: string }>(psk)) {
|
||||
console.error('Unexpected response for OPNsense PSK', psk);
|
||||
return err(new Error('Failed to fetch OPNsense PSK'));
|
||||
}
|
||||
|
||||
return ok({
|
||||
publicKey: keyPair.pubkey,
|
||||
privateKey: keyPair.privkey,
|
||||
preSharedKey: psk.psk,
|
||||
});
|
||||
}
|
||||
|
||||
async createClient(params: CreateClientParams): Promise<Result<null, Error>> {
|
||||
assert(this.opnsenseWgServerUuid, 'OPNsense server UUID not set, init() must be called first');
|
||||
|
||||
const createClientRes = await fetch(
|
||||
`${this.opnsenseUrl}/api/wireguard/client/addClientBuilder`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: this.opnsenseAuth,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
configbuilder: {
|
||||
enabled: '1',
|
||||
name: `vpgen-${opnsenseSanitezedUsername(params.user.username)}`,
|
||||
pubkey: params.publicKey,
|
||||
psk: params.preSharedKey,
|
||||
tunneladdress: params.allowedIps,
|
||||
server: this.opnsenseWgServerUuid,
|
||||
endpoint: '',
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
const createClientResJson = await createClientRes.json();
|
||||
if (createClientResJson['result'] !== 'saved') {
|
||||
console.error('Error creating client in OPNsense', createClientResJson);
|
||||
return err(new Error('Failed to create client in OPNsense'));
|
||||
}
|
||||
|
||||
const reconfigureRes = await fetch(`${this.opnsenseUrl}/api/wireguard/service/reconfigure`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: this.opnsenseAuth,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (reconfigureRes.status !== 200) {
|
||||
console.error('Error reconfiguring OPNsense', reconfigureRes);
|
||||
return err(new Error('Failed to reconfigure OPNsense'));
|
||||
}
|
||||
|
||||
return ok(null);
|
||||
}
|
||||
|
||||
async findConnections(user: User): Promise<Result<ClientConnection[], Error>> {
|
||||
const res = await fetch(`${this.opnsenseUrl}/api/wireguard/service/show`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: this.opnsenseAuth,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
current: 1,
|
||||
// "rowCount": 7,
|
||||
sort: {},
|
||||
// TODO: use a more unique search phrase
|
||||
// unfortunately 64 character limit,
|
||||
// but it should be fine if users can't change their own username
|
||||
searchPhrase: `vpgen-${opnsenseSanitezedUsername(user.username)}`,
|
||||
type: ['peer'],
|
||||
}),
|
||||
});
|
||||
|
||||
const peers = await res.json();
|
||||
if (!is<OpnsenseWgPeers>(peers)) {
|
||||
console.error('Unexpected response for OPNsense WireGuard peers', peers);
|
||||
return err(new Error('Failed to fetch OPNsense WireGuard peers'));
|
||||
}
|
||||
|
||||
return ok(
|
||||
peers.rows.map((peer) => {
|
||||
return {
|
||||
publicKey: peer['public-key'],
|
||||
endpoint: peer['endpoint'],
|
||||
allowedIps: peer['allowed-ips'],
|
||||
transferRx: peer['transfer-rx'],
|
||||
transferTx: peer['transfer-tx'],
|
||||
latestHandshake: peer['latest-handshake'] * 1000,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async deleteClient(publicKey: string): Promise<Result<null, Error>> {
|
||||
const client = await this.findOpnsenseClient(publicKey);
|
||||
const clientUuid = client?.uuid;
|
||||
if (typeof clientUuid !== 'string') {
|
||||
console.error('Failed to get OPNsense client UUID for deletion', client);
|
||||
return err(new Error('Failed to get OPNsense client UUID for deletion'));
|
||||
}
|
||||
|
||||
const res = await fetch(`${this.opnsenseUrl}/api/wireguard/client/delClient/${clientUuid}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: this.opnsenseAuth,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
const resJson = await res.json();
|
||||
if (resJson['result'] !== 'deleted') {
|
||||
console.error('Failed to delete OPNsense client', resJson);
|
||||
return err(new Error('Failed to delete OPNsense client'));
|
||||
}
|
||||
|
||||
return ok(null);
|
||||
}
|
||||
|
||||
private async findOpnsenseClient(publicKey: string) {
|
||||
const res = await fetch(`${this.opnsenseUrl}/api/wireguard/client/searchClient`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: this.opnsenseAuth,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
current: 1,
|
||||
sort: {},
|
||||
searchPhrase: publicKey,
|
||||
type: ['peer'],
|
||||
}),
|
||||
});
|
||||
return (await res.json())?.rows?.[0] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
function opnsenseSanitezedUsername(username: string) {
|
||||
return username.slice(0, 63).replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||
}
|
||||
|
||||
export type OpnsenseParams = {
|
||||
opnsenseUrl: string;
|
||||
opnsenseApiKey: string;
|
||||
opnsenseApiSecret: string;
|
||||
opnsenseWgIfname: string;
|
||||
};
|
@ -1,27 +1,2 @@
|
||||
class Ok<T> {
|
||||
readonly _tag = 'ok';
|
||||
value: T;
|
||||
|
||||
constructor(value: T) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
class Err<E> {
|
||||
readonly _tag = 'err';
|
||||
error: E;
|
||||
|
||||
constructor(error: E) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
export type Result<T, E> = Ok<T> | Err<E>;
|
||||
|
||||
export function err<E>(e: E): Err<E> {
|
||||
return new Err(e);
|
||||
}
|
||||
|
||||
export function ok<T>(t: T): Ok<T> {
|
||||
return new Ok(t);
|
||||
}
|
||||
export type { Result } from './result';
|
||||
export { ok, err } from './result';
|
||||
|
27
src/lib/types/result.ts
Normal file
27
src/lib/types/result.ts
Normal file
@ -0,0 +1,27 @@
|
||||
class Ok<T> {
|
||||
readonly _tag = 'ok';
|
||||
value: T;
|
||||
|
||||
constructor(value: T) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
class Err<E> {
|
||||
readonly _tag = 'err';
|
||||
error: E;
|
||||
|
||||
constructor(error: E) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
export type Result<T, E> = Ok<T> | Err<E>;
|
||||
|
||||
export function err<E>(e: E): Err<E> {
|
||||
return new Err(e);
|
||||
}
|
||||
|
||||
export function ok<T>(t: T): Ok<T> {
|
||||
return new Ok(t);
|
||||
}
|
@ -4,3 +4,11 @@ import { twMerge } from "tailwind-merge";
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export function envToBool(value: string | undefined, defaultValue = false): boolean {
|
||||
if (typeof value === "undefined") {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return ['true', '1', 'yes'].includes(value.toLowerCase());
|
||||
}
|
||||
|
@ -1,44 +1,43 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { opnsenseAuth, opnsenseUrl } from '$lib/server/opnsense';
|
||||
import type { OpnsenseWgPeers } from '$lib/opnsense/wg';
|
||||
import { findDevices } from '$lib/server/devices';
|
||||
import type { ConnectionDetails } from '$lib/connections';
|
||||
import { opnsenseSanitezedUsername } from '$lib/opnsense';
|
||||
import type { Result } from '$lib/types';
|
||||
import type { ClientConnection } from '$lib/server/types';
|
||||
import wgProvider from '$lib/server/wg-provider';
|
||||
|
||||
export const GET: RequestHandler = async (event) => {
|
||||
if (!event.locals.user) {
|
||||
return error(401, 'Unauthorized');
|
||||
}
|
||||
console.debug('/api/connections');
|
||||
const peers = await fetchOpnsensePeers(event.locals.user.username);
|
||||
console.debug('/api/connections: fetched opnsense peers', peers.rowCount);
|
||||
|
||||
const peersResult: Result<ClientConnection[], Error> = await wgProvider.findConnections(event.locals.user);
|
||||
if (peersResult._tag === 'err') return error(500, peersResult.error.message);
|
||||
|
||||
const devices = await findDevices(event.locals.user.id);
|
||||
console.debug('/api/connections: fetched db devices');
|
||||
|
||||
if (!peers) {
|
||||
return error(500, 'Error getting info from OPNsense API');
|
||||
}
|
||||
|
||||
// TODO: this is all garbage performance
|
||||
// filter devices with no recent handshakes
|
||||
peers.rows = peers.rows.filter((peer) => peer['latest-handshake']);
|
||||
const peers = peersResult.value.filter((peer) => peer.latestHandshake);
|
||||
|
||||
// start from devices, to treat db as the source of truth
|
||||
const connections: ConnectionDetails[] = [];
|
||||
for (const device of devices) {
|
||||
const peerData = peers.rows.find((peer) => peer['public-key'] === device.publicKey);
|
||||
const peerData = peers.find((peer) => peer.publicKey === device.publicKey);
|
||||
if (!peerData) continue;
|
||||
connections.push({
|
||||
deviceId: device.id,
|
||||
deviceName: device.name,
|
||||
devicePublicKey: device.publicKey,
|
||||
deviceIps: peerData['allowed-ips'].split(','),
|
||||
endpoint: peerData['endpoint'],
|
||||
deviceIps: peerData.allowedIps.split(','),
|
||||
endpoint: peerData.endpoint,
|
||||
// swap rx and tx, since the opnsense values are from the server perspective
|
||||
transferRx: peerData['transfer-tx'],
|
||||
transferTx: peerData['transfer-rx'],
|
||||
latestHandshake: peerData['latest-handshake'] * 1000,
|
||||
transferRx: peerData.transferTx,
|
||||
transferTx: peerData.transferRx,
|
||||
latestHandshake: peerData.latestHandshake,
|
||||
});
|
||||
}
|
||||
|
||||
@ -49,25 +48,3 @@ export const GET: RequestHandler = async (event) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
async function fetchOpnsensePeers(username: string) {
|
||||
const res = await fetch(`${opnsenseUrl}/api/wireguard/service/show`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: opnsenseAuth,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
current: 1,
|
||||
// "rowCount": 7,
|
||||
sort: {},
|
||||
// TODO: use a more unique search phrase
|
||||
// unfortunately 64 character limit,
|
||||
// but it should be fine if users can't change their own username
|
||||
searchPhrase: `vpgen-${opnsenseSanitezedUsername(username)}`,
|
||||
type: ['peer'],
|
||||
}),
|
||||
});
|
||||
return (await res.json()) as OpnsenseWgPeers;
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { fail, redirect } from "@sveltejs/kit";
|
||||
import { invalidateSession, deleteSessionTokenCookie } from "$lib/server/auth";
|
||||
import type { Actions } from "./$types";
|
||||
import { fail, redirect } from '@sveltejs/kit';
|
||||
import { invalidateSession, deleteSessionTokenCookie } from '$lib/server/auth';
|
||||
import type { Actions } from './$types';
|
||||
|
||||
export const actions: Actions = {
|
||||
logout: async (event) => {
|
||||
if (event.locals.session === null) {
|
||||
logout: async ({ locals, cookies }) => {
|
||||
if (locals.session === null) {
|
||||
return fail(401);
|
||||
}
|
||||
await invalidateSession(event.locals.session.id);
|
||||
deleteSessionTokenCookie(event);
|
||||
return redirect(302, "/");
|
||||
}
|
||||
await invalidateSession(locals.session.id);
|
||||
deleteSessionTokenCookie(cookies);
|
||||
redirect(302, '/');
|
||||
},
|
||||
};
|
||||
|
36
src/routes/auth/[provider]/+server.ts
Normal file
36
src/routes/auth/[provider]/+server.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { generateCodeVerifier, generateState } from 'arctic';
|
||||
import { oauthProviders } from '$lib/server/oauth';
|
||||
import { is } from 'typia';
|
||||
import { type AuthProvider, enabledAuthProviders } from '$lib/auth';
|
||||
|
||||
export async function GET({ params: { provider }, url, cookies }) {
|
||||
if (!is<AuthProvider>(provider) || !enabledAuthProviders[provider]) {
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
const oauthProvider = oauthProviders[provider];
|
||||
|
||||
const inviteToken = url.searchParams.get('invite') ?? '';
|
||||
const state = generateState();
|
||||
const codeVerifier = generateCodeVerifier();
|
||||
const authUrl = oauthProvider.createAuthorizationURL(state + inviteToken, codeVerifier);
|
||||
|
||||
cookies.set(`${provider}_oauth_state`, state, {
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
maxAge: 60 * 10, // 10 minutes
|
||||
sameSite: 'lax',
|
||||
});
|
||||
cookies.set(`${provider}_code_verifier`, codeVerifier, {
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
maxAge: 60 * 10, // 10 minutes
|
||||
sameSite: 'lax',
|
||||
});
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: authUrl.toString(),
|
||||
},
|
||||
});
|
||||
}
|
76
src/routes/auth/[provider]/callback/+page.server.ts
Normal file
76
src/routes/auth/[provider]/callback/+page.server.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { is } from 'typia';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { error, redirect } from '@sveltejs/kit';
|
||||
import { type AuthProvider, enabledAuthProviders } from '$lib/auth';
|
||||
import { oauthProviders } from '$lib/server/oauth';
|
||||
import { ArcticFetchError, OAuth2RequestError } from 'arctic';
|
||||
import { db } from '$lib/server/db';
|
||||
import * as table from '$lib/server/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { createSession, isValidInviteToken, setSessionTokenCookie } from '$lib/server/auth';
|
||||
|
||||
export const load: PageServerLoad = async ({ params: { provider }, url, cookies }) => {
|
||||
if (!is<AuthProvider>(provider) || !enabledAuthProviders[provider]) {
|
||||
error(404);
|
||||
}
|
||||
|
||||
const oauthProvider = oauthProviders[provider];
|
||||
const code = url.searchParams.get('code');
|
||||
const state = url.searchParams.get('state');
|
||||
const storedState = cookies.get(`${provider}_oauth_state`) ?? null;
|
||||
const codeVerifier = cookies.get(`${provider}_code_verifier`) ?? null;
|
||||
|
||||
if (code === null || state === null || storedState === null || codeVerifier === null) {
|
||||
error(400, 'Missing url parameters');
|
||||
}
|
||||
|
||||
const stateGeneratedToken = state.slice(0, storedState.length);
|
||||
const stateInviteToken = state.slice(storedState.length);
|
||||
if (stateGeneratedToken !== storedState) {
|
||||
error(400, 'Invalid state in url');
|
||||
}
|
||||
|
||||
let claims;
|
||||
try {
|
||||
claims = await oauthProvider.validateAuthorizationCode(code, codeVerifier);
|
||||
} catch (e) {
|
||||
if (e instanceof OAuth2RequestError) {
|
||||
console.debug('Arctic: OAuth: invalid authorization code, credentials, or redirect URI', e);
|
||||
error(400, 'Invalid authorization code');
|
||||
}
|
||||
if (e instanceof ArcticFetchError) {
|
||||
console.debug('Arctic: failed to call `fetch()`', e);
|
||||
error(400, 'Failed to validate authorization code');
|
||||
}
|
||||
console.error('Unexpected error validating authorization code', code, e);
|
||||
error(500);
|
||||
}
|
||||
|
||||
const existingUser = await db.query.users.findFirst({ where: eq(table.users.id, claims.sub) });
|
||||
|
||||
if (existingUser) {
|
||||
const session = await createSession(existingUser.id);
|
||||
setSessionTokenCookie(cookies, session.id, session.expiresAt);
|
||||
redirect(302, '/');
|
||||
}
|
||||
|
||||
if (oauthProvider.requireInvite && !isValidInviteToken(stateInviteToken)) {
|
||||
const message =
|
||||
stateInviteToken.length === 0 ? 'sign up with an invite link first' : 'invalid invite link';
|
||||
error(403, 'Not Authorized: ' + message);
|
||||
}
|
||||
|
||||
const user: table.User = {
|
||||
id: claims.sub,
|
||||
authSource: provider,
|
||||
username: claims.username,
|
||||
name: claims.name,
|
||||
};
|
||||
await db.insert(table.users).values(user);
|
||||
console.log('created user', user, 'using provider', provider, 'with invite token', stateInviteToken);
|
||||
|
||||
const session = await createSession(user.id);
|
||||
setSessionTokenCookie(cookies, session.id, session.expiresAt);
|
||||
|
||||
redirect(302, '/');
|
||||
};
|
@ -1,30 +0,0 @@
|
||||
import { generateState, generateCodeVerifier } from "arctic";
|
||||
import { authentik } from "$lib/server/oauth";
|
||||
|
||||
import type { RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
export async function GET(event: RequestEvent): Promise<Response> {
|
||||
const state = generateState();
|
||||
const codeVerifier = generateCodeVerifier();
|
||||
const url = authentik.createAuthorizationURL(state, codeVerifier, ["openid", "profile"]);
|
||||
|
||||
event.cookies.set("authentik_oauth_state", state, {
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
maxAge: 60 * 10, // 10 minutes
|
||||
sameSite: "lax"
|
||||
});
|
||||
event.cookies.set("authentik_code_verifier", codeVerifier, {
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
maxAge: 60 * 10, // 10 minutes
|
||||
sameSite: "lax"
|
||||
});
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: url.toString()
|
||||
}
|
||||
});
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
import { createSession, setSessionTokenCookie } from '$lib/server/auth';
|
||||
import { authentik } from '$lib/server/oauth';
|
||||
import { decodeIdToken } from 'arctic';
|
||||
|
||||
import type { RequestEvent } from '@sveltejs/kit';
|
||||
import type { OAuth2Tokens } from 'arctic';
|
||||
import { db } from '$lib/server/db';
|
||||
import { eq } from 'drizzle-orm';
|
||||
|
||||
import * as table from '$lib/server/db/schema';
|
||||
|
||||
export async function GET(event: RequestEvent): Promise<Response> {
|
||||
const code = event.url.searchParams.get('code');
|
||||
const state = event.url.searchParams.get('state');
|
||||
const storedState = event.cookies.get('authentik_oauth_state') ?? null;
|
||||
const codeVerifier = event.cookies.get('authentik_code_verifier') ?? null;
|
||||
|
||||
if (code === null || state === null || storedState === null || codeVerifier === null) {
|
||||
return new Response(null, {
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
if (state !== storedState) {
|
||||
return new Response(null, {
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
let tokens: OAuth2Tokens;
|
||||
try {
|
||||
tokens = await authentik.validateAuthorizationCode(code, codeVerifier);
|
||||
} catch (e) {
|
||||
// Invalid code or client credentials
|
||||
return new Response(null, {
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
const claims = decodeIdToken(tokens.idToken()) as {
|
||||
sub: string;
|
||||
preferred_username: string;
|
||||
name: string;
|
||||
};
|
||||
console.log('claims', claims);
|
||||
const userId: string = claims.sub;
|
||||
const username: string = claims.preferred_username;
|
||||
|
||||
const existingUser = await db.query.users.findFirst({ where: eq(table.users.id, userId) });
|
||||
|
||||
if (existingUser) {
|
||||
const session = await createSession(existingUser.id);
|
||||
setSessionTokenCookie(event, session.id, session.expiresAt);
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: '/',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const user: table.User = {
|
||||
id: userId,
|
||||
authSource: 'authentik',
|
||||
username,
|
||||
name: claims.name as string,
|
||||
};
|
||||
|
||||
try {
|
||||
await db.insert(table.users).values(user);
|
||||
const session = await createSession(user.id);
|
||||
setSessionTokenCookie(event, session.id, session.expiresAt);
|
||||
} catch (e) {
|
||||
console.error('failed to create user', e);
|
||||
return new Response(null, {
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: '/',
|
||||
},
|
||||
});
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
import type { RequestHandler } from './$types';
|
||||
import { generateCodeVerifier, generateState } from 'arctic';
|
||||
import { google } from '$lib/server/oauth';
|
||||
|
||||
export const GET: RequestHandler = ({ url, cookies }) => {
|
||||
const inviteToken = url.searchParams.get('invite');
|
||||
|
||||
const state = generateState();
|
||||
const codeVerifier = generateCodeVerifier();
|
||||
const scopes = ['openid', 'profile', 'email'];
|
||||
const authUrl = google.createAuthorizationURL(state, codeVerifier, scopes);
|
||||
|
||||
cookies.set('google_oauth_state', state, {
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
maxAge: 60 * 10, // 10 minutes
|
||||
sameSite: 'lax',
|
||||
});
|
||||
cookies.set('google_code_verifier', codeVerifier, {
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
maxAge: 60 * 10, // 10 minutes
|
||||
sameSite: 'lax',
|
||||
});
|
||||
if (inviteToken !== null) cookies.set('invite_token', inviteToken, {
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
maxAge: 60 * 10, // 10 minutes
|
||||
sameSite: 'lax',
|
||||
});
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: authUrl.toString(),
|
||||
},
|
||||
});
|
||||
};
|
@ -1,100 +0,0 @@
|
||||
import type { RequestHandler } from './$types';
|
||||
import * as arctic from 'arctic';
|
||||
import { google } from '$lib/server/oauth';
|
||||
import { db } from '$lib/server/db';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import * as table from '$lib/server/db/schema';
|
||||
import { createSession, isValidInviteToken, setSessionTokenCookie } from '$lib/server/auth';
|
||||
import type { OAuth2Tokens } from 'arctic';
|
||||
import { assertGuard } from 'typia';
|
||||
|
||||
export const GET: RequestHandler = async (event) => {
|
||||
const { url, cookies } = event;
|
||||
const code = url.searchParams.get('code');
|
||||
const state = url.searchParams.get('state');
|
||||
const storedState = cookies.get('google_oauth_state') ?? null;
|
||||
const codeVerifier = cookies.get('google_code_verifier', ) ?? null;
|
||||
const inviteToken = cookies.get('invite_token') ?? null;
|
||||
|
||||
if (code === null || state === null || storedState === null || codeVerifier === null) {
|
||||
return new Response(null, {
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
let tokens: OAuth2Tokens;
|
||||
try {
|
||||
tokens = await google.validateAuthorizationCode(code, codeVerifier);
|
||||
} catch (e) {
|
||||
if (e instanceof arctic.OAuth2RequestError) {
|
||||
console.debug('Arctic: OAuth: invalid authorization code, credentials, or redirect URI', e);
|
||||
return new Response(null, {
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
if (e instanceof arctic.ArcticFetchError) {
|
||||
console.debug('Arctic: failed to call `fetch()`', e);
|
||||
return new Response(null, {
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
|
||||
const idToken = tokens.idToken();
|
||||
const claims = arctic.decodeIdToken(idToken);
|
||||
|
||||
console.log('claims', claims);
|
||||
|
||||
assertGuard<{
|
||||
sub: string;
|
||||
email: string;
|
||||
name: string;
|
||||
}>(claims);
|
||||
|
||||
const userId = claims.sub;
|
||||
const existingUser = await db.query.users.findFirst({ where: eq(table.users.id, userId) });
|
||||
|
||||
if (existingUser) {
|
||||
const session = await createSession(existingUser.id);
|
||||
setSessionTokenCookie(event, session.id, session.expiresAt);
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: '/',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: proper error page
|
||||
if (inviteToken === null || !isValidInviteToken(inviteToken)) {
|
||||
return new Response(null, {
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const user: table.User = {
|
||||
id: userId,
|
||||
authSource: 'google',
|
||||
username: claims.email,
|
||||
name: claims.name,
|
||||
};
|
||||
|
||||
// TODO: proper error handling, delete cookies
|
||||
await db.insert(table.users).values(user);
|
||||
console.log('created user', user, 'with invite token', inviteToken);
|
||||
|
||||
const session = await createSession(user.id);
|
||||
|
||||
setSessionTokenCookie(event, session.id, session.expiresAt);
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: '/',
|
||||
},
|
||||
});
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
import type { Actions } from './$types';
|
||||
import { createDevice } from '$lib/server/devices';
|
||||
import { error, fail, redirect } from '@sveltejs/kit';
|
||||
import wgProvider from '$lib/server/wg-provider';
|
||||
|
||||
export const actions = {
|
||||
create: async (event) => {
|
||||
|
@ -4,7 +4,7 @@
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import { Button, buttonVariants } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { LucideLoaderCircle, LucidePlus } from 'lucide-svelte';
|
||||
import { LucideLoaderCircle, LucidePlus } from '@lucide/svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { page } from '$app/state';
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import { Button, buttonVariants } from '$lib/components/ui/button';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import { LucideLoaderCircle, LucideTrash } from 'lucide-svelte';
|
||||
import { LucideLoaderCircle, LucideTrash } from '@lucide/svelte';
|
||||
|
||||
const { device } = $props();
|
||||
let submitted = $state(false);
|
||||
@ -26,10 +26,12 @@
|
||||
<Button type="submit" variant="destructive" disabled={submitted}>
|
||||
Delete
|
||||
</Button>
|
||||
<Dialog.Close asChild let:builder>
|
||||
<button class={buttonVariants()} disabled={submitted} use:builder.action {...builder}>
|
||||
<Dialog.Close>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} disabled={submitted}>
|
||||
Cancel
|
||||
</button>
|
||||
</Button>
|
||||
{/snippet}
|
||||
</Dialog.Close>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
|
@ -2,6 +2,7 @@ import type { LayoutServerLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { isValidInviteToken } from '$lib/server/auth';
|
||||
|
||||
export const load: LayoutServerLoad = ({ params }) => {
|
||||
if (!isValidInviteToken(params.id)) redirect(307, '/')
|
||||
export const load: LayoutServerLoad = ({ params, locals }) => {
|
||||
if (!isValidInviteToken(params.id)) redirect(302, '/');
|
||||
if (locals.user !== null) redirect(302, '/');
|
||||
};
|
||||
|
@ -10,7 +10,7 @@
|
||||
</svelte:head>
|
||||
|
||||
<h1 class="mb-2 scroll-m-20 text-center text-3xl font-extrabold tracking-tight lg:text-4xl">
|
||||
Welcome to VPGen
|
||||
You are invited to VPGen
|
||||
</h1>
|
||||
|
||||
<AuthForm inviteToken={inviteToken} />
|
||||
<AuthForm {inviteToken} />
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { invalidate, invalidateAll } from '$app/navigation';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { LucideLoaderCircle, LucideLogOut, LucideRefreshCw } from 'lucide-svelte';
|
||||
import { LucideLoaderCircle, LucideLogOut, LucideRefreshCw } from '@lucide/svelte';
|
||||
import { CodeSnippet } from '$lib/components/app/code-snippet/index.js';
|
||||
|
||||
let { data } = $props();
|
||||
|
Loading…
x
Reference in New Issue
Block a user