diff --git a/.gitignore b/.gitignore index 6635cf5..ade934c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ node_modules !.env.example vite.config.js.timestamp-* vite.config.ts.timestamp-* +devdocker diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..63d29de --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + dialect: 'mysql', + schema: './src/lib/server/drizzle/schema.ts', + out: './src/lib/server/drizzle/migrations', + dbCredentials: { + host: process.env.DATABASE_HOST as string, + port: Number(process.env.DATABASE_PORT) || 3306, + database: process.env.DATABASE_DB as string, + user: process.env.DATABASE_USER as string, + password: process.env.DATABASE_PASS as string + }, + verbose: true, + strict: true +}) diff --git a/package-lock.json b/package-lock.json index cbfceed..b2b04b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,15 +8,26 @@ "name": "icysso", "version": "0.0.1", "dependencies": { - "@sveltejs/adapter-node": "^5.0.1" + "@sveltejs/adapter-node": "^5.0.1", + "bcryptjs": "^2.4.3", + "drizzle-orm": "^0.30.10", + "mysql2": "^3.9.7", + "otplib": "^12.0.1", + "svelte-kit-cookie-session": "^4.0.0", + "sveltekit-i18n": "^2.4.2", + "uuid": "^9.0.1" }, "devDependencies": { "@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@types/bcryptjs": "^2.4.6", "@types/eslint": "^8.56.0", + "@types/node": "^20.12.12", + "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", + "drizzle-kit": "^0.21.2", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "^2.35.1", @@ -41,6 +52,415 @@ "node": ">=6.0.0" } }, + "node_modules/@esbuild-kit/core-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", + "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", + "dev": true, + "dependencies": { + "esbuild": "~0.18.20", + "source-map-support": "^0.5.21" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/@esbuild-kit/esm-loader": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", + "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", + "dev": true, + "dependencies": { + "@esbuild-kit/core-utils": "^3.3.2", + "get-tsconfig": "^4.7.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", @@ -562,6 +982,14 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@noble/ciphers": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.2.0.tgz", + "integrity": "sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -597,6 +1025,48 @@ "node": ">= 8" } }, + "node_modules/@otplib/core": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz", + "integrity": "sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA==" + }, + "node_modules/@otplib/plugin-crypto": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto/-/plugin-crypto-12.0.1.tgz", + "integrity": "sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g==", + "dependencies": { + "@otplib/core": "^12.0.1" + } + }, + "node_modules/@otplib/plugin-thirty-two": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-thirty-two/-/plugin-thirty-two-12.0.1.tgz", + "integrity": "sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA==", + "dependencies": { + "@otplib/core": "^12.0.1", + "thirty-two": "^1.0.2" + } + }, + "node_modules/@otplib/preset-default": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/preset-default/-/preset-default-12.0.1.tgz", + "integrity": "sha512-xf1v9oOJRyXfluBhMdpOkr+bsE+Irt+0D5uHtvg6x1eosfmHCsCC6ej/m7FXiWqdo0+ZUI6xSKDhJwc8yfiOPQ==", + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1" + } + }, + "node_modules/@otplib/preset-v11": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/preset-v11/-/preset-v11-12.0.1.tgz", + "integrity": "sha512-9hSetMI7ECqbFiKICrNa4w70deTUfArtwXykPUvSHWOdzOlfa9ajglu7mNCntlvxycTiOAXkQGwjQCzzDEMRMg==", + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.25", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", @@ -1023,6 +1493,25 @@ "vite": "^5.0.0" } }, + "node_modules/@sveltekit-i18n/base": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@sveltekit-i18n/base/-/base-1.3.7.tgz", + "integrity": "sha512-kg1kql1/ro/lIudwFiWrv949Q07gmweln87tflUZR51MNdXXzK4fiJQv5Mw50K/CdQ5BOk/dJ0WOH2vOtBI6yw==", + "peerDependencies": { + "svelte": ">=3.49.0" + } + }, + "node_modules/@sveltekit-i18n/parser-default": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@sveltekit-i18n/parser-default/-/parser-default-1.1.1.tgz", + "integrity": "sha512-/gtzLlqm/sox7EoPKD56BxGZktK/syGc79EbJAPWY5KVitQD9SM0TP8yJCqDxTVPk7Lk0WJhrBGUE2Nn0f5M1w==" + }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true + }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", @@ -1049,6 +1538,15 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/node": { + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "devOptional": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/@types/pug": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz", @@ -1060,6 +1558,12 @@ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==" }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.9.0.tgz", @@ -1360,6 +1864,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1401,6 +1910,12 @@ "node": "*" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, "node_modules/builtin-modules": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", @@ -1473,6 +1988,22 @@ "node": ">= 6" } }, + "node_modules/cli-color": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.4.tgz", + "integrity": "sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.64", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.15", + "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/code-red": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", @@ -1503,6 +2034,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -1560,6 +2100,19 @@ "node": ">=4" } }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dev": true, + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -1590,6 +2143,14 @@ "node": ">=0.10.0" } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -1612,6 +2173,18 @@ "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.0.0.tgz", "integrity": "sha512-gO+/OMXF7488D+u3ue+G7Y4AA3ZmUnB3eHJXmBTgNHvr4ZNzl36A0ZtG+XCRNYCkYx/bFmw4qtkoFLa+wSrwAA==" }, + "node_modules/difflib": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz", + "integrity": "sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==", + "dev": true, + "dependencies": { + "heap": ">= 0.2.0" + }, + "engines": { + "node": "*" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1636,12 +2209,654 @@ "node": ">=6.0.0" } }, + "node_modules/dreamopt": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/dreamopt/-/dreamopt-0.8.0.tgz", + "integrity": "sha512-vyJTp8+mC+G+5dfgsY+r3ckxlz+QMX40VjPQsZc5gxVAxLmi64TBoVkP54A/pRAXMXsbu2GMMBrZPxNv23waMg==", + "dev": true, + "dependencies": { + "wordwrap": ">=0.0.2" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/drizzle-kit": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.21.2.tgz", + "integrity": "sha512-U87IhZyCt/9d0ZT/Na3KFJVY31tSxtTx/n9UMcWFpW/5c2Ede39xiCG5efNV/0iimsv97UIRtDI0ldLBW5lbcg==", + "dev": true, + "dependencies": { + "@esbuild-kit/esm-loader": "^2.5.5", + "commander": "^9.4.1", + "env-paths": "^3.0.0", + "esbuild": "^0.19.7", + "esbuild-register": "^3.5.0", + "glob": "^8.1.0", + "hanji": "^0.0.5", + "json-diff": "0.9.0", + "zod": "^3.20.2" + }, + "bin": { + "drizzle-kit": "bin.cjs" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/drizzle-kit/node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/drizzle-kit/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/drizzle-kit/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/drizzle-orm": { + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.30.10.tgz", + "integrity": "sha512-IRy/QmMWw9lAQHpwbUh1b8fcn27S/a9zMIzqea1WNOxK9/4EB8gIo+FZWLiPXzl2n9ixGSv8BhsLZiOppWEwBw==", + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=3", + "@electric-sql/pglite": ">=0.1.1", + "@libsql/client": "*", + "@neondatabase/serverless": ">=0.1", + "@op-engineering/op-sqlite": ">=2", + "@opentelemetry/api": "^1.4.1", + "@planetscale/database": ">=1", + "@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": ">=13.2.0", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "react": ">=18", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/react": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "react": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } + } + }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, "node_modules/es6-promise": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", "dev": true }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "dev": true, + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, "node_modules/esbuild": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", @@ -1679,6 +2894,18 @@ "@esbuild/win32-x64": "0.20.2" } }, + "node_modules/esbuild-register": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.5.0.tgz", + "integrity": "sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1863,6 +3090,21 @@ "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz", "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==" }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -1930,6 +3172,25 @@ "node": ">=0.10.0" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dev": true, + "dependencies": { + "type": "^2.7.2" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2071,6 +3332,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", + "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2182,6 +3463,16 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/hanji": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/hanji/-/hanji-0.0.5.tgz", + "integrity": "sha512-Abxw1Lq+TnYiL4BueXqMau222fPSPMFtya8HdpWsz/xVAhifXou71mPh/kY2+08RgFcVccjG3uZHs6K5HAe3zw==", + "dev": true, + "dependencies": { + "lodash.throttle": "^4.1.1", + "sisteransi": "^1.0.5" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2202,6 +3493,23 @@ "node": ">= 0.4" } }, + "node_modules/heap": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", + "dev": true + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -2340,6 +3648,17 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" + }, "node_modules/is-reference": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", @@ -2372,6 +3691,23 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-diff": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/json-diff/-/json-diff-0.9.0.tgz", + "integrity": "sha512-cVnggDrVkAAA3OvFfHpFEhOnmcsUpleEKq4d4O8sQWWSH40MBrWstKigVB1kGrgLWzuom+7rRdaCsnBD6VyObQ==", + "dev": true, + "dependencies": { + "cli-color": "^2.0.0", + "difflib": "~0.2.1", + "dreamopt": "~0.8.0" + }, + "bin": { + "json-diff": "bin/json-diff.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2455,6 +3791,34 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "dev": true + }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "engines": { + "node": ">=16.14" + } + }, + "node_modules/lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", + "dev": true, + "dependencies": { + "es5-ext": "~0.10.2" + } + }, "node_modules/magic-string": { "version": "0.30.10", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", @@ -2468,6 +3832,22 @@ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" }, + "node_modules/memoizee": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", + "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.53", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2556,6 +3936,43 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/mysql2": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.7.tgz", + "integrity": "sha512-KnJT8vYRcNAZv73uf9zpXqNbvBG7DJrs+1nACsjZP1HMJ1TgXEy8wnNilXAn/5i57JizXKtrUtwDB7HxT9DDpw==", + "dependencies": { + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru-cache": "^8.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -2579,6 +3996,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "dev": true + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2613,6 +4036,16 @@ "node": ">= 0.8.0" } }, + "node_modules/otplib": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/otplib/-/otplib-12.0.1.tgz", + "integrity": "sha512-xDGvUOQjop7RDgxTQ+o4pOol0/3xSZzawTiPKRrHnQWAy0WjhNs/5HdIDJCrqC4MBynmjXgULc6YfioaxZeFgg==", + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/preset-default": "^12.0.1", + "@otplib/preset-v11": "^12.0.1" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -2933,6 +4366,15 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -3026,6 +4468,11 @@ "node": ">=6" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/sander": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz", @@ -3062,6 +4509,11 @@ "node": ">=10" } }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, "node_modules/set-cookie-parser": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", @@ -3101,6 +4553,12 @@ "node": ">= 10" } }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -3125,6 +4583,15 @@ "sorcery": "bin/sorcery" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -3133,6 +4600,24 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -3276,6 +4761,14 @@ "svelte": "^3.19.0 || ^4.0.0" } }, + "node_modules/svelte-kit-cookie-session": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/svelte-kit-cookie-session/-/svelte-kit-cookie-session-4.0.0.tgz", + "integrity": "sha512-P7Og9z+cpwitS8JN9bBMxIFvX7kBM/Vrx4gDYbodO7qtT5C+EMJnNFpruvQgomL2UthzHYWLN/4aEgAGDgPTDA==", + "dependencies": { + "@noble/ciphers": "^0.2.0" + } + }, "node_modules/svelte-preprocess": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.4.tgz", @@ -3338,12 +4831,42 @@ } } }, + "node_modules/sveltekit-i18n": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/sveltekit-i18n/-/sveltekit-i18n-2.4.2.tgz", + "integrity": "sha512-hjRWn4V4DBL8JQKJoJa3MRvn6d32Zo+rWkoSP5bsQ/XIAguPdQUZJ8LMe6Nc1rST8WEVdu9+vZI3aFdKYGR3+Q==", + "dependencies": { + "@sveltekit-i18n/base": "~1.3.0", + "@sveltekit-i18n/parser-default": "~1.1.0" + }, + "peerDependencies": { + "svelte": ">=3.49.0" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/thirty-two": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", + "integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==", + "engines": { + "node": ">=0.2.6" + } + }, + "node_modules/timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "dev": true, + "dependencies": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, "node_modules/tiny-glob": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", @@ -3391,6 +4914,12 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, + "node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", + "dev": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3428,6 +4957,12 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "devOptional": true + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -3443,6 +4978,18 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vite": { "version": "5.2.11", "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz", @@ -3534,6 +5081,12 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3559,6 +5112,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 7f24d0a..dfe5550 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,13 @@ "@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@types/bcryptjs": "^2.4.6", "@types/eslint": "^8.56.0", + "@types/node": "^20.12.12", + "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", + "drizzle-kit": "^0.21.2", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "^2.35.1", @@ -31,6 +35,13 @@ }, "type": "module", "dependencies": { - "@sveltejs/adapter-node": "^5.0.1" + "@sveltejs/adapter-node": "^5.0.1", + "bcryptjs": "^2.4.3", + "drizzle-orm": "^0.30.10", + "mysql2": "^3.9.7", + "otplib": "^12.0.1", + "svelte-kit-cookie-session": "^4.0.0", + "sveltekit-i18n": "^2.4.2", + "uuid": "^9.0.1" } } diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..2082fc5 --- /dev/null +++ b/src/app.css @@ -0,0 +1,78 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +:root { + --in-text-color: #fff; + --in-link-color: #fff; + --in-outline-color: #00aaff; + --in-normalized-background: #000; + --in-input-background: #fff; + --in-input-color: #000; + --in-input-border-color: #ddd; + + --in-focus-outline: 3px solid var(--in-outline-color); +} + +:root { + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Open Sans', + 'Helvetica Neue', + sans-serif; + color: var(--in-text-color); +} + +html, +body { + margin: 0; + width: 100%; + height: 100%; +} + +body { + background-color: var(--in-normalized-background); + background-image: url('/background.jpg'); + background-attachment: fixed; + background-repeat: no-repeat; + background-size: cover; +} + +h1, +h2, +h3, +h4, +h5 { + margin-top: 0; +} + +a { + color: var(--in-link-color); + + &:visited { + color: var(--in-link-color); + } + + &:focus-visible { + outline: var(--in-focus-outline); + } +} + +a[target='_blank']::after { + content: ''; + background-image: url('data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 style=%27width:24px;height:24px%27 viewBox=%270 0 24 24%27%3E%3Cpath fill=%27%23ffffff%27 d=%27M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z%27 /%3E%3C/svg%3E'); + width: 0.95rem; + height: 0.95rem; + display: inline-block; + margin-left: 2px; + vertical-align: top; +} diff --git a/src/app.d.ts b/src/app.d.ts index 743f07b..d83caae 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,10 +1,24 @@ +import type { UserSession } from '$lib/server/users/types'; +import type { Session } from 'svelte-kit-cookie-session'; + +type SessionData = { + user?: UserSession; +} + // See https://kit.svelte.dev/docs/types#app // for information about these interfaces declare global { namespace App { // interface Error {} - // interface Locals {} - // interface PageData {} + + interface Locals { + session: Session; + } + + interface PageData { + session: SessionData; + } + // interface PageState {} // interface Platform {} } diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 0000000..508d8d1 --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,7 @@ +import { SESSION_SECRET } from '$env/static/private'; +import '$lib/server/drizzle'; +import { handleSession } from 'svelte-kit-cookie-session'; + +export const handle = handleSession({ + secret: SESSION_SECRET +}) diff --git a/src/lib/components/Button.svelte b/src/lib/components/Button.svelte new file mode 100644 index 0000000..1447963 --- /dev/null +++ b/src/lib/components/Button.svelte @@ -0,0 +1,36 @@ + + + + + diff --git a/src/lib/components/LogoutButton.svelte b/src/lib/components/LogoutButton.svelte new file mode 100644 index 0000000..d679617 --- /dev/null +++ b/src/lib/components/LogoutButton.svelte @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/lib/components/form/FormControl.svelte b/src/lib/components/form/FormControl.svelte new file mode 100644 index 0000000..48e87f5 --- /dev/null +++ b/src/lib/components/form/FormControl.svelte @@ -0,0 +1,33 @@ + +
+ +
+ + diff --git a/src/lib/components/form/FormSection.svelte b/src/lib/components/form/FormSection.svelte new file mode 100644 index 0000000..5c184a3 --- /dev/null +++ b/src/lib/components/form/FormSection.svelte @@ -0,0 +1,4 @@ + +
+ +
diff --git a/src/lib/components/form/FormWrapper.svelte b/src/lib/components/form/FormWrapper.svelte new file mode 100644 index 0000000..41d7244 --- /dev/null +++ b/src/lib/components/form/FormWrapper.svelte @@ -0,0 +1,10 @@ + +
+ +
+ + diff --git a/src/lib/i18n/en/account.json b/src/lib/i18n/en/account.json new file mode 100644 index 0000000..2fec6dd --- /dev/null +++ b/src/lib/i18n/en/account.json @@ -0,0 +1,28 @@ +{ + "username": "Username", + "displayName": "Display Name", + "changeEmail": "Change email address", + "currentEmail": "Current email address", + "newEmail": "New email address", + "changePassword": "Change password", + "currentPassword": "Current password", + "newPassword": "New password", + "repeatPassword": "Repeat new password", + "submit": "Submit", + "login": { + "title": "Log in", + "email": "Email", + "password": "Password", + "submit": "Log in" + }, + "errors": { + "invalidLogin": "Invalid email or password!", + "invalidRequest": "Invalid request! Please try again.", + "emailRequired": "Email address is required.", + "invalidEmail": "The email address is invalid.", + "passwordRequired": "The password is required.", + "passwordMismatch": "The passwords do not match!", + "invalidPassword": "The provided password is invalid.", + "invalidDisplayName": "The provided display name is invalid." + } +} diff --git a/src/lib/i18n/en/common.json b/src/lib/i18n/en/common.json new file mode 100644 index 0000000..76ae907 --- /dev/null +++ b/src/lib/i18n/en/common.json @@ -0,0 +1,5 @@ +{ + "siteName": "Icy Network", + "description": "Icy Network is a Single-Sign-On service used by other applications.", + "cookieDisclaimer": "The website may use temporary cookies for storing your login session and ensuring your security. This web service is completely open source and can be audited by anyone." +} diff --git a/src/lib/i18n/index.ts b/src/lib/i18n/index.ts new file mode 100644 index 0000000..c933657 --- /dev/null +++ b/src/lib/i18n/index.ts @@ -0,0 +1,18 @@ +import i18n from 'sveltekit-i18n'; + +const config = { + loaders: [ + { + locale: 'en', + key: 'common', + loader: async () => await import('./en/common.json') + }, + { + locale: 'en', + key: 'account', + loader: async () => await import('./en/account.json') + } + ] +}; + +export const { t, locale, locales, loading, loadTranslations } = new i18n(config); diff --git a/src/lib/server/challenge.ts b/src/lib/server/challenge.ts new file mode 100644 index 0000000..f34b5da --- /dev/null +++ b/src/lib/server/challenge.ts @@ -0,0 +1,79 @@ +import { Changesets } from './changesets'; +import { CryptoUtils } from './crypto-utils'; +import type { User } from './drizzle'; +import { TimeOTP } from './users/totp'; + +export interface ChallengeBody { + aud: string; + data: T; +} + +export class Challenge { + static async issueChallenge( + challenge: TChallenge, + recipient: string + ): Promise { + const body = >{ + aud: recipient, + data: challenge + }; + return CryptoUtils.encryptChallenge(body); + } + + static async verifyChallenge( + challenge: string, + recipient: string, + code: string, + secret: string + ) { + const { aud, data }: ChallengeBody = await CryptoUtils.decryptChallenge(challenge); + if (aud !== recipient) { + throw new Error('Invalid challenge'); + } + + if (!TimeOTP.validate(secret, code)) { + throw new Error('Invalid token'); + } + + return data; + } + + static async challengeFromBody( + body: FormData, + recipient: string, + secret: string + ): Promise { + const { challenge, otpCode } = Changesets.take<{ challenge?: string; otpCode?: string }>( + ['challenge', 'otpCode'], + body + ); + + if (!challenge || !otpCode) { + return; + } + + return Challenge.verifyChallenge(challenge, recipient, otpCode, secret); + } + + static async authorizedChanges( + fields: (keyof TRes)[], + body: FormData, + subject: User + ): Promise> { + if (!body.has('challenge')) { + return Changesets.take(fields, body); + } + + const userOtp = await TimeOTP.getUserOtp(subject); + if (!userOtp) { + throw new Error('Invalid request'); + } + + const data = await Challenge.challengeFromBody(body, subject.uuid, userOtp.token); + if (!data) { + throw new Error('Invalid request'); + } + + return data; + } +} diff --git a/src/lib/server/changesets.ts b/src/lib/server/changesets.ts new file mode 100644 index 0000000..154fbbc --- /dev/null +++ b/src/lib/server/changesets.ts @@ -0,0 +1,14 @@ +export class Changesets { + static take( + fields: (keyof TRes)[], + body: FormData, + challenge?: Partial + ): Partial { + return fields.reduce>((accum, field) => { + accum[field] = challenge + ? challenge[field] + : ((body.get(field as string) as string)?.trim() as TRes[typeof field]); + return accum; + }, {}); + } +} diff --git a/src/lib/server/crypto-utils.ts b/src/lib/server/crypto-utils.ts new file mode 100644 index 0000000..ba702ed --- /dev/null +++ b/src/lib/server/crypto-utils.ts @@ -0,0 +1,63 @@ +import { CHALLENGE_SECRET } from '$env/static/private'; +import * as crypto from 'crypto'; +import { v4 } from 'uuid'; + +const IV_LENGTH = 16; +const ALGORITHM = 'aes-256-cbc'; + +export class CryptoUtils { + public static generateString(length: number): string { + return crypto.randomBytes(length).toString('hex').slice(0, length); + } + + public static generateSecret(): string { + return crypto.randomBytes(256 / 8).toString('hex'); + } + + public static insecureHash(input: string): string { + return crypto.createHash('md5').update(input).digest('hex'); + } + + public static createUUID(): string { + return v4(); + } + + // https://stackoverflow.com/q/52212430 + /** + * Symmetric encryption function + * @param text String to encrypt + * @param key Encryption key + * @returns Encrypted text + */ + public static encrypt(text: string, key: string): string { + const iv = crypto.randomBytes(IV_LENGTH); + const cipher = crypto.createCipheriv(ALGORITHM, Buffer.from(key, 'hex'), iv); + let encrypted = cipher.update(text); + encrypted = Buffer.concat([encrypted, cipher.final()]); + return `${iv.toString('hex')}:${encrypted.toString('hex')}`; + } + + /** + * Symmetric decryption function + * @param text Encrypted string + * @param key Decryption key + * @returns Decrypted text + */ + public static decrypt(text: string, key: string): string { + const [iv, encryptedText] = text.split(':').map((part) => Buffer.from(part, 'hex')); + + const decipher = crypto.createDecipheriv(ALGORITHM, Buffer.from(key, 'hex'), iv); + + let decrypted = decipher.update(encryptedText); + decrypted = Buffer.concat([decrypted, decipher.final()]); + return decrypted.toString(); + } + + public static async encryptChallenge(challenge: T): Promise { + return this.encrypt(JSON.stringify(challenge), CHALLENGE_SECRET); + } + + public static async decryptChallenge(challenge: string): Promise { + return JSON.parse(this.decrypt(challenge, CHALLENGE_SECRET)); + } +} diff --git a/src/lib/server/drizzle/index.ts b/src/lib/server/drizzle/index.ts new file mode 100644 index 0000000..a1edfe4 --- /dev/null +++ b/src/lib/server/drizzle/index.ts @@ -0,0 +1,14 @@ +import { DATABASE_DB, DATABASE_HOST, DATABASE_PASS } from '$env/static/private'; +import { drizzle } from 'drizzle-orm/mysql2'; +import mysql from 'mysql2/promise'; +import * as schema from './schema'; + +const connection = await mysql.createConnection({ + host: DATABASE_HOST, + user: DATABASE_PASS, + password: DATABASE_PASS, + database: DATABASE_DB +}); + +export const db = drizzle(connection, { schema, mode: 'default' }); +export * from './schema'; diff --git a/src/lib/server/drizzle/migrations/0000_initial.sql b/src/lib/server/drizzle/migrations/0000_initial.sql new file mode 100644 index 0000000..04eb263 --- /dev/null +++ b/src/lib/server/drizzle/migrations/0000_initial.sql @@ -0,0 +1,137 @@ +-- Current sql file was generated after introspecting the database +-- If you want to run this migration please uncomment this code before executing migrations +/* +CREATE TABLE `audit_log` ( + `id` int(11) AUTO_INCREMENT NOT NULL, + `action` text NOT NULL, + `content` text DEFAULT 'NULL', + `actor_ip` text DEFAULT 'NULL', + `actor_ua` text DEFAULT 'NULL', + `flagged` tinyint NOT NULL DEFAULT 0, + `created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)', + `actorId` int(11) DEFAULT 'NULL' +); +--> statement-breakpoint +CREATE TABLE `document` ( + `id` int(11) AUTO_INCREMENT NOT NULL, + `title` text NOT NULL, + `slug` text NOT NULL, + `body` text NOT NULL, + `authorId` int(11) DEFAULT 'NULL', + `created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)', + `updated_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)' +); +--> statement-breakpoint +CREATE TABLE `o_auth2_client` ( + `id` int(11) AUTO_INCREMENT NOT NULL, + `client_id` varchar(36) NOT NULL, + `client_secret` text NOT NULL, + `title` varchar(255) NOT NULL, + `description` text DEFAULT 'NULL', + `scope` text DEFAULT 'NULL', + `grants` text NOT NULL DEFAULT ''authorization_code'', + `activated` tinyint NOT NULL DEFAULT 0, + `verified` tinyint NOT NULL DEFAULT 0, + `pictureId` int(11) DEFAULT 'NULL', + `ownerId` int(11) DEFAULT 'NULL', + `created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)', + `updated_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)', + CONSTRAINT `IDX_e9d16c213910ad57bd05e97b42` UNIQUE(`client_id`) +); +--> statement-breakpoint +CREATE TABLE `o_auth2_client_authorization` ( + `id` int(11) AUTO_INCREMENT NOT NULL, + `scope` text DEFAULT 'NULL', + `expires_at` timestamp NOT NULL DEFAULT 'current_timestamp()', + `clientId` int(11) DEFAULT 'NULL', + `userId` int(11) DEFAULT 'NULL', + `created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)' +); +--> statement-breakpoint +CREATE TABLE `o_auth2_client_url` ( + `id` int(11) AUTO_INCREMENT NOT NULL, + `url` varchar(255) NOT NULL, + `type` enum('redirect_uri','terms','privacy','website') NOT NULL, + `created_at` timestamp(6) NOT NULL DEFAULT 'current_timestamp(6)', + `updated_at` timestamp(6) NOT NULL DEFAULT 'current_timestamp(6)', + `clientId` int(11) DEFAULT 'NULL' +); +--> statement-breakpoint +CREATE TABLE `o_auth2_token` ( + `id` int(11) AUTO_INCREMENT NOT NULL, + `type` enum('code','access_token','refresh_token') NOT NULL, + `token` text NOT NULL, + `scope` text DEFAULT 'NULL', + `expires_at` timestamp NOT NULL DEFAULT 'current_timestamp()', + `userId` int(11) DEFAULT 'NULL', + `clientId` int(11) DEFAULT 'NULL', + `nonce` text DEFAULT 'NULL', + `created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)', + `updated_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)', + `pcke` text DEFAULT 'NULL' +); +--> statement-breakpoint +CREATE TABLE `privilege` ( + `id` int(11) AUTO_INCREMENT NOT NULL, + `name` text NOT NULL +); +--> statement-breakpoint +CREATE TABLE `upload` ( + `id` int(11) AUTO_INCREMENT NOT NULL, + `original_name` varchar(255) NOT NULL, + `mimetype` varchar(255) NOT NULL, + `file` varchar(255) NOT NULL, + `uploaderId` int(11) DEFAULT 'NULL', + `created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)', + `updated_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)' +); +--> statement-breakpoint +CREATE TABLE `user` ( + `id` int(11) AUTO_INCREMENT NOT NULL, + `uuid` varchar(36) NOT NULL, + `username` varchar(26) NOT NULL, + `email` varchar(255) NOT NULL, + `display_name` varchar(32) NOT NULL, + `password` text DEFAULT 'NULL', + `activated` tinyint NOT NULL DEFAULT 0, + `activity_at` timestamp NOT NULL DEFAULT 'current_timestamp()', + `pictureId` int(11) DEFAULT 'NULL', + `created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)', + `updated_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)', + CONSTRAINT `IDX_a95e949168be7b7ece1a2382fe` UNIQUE(`uuid`), + CONSTRAINT `IDX_78a916df40e02a9deb1c4b75ed` UNIQUE(`username`), + CONSTRAINT `IDX_e12875dfb3b1d92d7d7c5377e2` UNIQUE(`email`) +); +--> statement-breakpoint +CREATE TABLE `user_privileges_privilege` ( + `userId` int(11) NOT NULL, + `privilegeId` int(11) NOT NULL +); +--> statement-breakpoint +CREATE TABLE `user_token` ( + `id` int(11) AUTO_INCREMENT NOT NULL, + `token` text NOT NULL, + `type` enum('generic','activation','deactivation','password','login','gdpr','totp','public_key','recovery') NOT NULL, + `expires_at` timestamp DEFAULT 'NULL', + `userId` int(11) DEFAULT 'NULL', + `nonce` text DEFAULT 'NULL', + `created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)' +); +--> statement-breakpoint +ALTER TABLE `audit_log` ADD CONSTRAINT `FK_cb6aa6f6fd56f08eafb60316225` FOREIGN KEY (`actorId`) REFERENCES `user`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE `document` ADD CONSTRAINT `FK_6a2eb13cadfc503989cbe367572` FOREIGN KEY (`authorId`) REFERENCES `user`(`id`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE `o_auth2_client` ADD CONSTRAINT `FK_4a6c878506b872e85b3d07f6252` FOREIGN KEY (`ownerId`) REFERENCES `user`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE `o_auth2_client` ADD CONSTRAINT `FK_e8d65b1eec13474e493420517d7` FOREIGN KEY (`pictureId`) REFERENCES `upload`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE `o_auth2_client_authorization` ADD CONSTRAINT `FK_8227110f58510b7233f3db90cfb` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE `o_auth2_client_authorization` ADD CONSTRAINT `FK_9ca9ebb654e7ce71954d5fdb281` FOREIGN KEY (`clientId`) REFERENCES `o_auth2_client`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE `o_auth2_client_url` ADD CONSTRAINT `FK_aca59c7bdd65987487eea98d00f` FOREIGN KEY (`clientId`) REFERENCES `o_auth2_client`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE `o_auth2_token` ADD CONSTRAINT `FK_3ecb760b321ef9bbab635f05b45` FOREIGN KEY (`clientId`) REFERENCES `o_auth2_client`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE `o_auth2_token` ADD CONSTRAINT `FK_81ffb9b8d672cf3af1af9e789f3` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE `upload` ADD CONSTRAINT `FK_7b8d52838a953b188255682597b` FOREIGN KEY (`uploaderId`) REFERENCES `user`(`id`) ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE `user` ADD CONSTRAINT `FK_7478a15985dbfa32ed5fc77a7a1` FOREIGN KEY (`pictureId`) REFERENCES `upload`(`id`) ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE `user_privileges_privilege` ADD CONSTRAINT `FK_0664a7ff494a1859a09014c0f17` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE `user_privileges_privilege` ADD CONSTRAINT `FK_e71171f4ed20bc8564a1819d0b7` FOREIGN KEY (`privilegeId`) REFERENCES `privilege`(`id`) ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE `user_token` ADD CONSTRAINT `FK_d37db50eecdf9b8ce4eedd2f918` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX `IDX_0664a7ff494a1859a09014c0f1` ON `user_privileges_privilege` (`userId`);--> statement-breakpoint +CREATE INDEX `IDX_e71171f4ed20bc8564a1819d0b` ON `user_privileges_privilege` (`privilegeId`); +*/ diff --git a/src/lib/server/drizzle/migrations/meta/0000_snapshot.json b/src/lib/server/drizzle/migrations/meta/0000_snapshot.json new file mode 100644 index 0000000..04d3a05 --- /dev/null +++ b/src/lib/server/drizzle/migrations/meta/0000_snapshot.json @@ -0,0 +1,985 @@ +{ + "id": "00000000-0000-0000-0000-000000000000", + "prevId": "", + "version": "5", + "dialect": "mysql", + "tables": { + "audit_log": { + "name": "audit_log", + "columns": { + "id": { + "autoincrement": true, + "name": "id", + "type": "int(11)", + "primaryKey": false, + "notNull": true + }, + "action": { + "autoincrement": false, + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "default": "'NULL'", + "autoincrement": false, + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_ip": { + "default": "'NULL'", + "autoincrement": false, + "name": "actor_ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_ua": { + "default": "'NULL'", + "autoincrement": false, + "name": "actor_ua", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "flagged": { + "default": 0, + "autoincrement": false, + "name": "flagged", + "type": "tinyint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "default": "'current_timestamp(6)'", + "autoincrement": false, + "name": "created_at", + "type": "datetime(6)", + "primaryKey": false, + "notNull": true + }, + "actorId": { + "default": "'NULL'", + "autoincrement": false, + "name": "actorId", + "type": "int(11)", + "primaryKey": false, + "notNull": false + } + }, + "compositePrimaryKeys": {}, + "indexes": {}, + "foreignKeys": { + "FK_cb6aa6f6fd56f08eafb60316225": { + "name": "FK_cb6aa6f6fd56f08eafb60316225", + "tableFrom": "audit_log", + "tableTo": "user", + "columnsFrom": [ + "actorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "uniqueConstraints": {} + }, + "document": { + "name": "document", + "columns": { + "id": { + "autoincrement": true, + "name": "id", + "type": "int(11)", + "primaryKey": false, + "notNull": true + }, + "title": { + "autoincrement": false, + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "autoincrement": false, + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "autoincrement": false, + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "authorId": { + "default": "'NULL'", + "autoincrement": false, + "name": "authorId", + "type": "int(11)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "default": "'current_timestamp(6)'", + "autoincrement": false, + "name": "created_at", + "type": "datetime(6)", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "default": "'current_timestamp(6)'", + "autoincrement": false, + "name": "updated_at", + "type": "datetime(6)", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": {}, + "foreignKeys": { + "FK_6a2eb13cadfc503989cbe367572": { + "name": "FK_6a2eb13cadfc503989cbe367572", + "tableFrom": "document", + "tableTo": "user", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "uniqueConstraints": {} + }, + "migrations": { + "name": "migrations", + "columns": { + "id": { + "autoincrement": true, + "name": "id", + "type": "int(11)", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "autoincrement": false, + "name": "timestamp", + "type": "bigint(20)", + "primaryKey": false, + "notNull": true + }, + "name": { + "autoincrement": false, + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": {}, + "foreignKeys": {}, + "uniqueConstraints": {} + }, + "o_auth2_client": { + "name": "o_auth2_client", + "columns": { + "id": { + "autoincrement": true, + "name": "id", + "type": "int(11)", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "autoincrement": false, + "name": "client_id", + "type": "varchar(36)", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "autoincrement": false, + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "autoincrement": false, + "name": "title", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "default": "'NULL'", + "autoincrement": false, + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "default": "'NULL'", + "autoincrement": false, + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grants": { + "default": "''authorization_code''", + "autoincrement": false, + "name": "grants", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "activated": { + "default": 0, + "autoincrement": false, + "name": "activated", + "type": "tinyint", + "primaryKey": false, + "notNull": true + }, + "verified": { + "default": 0, + "autoincrement": false, + "name": "verified", + "type": "tinyint", + "primaryKey": false, + "notNull": true + }, + "pictureId": { + "default": "'NULL'", + "autoincrement": false, + "name": "pictureId", + "type": "int(11)", + "primaryKey": false, + "notNull": false + }, + "ownerId": { + "default": "'NULL'", + "autoincrement": false, + "name": "ownerId", + "type": "int(11)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "default": "'current_timestamp(6)'", + "autoincrement": false, + "name": "created_at", + "type": "datetime(6)", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "default": "'current_timestamp(6)'", + "autoincrement": false, + "name": "updated_at", + "type": "datetime(6)", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": {}, + "foreignKeys": { + "FK_4a6c878506b872e85b3d07f6252": { + "name": "FK_4a6c878506b872e85b3d07f6252", + "tableFrom": "o_auth2_client", + "tableTo": "user", + "columnsFrom": [ + "ownerId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "FK_e8d65b1eec13474e493420517d7": { + "name": "FK_e8d65b1eec13474e493420517d7", + "tableFrom": "o_auth2_client", + "tableTo": "upload", + "columnsFrom": [ + "pictureId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "uniqueConstraints": { + "IDX_e9d16c213910ad57bd05e97b42": { + "name": "IDX_e9d16c213910ad57bd05e97b42", + "columns": [ + "client_id" + ] + } + } + }, + "o_auth2_client_authorization": { + "name": "o_auth2_client_authorization", + "columns": { + "id": { + "autoincrement": true, + "name": "id", + "type": "int(11)", + "primaryKey": false, + "notNull": true + }, + "scope": { + "default": "'NULL'", + "autoincrement": false, + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "default": "'current_timestamp()'", + "autoincrement": false, + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "clientId": { + "default": "'NULL'", + "autoincrement": false, + "name": "clientId", + "type": "int(11)", + "primaryKey": false, + "notNull": false + }, + "userId": { + "default": "'NULL'", + "autoincrement": false, + "name": "userId", + "type": "int(11)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "default": "'current_timestamp(6)'", + "autoincrement": false, + "name": "created_at", + "type": "datetime(6)", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": {}, + "foreignKeys": { + "FK_8227110f58510b7233f3db90cfb": { + "name": "FK_8227110f58510b7233f3db90cfb", + "tableFrom": "o_auth2_client_authorization", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "FK_9ca9ebb654e7ce71954d5fdb281": { + "name": "FK_9ca9ebb654e7ce71954d5fdb281", + "tableFrom": "o_auth2_client_authorization", + "tableTo": "o_auth2_client", + "columnsFrom": [ + "clientId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "uniqueConstraints": {} + }, + "o_auth2_client_url": { + "name": "o_auth2_client_url", + "columns": { + "id": { + "autoincrement": true, + "name": "id", + "type": "int(11)", + "primaryKey": false, + "notNull": true + }, + "url": { + "autoincrement": false, + "name": "url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "autoincrement": false, + "name": "type", + "type": "enum('redirect_uri','terms','privacy','website')", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "default": "'current_timestamp(6)'", + "autoincrement": false, + "name": "created_at", + "type": "timestamp(6)", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "default": "'current_timestamp(6)'", + "autoincrement": false, + "name": "updated_at", + "type": "timestamp(6)", + "primaryKey": false, + "notNull": true + }, + "clientId": { + "default": "'NULL'", + "autoincrement": false, + "name": "clientId", + "type": "int(11)", + "primaryKey": false, + "notNull": false + } + }, + "compositePrimaryKeys": {}, + "indexes": {}, + "foreignKeys": { + "FK_aca59c7bdd65987487eea98d00f": { + "name": "FK_aca59c7bdd65987487eea98d00f", + "tableFrom": "o_auth2_client_url", + "tableTo": "o_auth2_client", + "columnsFrom": [ + "clientId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "uniqueConstraints": {} + }, + "o_auth2_token": { + "name": "o_auth2_token", + "columns": { + "id": { + "autoincrement": true, + "name": "id", + "type": "int(11)", + "primaryKey": false, + "notNull": true + }, + "type": { + "autoincrement": false, + "name": "type", + "type": "enum('code','access_token','refresh_token')", + "primaryKey": false, + "notNull": true + }, + "token": { + "autoincrement": false, + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "default": "'NULL'", + "autoincrement": false, + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "default": "'current_timestamp()'", + "autoincrement": false, + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "userId": { + "default": "'NULL'", + "autoincrement": false, + "name": "userId", + "type": "int(11)", + "primaryKey": false, + "notNull": false + }, + "clientId": { + "default": "'NULL'", + "autoincrement": false, + "name": "clientId", + "type": "int(11)", + "primaryKey": false, + "notNull": false + }, + "nonce": { + "default": "'NULL'", + "autoincrement": false, + "name": "nonce", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "default": "'current_timestamp(6)'", + "autoincrement": false, + "name": "created_at", + "type": "datetime(6)", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "default": "'current_timestamp(6)'", + "autoincrement": false, + "name": "updated_at", + "type": "datetime(6)", + "primaryKey": false, + "notNull": true + }, + "pcke": { + "default": "'NULL'", + "autoincrement": false, + "name": "pcke", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "compositePrimaryKeys": {}, + "indexes": {}, + "foreignKeys": { + "FK_3ecb760b321ef9bbab635f05b45": { + "name": "FK_3ecb760b321ef9bbab635f05b45", + "tableFrom": "o_auth2_token", + "tableTo": "o_auth2_client", + "columnsFrom": [ + "clientId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "FK_81ffb9b8d672cf3af1af9e789f3": { + "name": "FK_81ffb9b8d672cf3af1af9e789f3", + "tableFrom": "o_auth2_token", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "uniqueConstraints": {} + }, + "privilege": { + "name": "privilege", + "columns": { + "id": { + "autoincrement": true, + "name": "id", + "type": "int(11)", + "primaryKey": false, + "notNull": true + }, + "name": { + "autoincrement": false, + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": {}, + "foreignKeys": {}, + "uniqueConstraints": {} + }, + "upload": { + "name": "upload", + "columns": { + "id": { + "autoincrement": true, + "name": "id", + "type": "int(11)", + "primaryKey": false, + "notNull": true + }, + "original_name": { + "autoincrement": false, + "name": "original_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "mimetype": { + "autoincrement": false, + "name": "mimetype", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "file": { + "autoincrement": false, + "name": "file", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "uploaderId": { + "default": "'NULL'", + "autoincrement": false, + "name": "uploaderId", + "type": "int(11)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "default": "'current_timestamp(6)'", + "autoincrement": false, + "name": "created_at", + "type": "datetime(6)", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "default": "'current_timestamp(6)'", + "autoincrement": false, + "name": "updated_at", + "type": "datetime(6)", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": {}, + "foreignKeys": { + "FK_7b8d52838a953b188255682597b": { + "name": "FK_7b8d52838a953b188255682597b", + "tableFrom": "upload", + "tableTo": "user", + "columnsFrom": [ + "uploaderId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "autoincrement": true, + "name": "id", + "type": "int(11)", + "primaryKey": false, + "notNull": true + }, + "uuid": { + "autoincrement": false, + "name": "uuid", + "type": "varchar(36)", + "primaryKey": false, + "notNull": true + }, + "username": { + "autoincrement": false, + "name": "username", + "type": "varchar(26)", + "primaryKey": false, + "notNull": true + }, + "email": { + "autoincrement": false, + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "autoincrement": false, + "name": "display_name", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "password": { + "default": "'NULL'", + "autoincrement": false, + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activated": { + "default": 0, + "autoincrement": false, + "name": "activated", + "type": "tinyint", + "primaryKey": false, + "notNull": true + }, + "activity_at": { + "default": "'current_timestamp()'", + "autoincrement": false, + "name": "activity_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "pictureId": { + "default": "'NULL'", + "autoincrement": false, + "name": "pictureId", + "type": "int(11)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "default": "'current_timestamp(6)'", + "autoincrement": false, + "name": "created_at", + "type": "datetime(6)", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "default": "'current_timestamp(6)'", + "autoincrement": false, + "name": "updated_at", + "type": "datetime(6)", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": {}, + "foreignKeys": { + "FK_7478a15985dbfa32ed5fc77a7a1": { + "name": "FK_7478a15985dbfa32ed5fc77a7a1", + "tableFrom": "user", + "tableTo": "upload", + "columnsFrom": [ + "pictureId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "uniqueConstraints": { + "IDX_a95e949168be7b7ece1a2382fe": { + "name": "IDX_a95e949168be7b7ece1a2382fe", + "columns": [ + "uuid" + ] + }, + "IDX_78a916df40e02a9deb1c4b75ed": { + "name": "IDX_78a916df40e02a9deb1c4b75ed", + "columns": [ + "username" + ] + }, + "IDX_e12875dfb3b1d92d7d7c5377e2": { + "name": "IDX_e12875dfb3b1d92d7d7c5377e2", + "columns": [ + "email" + ] + } + } + }, + "user_privileges_privilege": { + "name": "user_privileges_privilege", + "columns": { + "userId": { + "autoincrement": false, + "name": "userId", + "type": "int(11)", + "primaryKey": false, + "notNull": true + }, + "privilegeId": { + "autoincrement": false, + "name": "privilegeId", + "type": "int(11)", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": { + "IDX_0664a7ff494a1859a09014c0f1": { + "name": "IDX_0664a7ff494a1859a09014c0f1", + "columns": [ + "userId" + ], + "isUnique": false + }, + "IDX_e71171f4ed20bc8564a1819d0b": { + "name": "IDX_e71171f4ed20bc8564a1819d0b", + "columns": [ + "privilegeId" + ], + "isUnique": false + } + }, + "foreignKeys": { + "FK_0664a7ff494a1859a09014c0f17": { + "name": "FK_0664a7ff494a1859a09014c0f17", + "tableFrom": "user_privileges_privilege", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "FK_e71171f4ed20bc8564a1819d0b7": { + "name": "FK_e71171f4ed20bc8564a1819d0b7", + "tableFrom": "user_privileges_privilege", + "tableTo": "privilege", + "columnsFrom": [ + "privilegeId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "uniqueConstraints": {} + }, + "user_token": { + "name": "user_token", + "columns": { + "id": { + "autoincrement": true, + "name": "id", + "type": "int(11)", + "primaryKey": false, + "notNull": true + }, + "token": { + "autoincrement": false, + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "autoincrement": false, + "name": "type", + "type": "enum('generic','activation','deactivation','password','login','gdpr','totp','public_key','recovery')", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "default": "'NULL'", + "autoincrement": false, + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "userId": { + "default": "'NULL'", + "autoincrement": false, + "name": "userId", + "type": "int(11)", + "primaryKey": false, + "notNull": false + }, + "nonce": { + "default": "'NULL'", + "autoincrement": false, + "name": "nonce", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "default": "'current_timestamp(6)'", + "autoincrement": false, + "name": "created_at", + "type": "datetime(6)", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": {}, + "foreignKeys": { + "FK_d37db50eecdf9b8ce4eedd2f918": { + "name": "FK_d37db50eecdf9b8ce4eedd2f918", + "tableFrom": "user_token", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "uniqueConstraints": {} + } + }, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {} + } +} \ No newline at end of file diff --git a/src/lib/server/drizzle/migrations/meta/_journal.json b/src/lib/server/drizzle/migrations/meta/_journal.json new file mode 100644 index 0000000..1cde4a2 --- /dev/null +++ b/src/lib/server/drizzle/migrations/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "6", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "5", + "when": 1715871691221, + "tag": "0000_initial", + "breakpoints": true + } + ] +} diff --git a/src/lib/server/drizzle/migrations/relations.ts b/src/lib/server/drizzle/migrations/relations.ts new file mode 100644 index 0000000..b0ceb00 --- /dev/null +++ b/src/lib/server/drizzle/migrations/relations.ts @@ -0,0 +1,129 @@ +import { relations } from 'drizzle-orm/relations'; +import { + user, + auditLog, + document, + oauth2Client, + upload, + oauth2ClientAuthorization, + oauth2ClientUrl, + oauth2Token, + userPrivilegesPrivilege, + privilege, + userToken +} from '../schema'; + +export const auditLogRelations = relations(auditLog, ({ one }) => ({ + user: one(user, { + fields: [auditLog.actorId], + references: [user.id] + }) +})); + +export const userRelations = relations(user, ({ one, many }) => ({ + audit_logs: many(auditLog), + documents: many(document), + o_auth2_clients: many(oauth2Client), + o_auth2_client_authorizations: many(oauth2ClientAuthorization), + o_auth2_tokens: many(oauth2Token), + uploads: many(upload, { + relationName: 'upload_uploaderId_user_id' + }), + upload: one(upload, { + fields: [user.pictureId], + references: [upload.id], + relationName: 'user_pictureId_upload_id' + }), + user_privileges_privileges: many(userPrivilegesPrivilege), + user_tokens: many(userToken) +})); + +export const documentRelations = relations(document, ({ one }) => ({ + user: one(user, { + fields: [document.authorId], + references: [user.id] + }) +})); + +export const oauth2ClientRelations = relations(oauth2Client, ({ one, many }) => ({ + user: one(user, { + fields: [oauth2Client.ownerId], + references: [user.id] + }), + upload: one(upload, { + fields: [oauth2Client.pictureId], + references: [upload.id] + }), + o_auth2_client_authorizations: many(oauth2ClientAuthorization), + o_auth2_client_urls: many(oauth2ClientUrl), + o_auth2_tokens: many(oauth2Token) +})); + +export const uploadRelations = relations(upload, ({ one, many }) => ({ + o_auth2_clients: many(oauth2Client), + user: one(user, { + fields: [upload.uploaderId], + references: [user.id], + relationName: 'upload_uploaderId_user_id' + }), + users: many(user, { + relationName: 'user_pictureId_upload_id' + }) +})); + +export const oauth2ClientAuthorizationRelations = relations( + oauth2ClientAuthorization, + ({ one }) => ({ + user: one(user, { + fields: [oauth2ClientAuthorization.userId], + references: [user.id] + }), + o_auth2_client: one(oauth2Client, { + fields: [oauth2ClientAuthorization.clientId], + references: [oauth2Client.id] + }) + }) +); + +export const oauth2ClientUrlRelations = relations(oauth2ClientUrl, ({ one }) => ({ + o_auth2_client: one(oauth2Client, { + fields: [oauth2ClientUrl.clientId], + references: [oauth2Client.id] + }) +})); + +export const oauth2TokenRelations = relations(oauth2Token, ({ one }) => ({ + o_auth2_client: one(oauth2Client, { + fields: [oauth2Token.clientId], + references: [oauth2Client.id] + }), + user: one(user, { + fields: [oauth2Token.userId], + references: [user.id] + }) +})); + +export const userPrivilegesPrivilegeRelations = relations( + userPrivilegesPrivilege, + ({ one }) => ({ + user: one(user, { + fields: [userPrivilegesPrivilege.userId], + references: [user.id] + }), + privilege: one(privilege, { + fields: [userPrivilegesPrivilege.privilegeId], + references: [privilege.id] + }) + }) +); + +export const privilegeRelations = relations(privilege, ({ many }) => ({ + user_privileges_privileges: many(userPrivilegesPrivilege) +})); + +export const userTokenRelations = relations(userToken, ({ one }) => ({ + user: one(user, { + fields: [userToken.userId], + references: [user.id] + }) +})); diff --git a/src/lib/server/drizzle/schema.ts b/src/lib/server/drizzle/schema.ts new file mode 100644 index 0000000..9552f46 --- /dev/null +++ b/src/lib/server/drizzle/schema.ts @@ -0,0 +1,208 @@ +import { + mysqlTable, + int, + text, + tinyint, + datetime, + varchar, + unique, + timestamp, + mysqlEnum, + index, + type AnyMySqlColumn, +} from 'drizzle-orm/mysql-core'; + +export const auditLog = mysqlTable('audit_log', { + id: int('id').autoincrement().notNull(), + action: text('action').notNull(), + content: text('content'), + actor_ip: text('actor_ip'), + actor_ua: text('actor_ua'), + flagged: tinyint('flagged').default(0).notNull(), + created_at: datetime('created_at', { mode: 'string', fsp: 6 }) + .default('current_timestamp(6)') + .notNull(), + actorId: int('actorId').references(() => user.id, { onDelete: 'set null' }) +}); + +export const document = mysqlTable('document', { + id: int('id').autoincrement().notNull(), + title: text('title').notNull(), + slug: text('slug').notNull(), + body: text('body').notNull(), + authorId: int('authorId').references(() => user.id), + created_at: datetime('created_at', { mode: 'string', fsp: 6 }) + .default('current_timestamp(6)') + .notNull(), + updated_at: datetime('updated_at', { mode: 'string', fsp: 6 }) + .default('current_timestamp(6)') + .notNull() +}); + +export const oauth2Client = mysqlTable( + 'o_auth2_client', + { + id: int('id').autoincrement().notNull(), + client_id: varchar('client_id', { length: 36 }).notNull(), + client_secret: text('client_secret').notNull(), + title: varchar('title', { length: 255 }).notNull(), + description: text('description'), + scope: text('scope'), + grants: text('grants').default('authorization_code').notNull(), + activated: tinyint('activated').default(0).notNull(), + verified: tinyint('verified').default(0).notNull(), + pictureId: int('pictureId').references(() => upload.id, { onDelete: 'set null' }), + ownerId: int('ownerId').references(() => user.id, { onDelete: 'set null' }), + created_at: datetime('created_at', { mode: 'string', fsp: 6 }) + .default('current_timestamp(6)') + .notNull(), + updated_at: datetime('updated_at', { mode: 'string', fsp: 6 }) + .default('current_timestamp(6)') + .notNull() + }, + (table) => { + return { + IDX_e9d16c213910ad57bd05e97b42: unique('IDX_e9d16c213910ad57bd05e97b42').on(table.client_id) + }; + } +); + +export const oauth2ClientAuthorization = mysqlTable('o_auth2_client_authorization', { + id: int('id').autoincrement().notNull(), + scope: text('scope'), + expires_at: timestamp('expires_at', { mode: 'string' }).default('current_timestamp()').notNull(), + clientId: int('clientId').references(() => oauth2Client.id, { onDelete: 'cascade' }), + userId: int('userId').references(() => user.id, { onDelete: 'cascade' }), + created_at: datetime('created_at', { mode: 'string', fsp: 6 }) + .default('current_timestamp(6)') + .notNull() +}); + +export const oauth2ClientUrl = mysqlTable('o_auth2_client_url', { + id: int('id').autoincrement().notNull(), + url: varchar('url', { length: 255 }).notNull(), + type: mysqlEnum('type', ['redirect_uri', 'terms', 'privacy', 'website']).notNull(), + created_at: timestamp('created_at', { fsp: 6, mode: 'string' }) + .default('current_timestamp(6)') + .notNull(), + updated_at: timestamp('updated_at', { fsp: 6, mode: 'string' }) + .default('current_timestamp(6)') + .notNull(), + clientId: int('clientId').references(() => oauth2Client.id, { onDelete: 'cascade' }) +}); + +export const oauth2Token = mysqlTable('o_auth2_token', { + id: int('id').autoincrement().notNull(), + type: mysqlEnum('type', ['code', 'access_token', 'refresh_token']).notNull(), + token: text('token').notNull(), + scope: text('scope'), + expires_at: timestamp('expires_at', { mode: 'string' }).default('current_timestamp()').notNull(), + userId: int('userId').references(() => user.id, { onDelete: 'cascade' }), + clientId: int('clientId').references(() => oauth2Client.id, { onDelete: 'cascade' }), + nonce: text('nonce'), + created_at: datetime('created_at', { mode: 'string', fsp: 6 }) + .default('current_timestamp(6)') + .notNull(), + updated_at: datetime('updated_at', { mode: 'string', fsp: 6 }) + .default('current_timestamp(6)') + .notNull(), + pcke: text('pcke') +}); + +export const privilege = mysqlTable('privilege', { + id: int('id').autoincrement().notNull(), + name: text('name').notNull() +}); + +export const upload = mysqlTable('upload', { + id: int('id').autoincrement().notNull(), + original_name: varchar('original_name', { length: 255 }).notNull(), + mimetype: varchar('mimetype', { length: 255 }).notNull(), + file: varchar('file', { length: 255 }).notNull(), + uploaderId: int('uploaderId').references((): AnyMySqlColumn => user.id, { + onDelete: 'set null', + onUpdate: 'cascade' + }), + created_at: datetime('created_at', { mode: 'string', fsp: 6 }) + .default('current_timestamp(6)') + .notNull(), + updated_at: datetime('updated_at', { mode: 'string', fsp: 6 }) + .default('current_timestamp(6)') + .notNull() +}); + +export const user = mysqlTable( + 'user', + { + id: int('id').autoincrement().notNull(), + uuid: varchar('uuid', { length: 36 }).notNull(), + username: varchar('username', { length: 26 }).notNull(), + email: varchar('email', { length: 255 }).notNull(), + display_name: varchar('display_name', { length: 32 }).notNull(), + password: text('password'), + activated: tinyint('activated').default(0).notNull(), + activity_at: timestamp('activity_at', { mode: 'string' }) + .default('current_timestamp()') + .notNull(), + pictureId: int('pictureId').references((): AnyMySqlColumn => upload.id, { + onDelete: 'set null', + onUpdate: 'cascade' + }), + created_at: datetime('created_at', { mode: 'string', fsp: 6 }) + .default('current_timestamp(6)') + .notNull(), + updated_at: datetime('updated_at', { mode: 'string', fsp: 6 }) + .default('current_timestamp(6)') + .notNull() + }, + (table) => { + return { + IDX_a95e949168be7b7ece1a2382fe: unique('IDX_a95e949168be7b7ece1a2382fe').on(table.uuid), + IDX_78a916df40e02a9deb1c4b75ed: unique('IDX_78a916df40e02a9deb1c4b75ed').on(table.username), + IDX_e12875dfb3b1d92d7d7c5377e2: unique('IDX_e12875dfb3b1d92d7d7c5377e2').on(table.email) + }; + } +); + +export type User = typeof user.$inferSelect; +export type NewUser = typeof user.$inferInsert; + +export const userPrivilegesPrivilege = mysqlTable( + 'user_privileges_privilege', + { + userId: int('userId') + .notNull() + .references(() => user.id, { onDelete: 'cascade', onUpdate: 'cascade' }), + privilegeId: int('privilegeId') + .notNull() + .references(() => privilege.id, { onDelete: 'cascade', onUpdate: 'cascade' }) + }, + (table) => { + return { + IDX_0664a7ff494a1859a09014c0f1: index('IDX_0664a7ff494a1859a09014c0f1').on(table.userId), + IDX_e71171f4ed20bc8564a1819d0b: index('IDX_e71171f4ed20bc8564a1819d0b').on(table.privilegeId) + }; + } +); + +export const userToken = mysqlTable('user_token', { + id: int('id').autoincrement().notNull(), + token: text('token').notNull(), + type: mysqlEnum('type', [ + 'generic', + 'activation', + 'deactivation', + 'password', + 'login', + 'gdpr', + 'totp', + 'public_key', + 'recovery' + ]).notNull(), + expires_at: timestamp('expires_at', { mode: 'string' }), + userId: int('userId').references(() => user.id, { onDelete: 'cascade' }), + nonce: text('nonce'), + created_at: datetime('created_at', { mode: 'string', fsp: 6 }) + .default('current_timestamp(6)') + .notNull() +}); diff --git a/src/lib/server/users/index.ts b/src/lib/server/users/index.ts new file mode 100644 index 0000000..af42284 --- /dev/null +++ b/src/lib/server/users/index.ts @@ -0,0 +1,48 @@ +import bcrypt from 'bcryptjs'; +import { and, eq, or } from 'drizzle-orm'; +import { db, user, type User } from '../drizzle'; +import type { UserSession } from './types'; + +export class Users { + static async getByLogin(login: string): Promise { + const [result] = await db + .select() + .from(user) + .where(and(or(eq(user.email, login), eq(user.username, login)), eq(user.activated, 1))) + .limit(1); + return result; + } + + static async getBySession(session?: UserSession): Promise { + if (!session) return undefined; + const [result] = await db + .select() + .from(user) + .where(and(eq(user.id, session.uid), eq(user.activated, 1))) + .limit(1); + return result; + } + + static async update(subject: User, fields: Partial) { + return db.update(user).set(fields).where(eq(user.id, subject.id)); + } + + static async validatePassword(user: User, password: string): Promise { + return bcrypt.compare(password, user.password as string); + } + + static async hashPassword(password: string): Promise { + return bcrypt.hash(password, 10); + } + + static async toSession(user: User): Promise { + return { + uid: user.id, + uuid: user.uuid, + name: user.display_name, + username: user.username + }; + } +} + +export * from './types'; diff --git a/src/lib/server/users/totp.ts b/src/lib/server/users/totp.ts new file mode 100644 index 0000000..644b213 --- /dev/null +++ b/src/lib/server/users/totp.ts @@ -0,0 +1,50 @@ +import { authenticator as totp } from 'otplib'; +import { db, userToken, type User } from '../drizzle'; +import { and, eq, gt, isNull, or } from 'drizzle-orm'; + +totp.options = { + window: 2 +}; + +export class TimeOTP { + public static validate(secret: string, token: string): boolean { + return totp.verify({ token, secret }); + } + + public static getUri(secret: string, username: string): string { + return totp.keyuri(username, 'Icy Network', secret); + } + + public static createSecret(): string { + return totp.generateSecret(); + } + + public static async isUserOtp(subject: User) { + const tokens = await db + .select({ id: userToken.id }) + .from(userToken) + .where( + and( + eq(userToken.type, 'totp'), + eq(userToken.userId, subject.id), + or(isNull(userToken.expires_at), gt(userToken.expires_at, new Date().toISOString())) + ) + ); + return tokens?.length; + } + + public static async getUserOtp(subject: User) { + const [token] = await db + .select({ id: userToken.id, token: userToken.token }) + .from(userToken) + .where( + and( + eq(userToken.type, 'totp'), + eq(userToken.userId, subject.id), + or(isNull(userToken.expires_at), gt(userToken.expires_at, new Date().toISOString())) + ) + ) + .limit(1); + return token; + } +} diff --git a/src/lib/server/users/types.ts b/src/lib/server/users/types.ts new file mode 100644 index 0000000..2d54636 --- /dev/null +++ b/src/lib/server/users/types.ts @@ -0,0 +1,6 @@ +export interface UserSession { + uid: number; + uuid: string; + name: string; + username: string; +} diff --git a/src/lib/validators.ts b/src/lib/validators.ts new file mode 100644 index 0000000..68cf245 --- /dev/null +++ b/src/lib/validators.ts @@ -0,0 +1,4 @@ +export const emailRegex = + /^[-!#$%&'*+\\/0-9=?A-Z^_a-z`{|}~](\.?[-!#$%&'*+\\/0-9=?A-Z^_a-z`{|}~])*@[a-zA-Z0-9](-*\.?[a-zA-Z0-9])*\.[a-zA-Z](-?[a-zA-Z0-9])+$/; +export const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d\w\W]{8,}$/; +export const usernameRegex = /^[a-zA-Z0-9_\-.]{3,26}$/; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..2440caf --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,16 @@ + + +
+ +
+ + diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts new file mode 100644 index 0000000..9cde7a6 --- /dev/null +++ b/src/routes/+layout.ts @@ -0,0 +1,11 @@ +import { loadTranslations } from '$lib/i18n'; + +export const load = async ({ url }) => { + const { pathname } = url; + + const initLocale = 'en'; // get from cookie, user session, ... + + await loadTranslations(initLocale, pathname); + + return {}; +} diff --git a/src/routes/account/+page.server.ts b/src/routes/account/+page.server.ts new file mode 100644 index 0000000..0e9a5cc --- /dev/null +++ b/src/routes/account/+page.server.ts @@ -0,0 +1,156 @@ +import { Challenge } from '$lib/server/challenge.js'; +import type { User } from '$lib/server/drizzle'; +import { Users, type UserSession } from '$lib/server/users/index.js'; +import { TimeOTP } from '$lib/server/users/totp.js'; +import { fail, redirect } from '@sveltejs/kit'; + +interface AccountUpdate { + displayName: string; + + currentEmail: string; + newEmail: string; + + currentPassword: string; + newPassword: string; +} + +export const actions = { + logout: async ({ locals }) => { + await locals.session.destroy(); + return redirect(303, '/'); + }, + update: async ({ request, locals }) => { + const currentUser = await Users.getBySession(locals.session.data?.user); + if (!currentUser) { + await locals.session.destroy(); + return redirect(301, '/login'); + } + + const body = await request.formData(); + let data: Partial; + try { + data = await Challenge.authorizedChanges( + ['displayName', 'currentEmail', 'newEmail', 'currentPassword', 'newPassword'], + body, + currentUser + ); + } catch { + return fail(400, { errors: ['invalidRequest'] }); + } + + // No changes + if ( + (!data.displayName || data.displayName === currentUser.display_name) && + !data.newEmail && + !data.newPassword + ) { + return { success: true, errors: [], fields: [] }; + } + + // Current email is not provided + if (data.newEmail && !data.currentEmail) { + return fail(400, { + displayName: data.displayName, + errors: ['emailRequired'], + fields: ['currentEmail'] + }); + } + + // Current email is invalid + if (data.currentEmail && data.currentEmail !== currentUser.email) { + return fail(400, { + displayName: data.displayName, + errors: ['invalidEmail'], + fields: ['currentEmail'] + }); + } + + // Current password is not provided + if (data.newPassword && !data.currentPassword) { + return fail(400, { + displayName: data.displayName, + errors: ['passwordRequired'], + fields: ['currentPassword'] + }); + } + + // Password does not match current password + if ( + data.currentPassword && + !(await Users.validatePassword(currentUser, data.currentPassword)) + ) { + return fail(400, { + displayName: data.displayName, + errors: ['invalidPassword'], + fields: ['currentPassword'] + }); + } + + // Invalid display name + if (data.displayName && (data.displayName.length < 3 || data.displayName.length > 32)) { + return fail(400, { + displayName: data.displayName, + errors: ['invalidDisplayName'], + fields: ['displayName'] + }); + } + + // When updating email or password, we check if OTP has been enabled. + // If it is, we need to ask for the OTP code. + if (data.newEmail || data.newPassword) { + const isOtp = await TimeOTP.isUserOtp(currentUser); + if (isOtp) { + const challenge = await Challenge.issueChallenge(data, currentUser.uuid); + return { otpRequired: challenge }; + } + } + + // Update the user table + const updates: Partial = {}; + if (data.displayName) { + updates.display_name = data.displayName; + } + + if (data.newEmail) { + updates.email = data.newEmail; + } + + if (data.newPassword) { + updates.password = await Users.hashPassword(data.newPassword); + } + + await Users.update(currentUser, updates); + + // Update session display name + if (data.displayName) { + await locals.session.update(({ user }) => ({ + user: { ...user, name: data.displayName as string } as UserSession + })); + } + + // TODO: audit log + + return { + success: true, + errors: [], + fields: [], + displayName: data.displayName || currentUser.display_name + }; + } +}; + +export async function load({ locals, url }) { + const userInfo = locals.session.data?.user; + const currentUser = await Users.getBySession(userInfo); + if (!userInfo || !currentUser) { + await locals.session.destroy(); + return redirect(301, `/login?redirectTo=${encodeURIComponent(url.pathname)}`); + } + + const otpEnabled = await TimeOTP.isUserOtp(currentUser); + + return { + user: userInfo, + otpEnabled + }; +} diff --git a/src/routes/account/+page.svelte b/src/routes/account/+page.svelte new file mode 100644 index 0000000..b63562c --- /dev/null +++ b/src/routes/account/+page.svelte @@ -0,0 +1,76 @@ + + +{data.user.name} + +
+
+ + +
+ +
+ + +
+ +
{$t('account.changeEmail')}
+ +
+ + +
+ +
+ + +
+ +
{$t('account.changePassword')}
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + diff --git a/src/routes/login/+page.server.ts b/src/routes/login/+page.server.ts new file mode 100644 index 0000000..7c87e94 --- /dev/null +++ b/src/routes/login/+page.server.ts @@ -0,0 +1,40 @@ +import { Users } from '$lib/server/users/index.js'; +import { fail, redirect, type Actions } from '@sveltejs/kit'; + +export const actions = { + default: async ({ request, locals, url }) => { + // Redirect + const redirectUrl = url.searchParams.has('redirectTo') + ? (url.searchParams.get('redirectTo') as string) + : '/'; + + // Already logged in + if (locals.session.data?.user) { + return redirect(303, redirectUrl); + } + + const data = await request.formData(); + const email = data.get('email') as string; + const password = data.get('password') as string; + + if (!email?.trim() || !password?.trim()) { + return fail(400, { incorrect: true }); + } + + // Find existing active user + const loginUser = await Users.getByLogin(email); + + // Compare user password + if (!loginUser || !(await Users.validatePassword(loginUser, password))) { + return fail(400, { email, incorrect: true }); + } + + // TODO: check two-factor + + // Create session data for user + const sessionUser = await Users.toSession(loginUser); + await locals.session.set({ user: sessionUser }); + + return redirect(303, redirectUrl); + } +} as Actions; diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte new file mode 100644 index 0000000..43b6aa1 --- /dev/null +++ b/src/routes/login/+page.svelte @@ -0,0 +1,74 @@ + + + + + diff --git a/static/background.jpg b/static/background.jpg new file mode 100644 index 0000000..246ea33 Binary files /dev/null and b/static/background.jpg differ