Add session-based authentication with login/logout
- Users and sessions tables (Argon2 password hashing, SHA-256 session tokens) - Server hooks validate session cookie on every request - (app) routes redirect to /login if not authenticated - Login page with email/password, styled matching budget app - Logout via POST form action (invalidates session) - User display name and sign out button in header - create-user CLI script: npm run create-user <email> <password> [name] - 30-day sessions with auto-refresh after 15 days Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Generated
+844
@@ -8,7 +8,11 @@
|
|||||||
"name": "buildfor-life-repair",
|
"name": "buildfor-life-repair",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@node-rs/argon2": "^2.0.2",
|
||||||
|
"@oslojs/crypto": "^1.0.1",
|
||||||
|
"@oslojs/encoding": "^1.1.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
"dotenv": "^17.4.1",
|
||||||
"drizzle-orm": "^0.38.4",
|
"drizzle-orm": "^0.38.4",
|
||||||
"pg": "^8.13.1",
|
"pg": "^8.13.1",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
@@ -26,6 +30,7 @@
|
|||||||
"svelte": "^5.19.0",
|
"svelte": "^5.19.0",
|
||||||
"svelte-check": "^4.1.4",
|
"svelte-check": "^4.1.4",
|
||||||
"tailwindcss": "^4.1.3",
|
"tailwindcss": "^4.1.3",
|
||||||
|
"tsx": "^4.21.0",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"vite": "^6.1.0"
|
"vite": "^6.1.0"
|
||||||
}
|
}
|
||||||
@@ -37,6 +42,17 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/@emnapi/core": {
|
||||||
|
"version": "1.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
|
||||||
|
"integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/wasi-threads": "1.2.1",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@emnapi/runtime": {
|
"node_modules/@emnapi/runtime": {
|
||||||
"version": "1.9.2",
|
"version": "1.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
|
||||||
@@ -47,6 +63,16 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@emnapi/wasi-threads": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@esbuild-kit/core-utils": {
|
"node_modules/@esbuild-kit/core-utils": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz",
|
||||||
@@ -1336,6 +1362,298 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@napi-rs/wasm-runtime": {
|
||||||
|
"version": "0.2.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
|
||||||
|
"integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/core": "^1.4.3",
|
||||||
|
"@emnapi/runtime": "^1.4.3",
|
||||||
|
"@tybys/wasm-util": "^0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@node-rs/argon2": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@node-rs/argon2/-/argon2-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-t64wIsPEtNd4aUPuTAyeL2ubxATCBGmeluaKXEMAFk/8w6AJIVVkeLKMBpgLW6LU2t5cQxT+env/c6jxbtTQBg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@node-rs/argon2-android-arm-eabi": "2.0.2",
|
||||||
|
"@node-rs/argon2-android-arm64": "2.0.2",
|
||||||
|
"@node-rs/argon2-darwin-arm64": "2.0.2",
|
||||||
|
"@node-rs/argon2-darwin-x64": "2.0.2",
|
||||||
|
"@node-rs/argon2-freebsd-x64": "2.0.2",
|
||||||
|
"@node-rs/argon2-linux-arm-gnueabihf": "2.0.2",
|
||||||
|
"@node-rs/argon2-linux-arm64-gnu": "2.0.2",
|
||||||
|
"@node-rs/argon2-linux-arm64-musl": "2.0.2",
|
||||||
|
"@node-rs/argon2-linux-x64-gnu": "2.0.2",
|
||||||
|
"@node-rs/argon2-linux-x64-musl": "2.0.2",
|
||||||
|
"@node-rs/argon2-wasm32-wasi": "2.0.2",
|
||||||
|
"@node-rs/argon2-win32-arm64-msvc": "2.0.2",
|
||||||
|
"@node-rs/argon2-win32-ia32-msvc": "2.0.2",
|
||||||
|
"@node-rs/argon2-win32-x64-msvc": "2.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@node-rs/argon2-android-arm-eabi": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm-eabi/-/argon2-android-arm-eabi-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-DV/H8p/jt40lrao5z5g6nM9dPNPGEHL+aK6Iy/og+dbL503Uj0AHLqj1Hk9aVUSCNnsDdUEKp4TVMi0YakDYKw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@node-rs/argon2-android-arm64": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm64/-/argon2-android-arm64-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-1LKwskau+8O1ktKx7TbK7jx1oMOMt4YEXZOdSNIar1TQKxm6isZ0cRXgHLibPHEcNHgYRsJWDE9zvDGBB17QDg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@node-rs/argon2-darwin-arm64": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-arm64/-/argon2-darwin-arm64-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-3TTNL/7wbcpNju5YcqUrCgXnXUSbD7ogeAKatzBVHsbpjZQbNb1NDxDjqqrWoTt6XL3z9mJUMGwbAk7zQltHtA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@node-rs/argon2-darwin-x64": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-x64/-/argon2-darwin-x64-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-vNPfkLj5Ij5111UTiYuwgxMqE7DRbOS2y58O2DIySzSHbcnu+nipmRKg+P0doRq6eKIJStyBK8dQi5Ic8pFyDw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@node-rs/argon2-freebsd-x64": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@node-rs/argon2-freebsd-x64/-/argon2-freebsd-x64-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-M8vQZk01qojQfCqQU0/O1j1a4zPPrz93zc9fSINY7Q/6RhQRBCYwDw7ltDCZXg5JRGlSaeS8cUXWyhPGar3cGg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@node-rs/argon2-linux-arm-gnueabihf": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm-gnueabihf/-/argon2-linux-arm-gnueabihf-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-7EmmEPHLzcu0G2GDh30L6G48CH38roFC2dqlQJmtRCxs6no3tTE/pvgBGatTp/o2n2oyOJcfmgndVFcUpwMnww==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@node-rs/argon2-linux-arm64-gnu": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-gnu/-/argon2-linux-arm64-gnu-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-6lsYh3Ftbk+HAIZ7wNuRF4SZDtxtFTfK+HYFAQQyW7Ig3LHqasqwfUKRXVSV5tJ+xTnxjqgKzvZSUJCAyIfHew==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@node-rs/argon2-linux-arm64-musl": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-musl/-/argon2-linux-arm64-musl-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-p3YqVMNT/4DNR67tIHTYGbedYmXxW9QlFmF39SkXyEbGQwpgSf6pH457/fyXBIYznTU/smnG9EH+C1uzT5j4hA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@node-rs/argon2-linux-x64-gnu": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-gnu/-/argon2-linux-x64-gnu-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-ZM3jrHuJ0dKOhvA80gKJqBpBRmTJTFSo2+xVZR+phQcbAKRlDMSZMFDiKbSTnctkfwNFtjgDdh5g1vaEV04AvA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@node-rs/argon2-linux-x64-musl": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-musl/-/argon2-linux-x64-musl-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-of5uPqk7oCRF/44a89YlWTEfjsftPywyTULwuFDKyD8QtVZoonrJR6ZWvfFE/6jBT68S0okAkAzzMEdBVWdxWw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@node-rs/argon2-wasm32-wasi": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@node-rs/argon2-wasm32-wasi/-/argon2-wasm32-wasi-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-U3PzLYKSQYzTERstgtHLd4ZTkOF9co57zTXT77r0cVUsleGZOrd6ut7rHzeWwoJSiHOVxxa0OhG1JVQeB7lLoQ==",
|
||||||
|
"cpu": [
|
||||||
|
"wasm32"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@napi-rs/wasm-runtime": "^0.2.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@node-rs/argon2-win32-arm64-msvc": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-arm64-msvc/-/argon2-win32-arm64-msvc-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Eisd7/NM0m23ijrGr6xI2iMocdOuyl6gO27gfMfya4C5BODbUSP7ljKJ7LrA0teqZMdYHesRDzx36Js++/vhiQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@node-rs/argon2-win32-ia32-msvc": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-ia32-msvc/-/argon2-win32-ia32-msvc-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-GsE2ezwAYwh72f9gIjbGTZOf4HxEksb5M2eCaj+Y5rGYVwAdt7C12Q2e9H5LRYxWcFvLH4m4jiSZpQQ4upnPAQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@node-rs/argon2-win32-x64-msvc": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-x64-msvc/-/argon2-win32-x64-msvc-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-cJxWXanH4Ew9CfuZ4IAEiafpOBCe97bzoKowHCGk5lG/7kR4WF/eknnBlHW9m8q7t10mKq75kruPLtbSDqgRTw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@oslojs/asn1": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@oslojs/asn1/-/asn1-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@oslojs/binary": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@oslojs/binary": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@oslojs/binary/-/binary-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@oslojs/crypto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@oslojs/crypto/-/crypto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@oslojs/asn1": "1.0.0",
|
||||||
|
"@oslojs/binary": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@oslojs/encoding": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@petamoriken/float16": {
|
"node_modules/@petamoriken/float16": {
|
||||||
"version": "3.9.3",
|
"version": "3.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.3.tgz",
|
||||||
@@ -2183,6 +2501,16 @@
|
|||||||
"vite": "^5.2.0 || ^6 || ^7 || ^8"
|
"vite": "^5.2.0 || ^6 || ^7 || ^8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tybys/wasm-util": {
|
||||||
|
"version": "0.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
||||||
|
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/cookie": {
|
"node_modules/@types/cookie": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
|
||||||
@@ -2494,6 +2822,18 @@
|
|||||||
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
|
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "17.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.1.tgz",
|
||||||
|
"integrity": "sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://dotenvx.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/drizzle-kit": {
|
"node_modules/drizzle-kit": {
|
||||||
"version": "0.30.6",
|
"version": "0.30.6",
|
||||||
"resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.30.6.tgz",
|
"resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.30.6.tgz",
|
||||||
@@ -3949,6 +4289,510 @@
|
|||||||
"license": "0BSD",
|
"license": "0BSD",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/tsx": {
|
||||||
|
"version": "4.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
||||||
|
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"esbuild": "~0.27.0",
|
||||||
|
"get-tsconfig": "^4.7.5"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"tsx": "dist/cli.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "~2.3.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"aix"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/android-arm": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/android-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/android-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/darwin-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/linux-arm": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/linux-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/linux-ia32": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/linux-loong64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
|
||||||
|
"cpu": [
|
||||||
|
"mips64el"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/linux-s390x": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/linux-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/netbsd-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/openbsd-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/openharmony-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openharmony"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/sunos-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"sunos"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/win32-arm64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/win32-ia32": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/@esbuild/win32-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx/node_modules/esbuild": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"esbuild": "bin/esbuild"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@esbuild/aix-ppc64": "0.27.7",
|
||||||
|
"@esbuild/android-arm": "0.27.7",
|
||||||
|
"@esbuild/android-arm64": "0.27.7",
|
||||||
|
"@esbuild/android-x64": "0.27.7",
|
||||||
|
"@esbuild/darwin-arm64": "0.27.7",
|
||||||
|
"@esbuild/darwin-x64": "0.27.7",
|
||||||
|
"@esbuild/freebsd-arm64": "0.27.7",
|
||||||
|
"@esbuild/freebsd-x64": "0.27.7",
|
||||||
|
"@esbuild/linux-arm": "0.27.7",
|
||||||
|
"@esbuild/linux-arm64": "0.27.7",
|
||||||
|
"@esbuild/linux-ia32": "0.27.7",
|
||||||
|
"@esbuild/linux-loong64": "0.27.7",
|
||||||
|
"@esbuild/linux-mips64el": "0.27.7",
|
||||||
|
"@esbuild/linux-ppc64": "0.27.7",
|
||||||
|
"@esbuild/linux-riscv64": "0.27.7",
|
||||||
|
"@esbuild/linux-s390x": "0.27.7",
|
||||||
|
"@esbuild/linux-x64": "0.27.7",
|
||||||
|
"@esbuild/netbsd-arm64": "0.27.7",
|
||||||
|
"@esbuild/netbsd-x64": "0.27.7",
|
||||||
|
"@esbuild/openbsd-arm64": "0.27.7",
|
||||||
|
"@esbuild/openbsd-x64": "0.27.7",
|
||||||
|
"@esbuild/openharmony-arm64": "0.27.7",
|
||||||
|
"@esbuild/sunos-x64": "0.27.7",
|
||||||
|
"@esbuild/win32-arm64": "0.27.7",
|
||||||
|
"@esbuild/win32-ia32": "0.27.7",
|
||||||
|
"@esbuild/win32-x64": "0.27.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
|
|||||||
+7
-1
@@ -11,10 +11,15 @@
|
|||||||
"db:generate": "drizzle-kit generate",
|
"db:generate": "drizzle-kit generate",
|
||||||
"db:migrate": "drizzle-kit migrate",
|
"db:migrate": "drizzle-kit migrate",
|
||||||
"db:push": "drizzle-kit push",
|
"db:push": "drizzle-kit push",
|
||||||
"db:studio": "drizzle-kit studio"
|
"db:studio": "drizzle-kit studio",
|
||||||
|
"create-user": "tsx scripts/create-user.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@node-rs/argon2": "^2.0.2",
|
||||||
|
"@oslojs/crypto": "^1.0.1",
|
||||||
|
"@oslojs/encoding": "^1.1.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
"dotenv": "^17.4.1",
|
||||||
"drizzle-orm": "^0.38.4",
|
"drizzle-orm": "^0.38.4",
|
||||||
"pg": "^8.13.1",
|
"pg": "^8.13.1",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
@@ -32,6 +37,7 @@
|
|||||||
"svelte": "^5.19.0",
|
"svelte": "^5.19.0",
|
||||||
"svelte-check": "^4.1.4",
|
"svelte-check": "^4.1.4",
|
||||||
"tailwindcss": "^4.1.3",
|
"tailwindcss": "^4.1.3",
|
||||||
|
"tsx": "^4.21.0",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"vite": "^6.1.0"
|
"vite": "^6.1.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* Create a user for buildfor_life_repair.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* npx tsx scripts/create-user.ts <email> <password> [displayName]
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* npx tsx scripts/create-user.ts admin@b4l.co.th mypassword "Admin"
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'dotenv/config';
|
||||||
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||||
|
import pg from 'pg';
|
||||||
|
import { users } from '../src/lib/server/db/schema.js';
|
||||||
|
import { hash } from '@node-rs/argon2';
|
||||||
|
import { encodeBase32LowerCaseNoPadding } from '@oslojs/encoding';
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (args.length < 2) {
|
||||||
|
console.error('Usage: npx tsx scripts/create-user.ts <email> <password> [displayName]');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [email, password, displayName] = args;
|
||||||
|
|
||||||
|
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
|
||||||
|
const db = drizzle(pool);
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const id = encodeBase32LowerCaseNoPadding(crypto.getRandomValues(new Uint8Array(15)));
|
||||||
|
|
||||||
|
const passwordHash = await hash(password!, {
|
||||||
|
memoryCost: 19456,
|
||||||
|
timeCost: 2,
|
||||||
|
outputLen: 32,
|
||||||
|
parallelism: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.insert(users).values({
|
||||||
|
id,
|
||||||
|
email: email!.toLowerCase().trim(),
|
||||||
|
displayName: displayName || null,
|
||||||
|
passwordHash
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`User created: ${email} (id: ${id})`);
|
||||||
|
await pool.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error('Failed to create user:', err.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Vendored
+4
-1
@@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace App {
|
namespace App {
|
||||||
interface Locals {}
|
interface Locals {
|
||||||
|
user: { id: string; email: string; displayName: string | null } | null;
|
||||||
|
session: { id: string; userId: string; expiresAt: Date; fresh: boolean } | null;
|
||||||
|
}
|
||||||
interface PageData {}
|
interface PageData {}
|
||||||
interface Platform {}
|
interface Platform {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import type { Handle } from '@sveltejs/kit';
|
||||||
|
import { validateSession, setSessionCookie, deleteSessionCookie } from '$lib/server/auth/index.js';
|
||||||
|
|
||||||
|
export const handle: Handle = async ({ event, resolve }) => {
|
||||||
|
const token = event.cookies.get('session');
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
const { session, user } = await validateSession(token);
|
||||||
|
|
||||||
|
if (session && user) {
|
||||||
|
event.locals.user = user;
|
||||||
|
event.locals.session = session;
|
||||||
|
|
||||||
|
// Refresh cookie if session was extended
|
||||||
|
if (session.fresh) {
|
||||||
|
setSessionCookie(event, token, session.expiresAt);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
event.locals.user = null;
|
||||||
|
event.locals.session = null;
|
||||||
|
deleteSessionCookie(event);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
event.locals.user = null;
|
||||||
|
event.locals.session = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(event);
|
||||||
|
};
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import { sha256 } from '@oslojs/crypto/sha2';
|
||||||
|
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding';
|
||||||
|
import { db } from '$lib/server/db/index.js';
|
||||||
|
import { sessions, users } from '$lib/server/db/schema.js';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import type { RequestEvent } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
const SESSION_DURATION_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
|
||||||
|
const SESSION_REFRESH_MS = 15 * 24 * 60 * 60 * 1000; // 15 days
|
||||||
|
|
||||||
|
export function generateSessionToken(): string {
|
||||||
|
const bytes = new Uint8Array(20);
|
||||||
|
crypto.getRandomValues(bytes);
|
||||||
|
return encodeBase32LowerCaseNoPadding(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateUserId(): string {
|
||||||
|
const bytes = new Uint8Array(15);
|
||||||
|
crypto.getRandomValues(bytes);
|
||||||
|
return encodeBase32LowerCaseNoPadding(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hashToken(token: string): string {
|
||||||
|
const encoded = new TextEncoder().encode(token);
|
||||||
|
return encodeHexLowerCase(sha256(encoded));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createSession(token: string, userId: string) {
|
||||||
|
const id = hashToken(token);
|
||||||
|
const expiresAt = new Date(Date.now() + SESSION_DURATION_MS);
|
||||||
|
|
||||||
|
await db.insert(sessions).values({ id, userId, expiresAt });
|
||||||
|
|
||||||
|
return { id, userId, expiresAt, fresh: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validateSession(token: string) {
|
||||||
|
const id = hashToken(token);
|
||||||
|
|
||||||
|
const [result] = await db
|
||||||
|
.select({
|
||||||
|
sessionId: sessions.id,
|
||||||
|
userId: sessions.userId,
|
||||||
|
expiresAt: sessions.expiresAt,
|
||||||
|
userEmail: users.email,
|
||||||
|
userDisplayName: users.displayName
|
||||||
|
})
|
||||||
|
.from(sessions)
|
||||||
|
.innerJoin(users, eq(sessions.userId, users.id))
|
||||||
|
.where(eq(sessions.id, id));
|
||||||
|
|
||||||
|
if (!result) return { session: null, user: null };
|
||||||
|
|
||||||
|
if (result.expiresAt < new Date()) {
|
||||||
|
await db.delete(sessions).where(eq(sessions.id, id));
|
||||||
|
return { session: null, user: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = {
|
||||||
|
id: result.sessionId,
|
||||||
|
userId: result.userId,
|
||||||
|
expiresAt: result.expiresAt,
|
||||||
|
fresh: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// Refresh if older than 15 days
|
||||||
|
if (Date.now() - (result.expiresAt.getTime() - SESSION_DURATION_MS) > SESSION_REFRESH_MS) {
|
||||||
|
session.expiresAt = new Date(Date.now() + SESSION_DURATION_MS);
|
||||||
|
session.fresh = true;
|
||||||
|
await db
|
||||||
|
.update(sessions)
|
||||||
|
.set({ expiresAt: session.expiresAt })
|
||||||
|
.where(eq(sessions.id, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = {
|
||||||
|
id: result.userId,
|
||||||
|
email: result.userEmail,
|
||||||
|
displayName: result.userDisplayName
|
||||||
|
};
|
||||||
|
|
||||||
|
return { session, user };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function invalidateSession(token: string) {
|
||||||
|
const id = hashToken(token);
|
||||||
|
await db.delete(sessions).where(eq(sessions.id, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setSessionCookie(event: RequestEvent, token: string, expiresAt: Date) {
|
||||||
|
event.cookies.set('session', token, {
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: 'lax',
|
||||||
|
secure: event.url.protocol === 'https:',
|
||||||
|
path: '/',
|
||||||
|
expires: expiresAt
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteSessionCookie(event: RequestEvent) {
|
||||||
|
event.cookies.set('session', '', {
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: 'lax',
|
||||||
|
secure: event.url.protocol === 'https:',
|
||||||
|
path: '/',
|
||||||
|
maxAge: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { hash, verify } from '@node-rs/argon2';
|
||||||
|
|
||||||
|
const ARGON2_OPTIONS = {
|
||||||
|
memoryCost: 19456,
|
||||||
|
timeCost: 2,
|
||||||
|
outputLen: 32,
|
||||||
|
parallelism: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function hashPassword(password: string): Promise<string> {
|
||||||
|
return hash(password, ARGON2_OPTIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function verifyPassword(passwordHash: string, password: string): Promise<boolean> {
|
||||||
|
return verify(passwordHash, password, ARGON2_OPTIONS);
|
||||||
|
}
|
||||||
@@ -10,6 +10,25 @@ import {
|
|||||||
} from 'drizzle-orm/pg-core';
|
} from 'drizzle-orm/pg-core';
|
||||||
import { sql } from 'drizzle-orm';
|
import { sql } from 'drizzle-orm';
|
||||||
|
|
||||||
|
// ─── Users & Sessions ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
export const users = pgTable('users', {
|
||||||
|
id: text('id').primaryKey(),
|
||||||
|
email: text('email').unique().notNull(),
|
||||||
|
displayName: text('display_name'),
|
||||||
|
passwordHash: text('password_hash').notNull(),
|
||||||
|
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const sessions = pgTable('sessions', {
|
||||||
|
id: text('id').primaryKey(), // SHA-256 hash of the session token
|
||||||
|
userId: text('user_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id, { onDelete: 'cascade' }),
|
||||||
|
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull()
|
||||||
|
});
|
||||||
|
|
||||||
// ─── Locations ──────────────────────────────────────────────────────
|
// ─── Locations ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
export const locations = pgTable('locations', {
|
export const locations = pgTable('locations', {
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
import type { LayoutServerLoad } from './$types';
|
import type { LayoutServerLoad } from './$types';
|
||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
import { db } from '$lib/server/db/index.js';
|
import { db } from '$lib/server/db/index.js';
|
||||||
import { devices, components } from '$lib/server/db/schema.js';
|
import { devices, components } from '$lib/server/db/schema.js';
|
||||||
import { count, eq, or, and } from 'drizzle-orm';
|
import { count, eq, or, and } from 'drizzle-orm';
|
||||||
|
|
||||||
export const load: LayoutServerLoad = async () => {
|
export const load: LayoutServerLoad = async ({ locals }) => {
|
||||||
|
if (!locals.user) {
|
||||||
|
redirect(302, '/login');
|
||||||
|
}
|
||||||
|
|
||||||
const [deviceCount] = await db.select({ value: count() }).from(devices).where(eq(devices.disabled, false));
|
const [deviceCount] = await db.select({ value: count() }).from(devices).where(eq(devices.disabled, false));
|
||||||
const [componentCount] = await db.select({ value: count() }).from(components);
|
const [componentCount] = await db.select({ value: count() }).from(components);
|
||||||
const [repairCount] = await db
|
const [repairCount] = await db
|
||||||
@@ -12,6 +17,7 @@ export const load: LayoutServerLoad = async () => {
|
|||||||
.where(and(eq(devices.disabled, false), or(eq(devices.condition, 'In Repair'), eq(devices.condition, 'Waiting for Repair'))));
|
.where(and(eq(devices.disabled, false), or(eq(devices.condition, 'In Repair'), eq(devices.condition, 'Waiting for Repair'))));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
user: locals.user,
|
||||||
counts: {
|
counts: {
|
||||||
devices: deviceCount?.value ?? 0,
|
devices: deviceCount?.value ?? 0,
|
||||||
components: componentCount?.value ?? 0,
|
components: componentCount?.value ?? 0,
|
||||||
|
|||||||
@@ -37,6 +37,15 @@
|
|||||||
|
|
||||||
<div class="ml-auto flex items-center gap-3">
|
<div class="ml-auto flex items-center gap-3">
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
|
<span class="text-sm text-gray-700 dark:text-gray-300">{data.user.displayName ?? data.user.email}</span>
|
||||||
|
<form method="POST" action="/logout">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="rounded-md px-3 py-1.5 text-sm text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
|
||||||
|
>
|
||||||
|
Sign Out
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import '../../app.css';
|
||||||
|
import ThemeToggle from '$lib/components/layout/ThemeToggle.svelte';
|
||||||
|
|
||||||
|
let { children } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex min-h-screen items-center justify-center bg-gray-50 dark:bg-gray-900">
|
||||||
|
<div class="absolute top-4 right-4">
|
||||||
|
<ThemeToggle />
|
||||||
|
</div>
|
||||||
|
{@render children()}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import type { PageServerLoad, Actions } from './$types';
|
||||||
|
import { fail, redirect } from '@sveltejs/kit';
|
||||||
|
import { db } from '$lib/server/db/index.js';
|
||||||
|
import { users } from '$lib/server/db/schema.js';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { verifyPassword } from '$lib/server/auth/password.js';
|
||||||
|
import {
|
||||||
|
generateSessionToken,
|
||||||
|
createSession,
|
||||||
|
setSessionCookie
|
||||||
|
} from '$lib/server/auth/index.js';
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ locals }) => {
|
||||||
|
if (locals.user) {
|
||||||
|
redirect(302, '/');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions: Actions = {
|
||||||
|
default: async (event) => {
|
||||||
|
const formData = await event.request.formData();
|
||||||
|
const email = formData.get('email') as string;
|
||||||
|
const password = formData.get('password') as string;
|
||||||
|
|
||||||
|
if (!email || !password) {
|
||||||
|
return fail(400, { error: 'Email and password are required', email });
|
||||||
|
}
|
||||||
|
|
||||||
|
const [user] = await db
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.email, email.toLowerCase().trim()));
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return fail(400, { error: 'Invalid email or password', email });
|
||||||
|
}
|
||||||
|
|
||||||
|
const valid = await verifyPassword(user.passwordHash, password);
|
||||||
|
if (!valid) {
|
||||||
|
return fail(400, { error: 'Invalid email or password', email });
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = generateSessionToken();
|
||||||
|
const session = await createSession(token, user.id);
|
||||||
|
setSessionCookie(event, token, session.expiresAt);
|
||||||
|
|
||||||
|
redirect(302, '/');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { enhance } from '$app/forms';
|
||||||
|
|
||||||
|
let { form } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Sign In - B4L Repair</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<div class="w-full max-w-sm">
|
||||||
|
<div class="rounded-lg border border-gray-200 bg-white p-6 shadow-sm dark:border-gray-700 dark:bg-gray-800">
|
||||||
|
<h1 class="mb-6 text-center text-xl font-bold text-gray-900 dark:text-white">B4L Repair</h1>
|
||||||
|
|
||||||
|
{#if form?.error}
|
||||||
|
<div class="mb-4 rounded-md bg-red-50 p-3 text-sm text-red-700 dark:bg-red-900/30 dark:text-red-300">
|
||||||
|
{form.error}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<form method="POST" use:enhance class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label for="email" class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Email</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
required
|
||||||
|
autocomplete="email"
|
||||||
|
value={form?.email ?? ''}
|
||||||
|
class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="password" class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Password</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
required
|
||||||
|
autocomplete="current-password"
|
||||||
|
class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-full rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import type { PageServerLoad, Actions } from './$types';
|
||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
import { invalidateSession, deleteSessionCookie } from '$lib/server/auth/index.js';
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async () => {
|
||||||
|
redirect(302, '/login');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions: Actions = {
|
||||||
|
default: async (event) => {
|
||||||
|
const token = event.cookies.get('session');
|
||||||
|
if (token) {
|
||||||
|
await invalidateSession(token);
|
||||||
|
}
|
||||||
|
deleteSessionCookie(event);
|
||||||
|
redirect(302, '/login');
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user