From 3bff254a22303de2dfe8a718b3c6d71dce681f1a Mon Sep 17 00:00:00 2001 From: SunWuyuan Date: Sat, 17 May 2025 20:02:55 +0800 Subject: [PATCH] Implement device authentication middleware and Prisma model for device management. Update routes to include device info retrieval and password management, enhancing security and functionality. --- middleware/auth.js | 116 ++++++++++++-- package.json | 4 +- pnpm-lock.yaml | 371 ++++++------------------------------------- prisma/schema.prisma | 9 ++ routes/kv.js | 102 +++++++++++- 5 files changed, 259 insertions(+), 343 deletions(-) diff --git a/middleware/auth.js b/middleware/auth.js index fa90f16..742eeac 100644 --- a/middleware/auth.js +++ b/middleware/auth.js @@ -1,22 +1,120 @@ import { siteKey } from "../config.js"; import AppError from "../utils/errors.js"; +import { PrismaClient } from '@prisma/client'; +const prisma = new PrismaClient(); -const checkSiteKey = (req, res, next) => { +export const ACCESS_TYPES = { + NO_PASSWORD_WRITABLE: 'NO_PASSWORD_WRITABLE', + NO_PASSWORD_READABLE: 'NO_PASSWORD_READABLE', + NO_PASSWORD_UNREADABLE: 'NO_PASSWORD_UNREADABLE' +}; + +export const checkSiteKey = (req, res, next) => { const providedKey = req.headers['x-site-key']; if (!siteKey) { return next(); } - if (!providedKey || providedKey !== siteKey) { - const error = AppError.createError( - AppError.HTTP_STATUS.UNAUTHORIZED, - "此服务器已开启站点密钥验证,请在请求头中添加 x-site-key 以继续访问" - ); - return res.status(error.statusCode).json(error); - } + if (!providedKey || providedKey !== siteKey) { + const error = AppError.createError( + AppError.HTTP_STATUS.UNAUTHORIZED, + "此服务器已开启站点密钥验证,请在请求头中添加 x-site-key 以继续访问" + ); + return res.status(error.statusCode).json(error); + } next(); }; -export default checkSiteKey; \ No newline at end of file +async function getOrCreateDevice(uuid, className) { + let device = await prisma.device.findUnique({ + where: { uuid } + }); + + if (!device) { + device = await prisma.device.create({ + data: { + uuid, + name: className || null, + accessType: ACCESS_TYPES.NO_PASSWORD_WRITABLE + } + }); + } + + return device; +} + +async function validatePassword(device, password) { + if (!device.password) return true; + return device.password === password; +} + +export const authMiddleware = async (req, res, next) => { + const { namespace } = req.params; + const { password } = req.body; + + try { + const device = await getOrCreateDevice(namespace, req.body.className); + req.device = device; + + if (device.password && !await validatePassword(device, password)) { + return res.status(401).json({ error: 'Invalid password' }); + } + + next(); + } catch (error) { + console.error('Auth middleware error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}; + +export const readAuthMiddleware = async (req, res, next) => { + const { namespace } = req.params; + + try { + const device = await getOrCreateDevice(namespace); + req.device = device; + + if (device.accessType === ACCESS_TYPES.NO_PASSWORD_UNREADABLE) { + return res.status(403).json({ error: 'Device is not readable' }); + } + + if (device.accessType === ACCESS_TYPES.NO_PASSWORD_READABLE) { + return next(); + } + + if (device.password) { + const { password } = req.body; + if (!await validatePassword(device, password)) { + return res.status(401).json({ error: 'Invalid password' }); + } + } + + next(); + } catch (error) { + console.error('Read auth middleware error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}; + +export const writeAuthMiddleware = async (req, res, next) => { + const { namespace } = req.params; + + try { + const device = await getOrCreateDevice(namespace); + req.device = device; + + if (device.password) { + const { password } = req.body; + if (!await validatePassword(device, password)) { + return res.status(401).json({ error: 'Invalid password' }); + } + } + + next(); + } catch (error) { + console.error('Write auth middleware error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}; \ No newline at end of file diff --git a/package.json b/package.json index 7066ce4..0d2f9c6 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "@opentelemetry/sdk-node": "^0.200.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", - "@prisma/client": "6.7.0", + "@prisma/client": "6.8.2", "axios": "^1.9.0", "body-parser": "^2.2.0", "cookie-parser": "~1.4.4", @@ -36,6 +36,6 @@ "uuid": "^11.1.0" }, "devDependencies": { - "prisma": "6.7.0" + "prisma": "6.8.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4452a4d..6e3ad8f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,8 +27,8 @@ importers: specifier: ^1.33.0 version: 1.33.0 '@prisma/client': - specifier: 6.7.0 - version: 6.7.0(prisma@6.7.0) + specifier: 6.8.2 + version: 6.8.2(prisma@6.8.2) axios: specifier: ^1.9.0 version: 1.9.0(debug@4.4.0) @@ -67,161 +67,11 @@ importers: version: 11.1.0 devDependencies: prisma: - specifier: 6.7.0 - version: 6.7.0 + specifier: 6.8.2 + version: 6.8.2 packages: - '@esbuild/aix-ppc64@0.25.0': - resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.25.0': - resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.25.0': - resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.25.0': - resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.25.0': - resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.25.0': - resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.25.0': - resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.25.0': - resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.25.0': - resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.25.0': - resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.25.0': - resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.25.0': - resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.25.0': - resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.25.0': - resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.25.0': - resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.25.0': - resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.25.0': - resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.25.0': - resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.25.0': - resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.25.0': - resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.25.0': - resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/sunos-x64@0.25.0': - resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.25.0': - resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.25.0': - resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.25.0': - resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@grpc/grpc-js@1.13.3': resolution: {integrity: sha512-FTXHdOoPbZrBjlVLHuKbDZnsTxXv2BlHF57xw6LuThXacXvtkahEPED0CKMk6obZDf65Hv4k3z62eyPNpvinIg==} engines: {node: '>=12.10.0'} @@ -689,8 +539,8 @@ packages: peerDependencies: '@opentelemetry/api': ^1.1.0 - '@prisma/client@6.7.0': - resolution: {integrity: sha512-+k61zZn1XHjbZul8q6TdQLpuI/cvyfil87zqK2zpreNIXyXtpUv3+H/oM69hcsFcZXaokHJIzPAt5Z8C8eK2QA==} + '@prisma/client@6.8.2': + resolution: {integrity: sha512-5II+vbyzv4si6Yunwgkj0qT/iY0zyspttoDrL3R4BYgLdp42/d2C8xdi9vqkrYtKt9H32oFIukvyw3Koz5JoDg==} engines: {node: '>=18.18'} peerDependencies: prisma: '*' @@ -701,23 +551,23 @@ packages: typescript: optional: true - '@prisma/config@6.7.0': - resolution: {integrity: sha512-di8QDdvSz7DLUi3OOcCHSwxRNeW7jtGRUD2+Z3SdNE3A+pPiNT8WgUJoUyOwJmUr5t+JA2W15P78C/N+8RXrOA==} + '@prisma/config@6.8.2': + resolution: {integrity: sha512-ZJY1fF4qRBPdLQ/60wxNtX+eu89c3AkYEcP7L3jkp0IPXCNphCYxikTg55kPJLDOG6P0X+QG5tCv6CmsBRZWFQ==} - '@prisma/debug@6.7.0': - resolution: {integrity: sha512-RabHn9emKoYFsv99RLxvfG2GHzWk2ZI1BuVzqYtmMSIcuGboHY5uFt3Q3boOREM9de6z5s3bQoyKeWnq8Fz22w==} + '@prisma/debug@6.8.2': + resolution: {integrity: sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg==} - '@prisma/engines-version@6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed': - resolution: {integrity: sha512-EvpOFEWf1KkJpDsBCrih0kg3HdHuaCnXmMn7XFPObpFTzagK1N0Q0FMnYPsEhvARfANP5Ok11QyoTIRA2hgJTA==} + '@prisma/engines-version@6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e': + resolution: {integrity: sha512-Rkik9lMyHpFNGaLpPF3H5q5TQTkm/aE7DsGM5m92FZTvWQsvmi6Va8On3pWvqLHOt5aPUvFb/FeZTmphI4CPiQ==} - '@prisma/engines@6.7.0': - resolution: {integrity: sha512-3wDMesnOxPrOsq++e5oKV9LmIiEazFTRFZrlULDQ8fxdub5w4NgRBoxtWbvXmj2nJVCnzuz6eFix3OhIqsZ1jw==} + '@prisma/engines@6.8.2': + resolution: {integrity: sha512-XqAJ//LXjqYRQ1RRabs79KOY4+v6gZOGzbcwDQl0D6n9WBKjV7qdrbd042CwSK0v0lM9MSHsbcFnU2Yn7z8Zlw==} - '@prisma/fetch-engine@6.7.0': - resolution: {integrity: sha512-zLlAGnrkmioPKJR4Yf7NfW3hftcvqeNNEHleMZK9yX7RZSkhmxacAYyfGsCcqRt47jiZ7RKdgE0Wh2fWnm7WsQ==} + '@prisma/fetch-engine@6.8.2': + resolution: {integrity: sha512-lCvikWOgaLOfqXGacEKSNeenvj0n3qR5QvZUOmPE2e1Eh8cMYSobxonCg9rqM6FSdTfbpqp9xwhSAOYfNqSW0g==} - '@prisma/get-platform@6.7.0': - resolution: {integrity: sha512-i9IH5lO4fQwnMLvQLYNdgVh9TK3PuWBfQd7QLk/YurnAIg+VeADcZDbmhAi4XBBDD+hDif9hrKyASu0hbjwabw==} + '@prisma/get-platform@6.8.2': + resolution: {integrity: sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow==} '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -962,16 +812,6 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - esbuild-register@3.6.0: - resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} - peerDependencies: - esbuild: '>=0.12 <1' - - esbuild@0.25.0: - resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} - engines: {node: '>=18'} - hasBin: true - escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1027,11 +867,6 @@ packages: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -1125,6 +960,10 @@ packages: engines: {node: '>=10'} hasBin: true + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + json-bigint@1.0.0: resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} @@ -1256,8 +1095,8 @@ packages: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} - prisma@6.7.0: - resolution: {integrity: sha512-vArg+4UqnQ13CVhc2WUosemwh6hr6cr6FY2uzDvCIFwH8pu8BXVv38PktoMLVjtX7sbYThxbnZF5YiR8sN2clw==} + prisma@6.8.2: + resolution: {integrity: sha512-JNricTXQxzDtRS7lCGGOB4g5DJ91eg3nozdubXze3LpcMl1oWwcFddrj++Up3jnRE6X/3gB/xz3V+ecBk/eEGA==} engines: {node: '>=18.18'} hasBin: true peerDependencies: @@ -1426,81 +1265,6 @@ packages: snapshots: - '@esbuild/aix-ppc64@0.25.0': - optional: true - - '@esbuild/android-arm64@0.25.0': - optional: true - - '@esbuild/android-arm@0.25.0': - optional: true - - '@esbuild/android-x64@0.25.0': - optional: true - - '@esbuild/darwin-arm64@0.25.0': - optional: true - - '@esbuild/darwin-x64@0.25.0': - optional: true - - '@esbuild/freebsd-arm64@0.25.0': - optional: true - - '@esbuild/freebsd-x64@0.25.0': - optional: true - - '@esbuild/linux-arm64@0.25.0': - optional: true - - '@esbuild/linux-arm@0.25.0': - optional: true - - '@esbuild/linux-ia32@0.25.0': - optional: true - - '@esbuild/linux-loong64@0.25.0': - optional: true - - '@esbuild/linux-mips64el@0.25.0': - optional: true - - '@esbuild/linux-ppc64@0.25.0': - optional: true - - '@esbuild/linux-riscv64@0.25.0': - optional: true - - '@esbuild/linux-s390x@0.25.0': - optional: true - - '@esbuild/linux-x64@0.25.0': - optional: true - - '@esbuild/netbsd-arm64@0.25.0': - optional: true - - '@esbuild/netbsd-x64@0.25.0': - optional: true - - '@esbuild/openbsd-arm64@0.25.0': - optional: true - - '@esbuild/openbsd-x64@0.25.0': - optional: true - - '@esbuild/sunos-x64@0.25.0': - optional: true - - '@esbuild/win32-arm64@0.25.0': - optional: true - - '@esbuild/win32-ia32@0.25.0': - optional: true - - '@esbuild/win32-x64@0.25.0': - optional: true - '@grpc/grpc-js@1.13.3': dependencies: '@grpc/proto-loader': 0.7.15 @@ -2191,37 +1955,34 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.0.0(@opentelemetry/api@1.9.0) - '@prisma/client@6.7.0(prisma@6.7.0)': + '@prisma/client@6.8.2(prisma@6.8.2)': optionalDependencies: - prisma: 6.7.0 + prisma: 6.8.2 - '@prisma/config@6.7.0': + '@prisma/config@6.8.2': dependencies: - esbuild: 0.25.0 - esbuild-register: 3.6.0(esbuild@0.25.0) - transitivePeerDependencies: - - supports-color + jiti: 2.4.2 - '@prisma/debug@6.7.0': {} + '@prisma/debug@6.8.2': {} - '@prisma/engines-version@6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed': {} + '@prisma/engines-version@6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e': {} - '@prisma/engines@6.7.0': + '@prisma/engines@6.8.2': dependencies: - '@prisma/debug': 6.7.0 - '@prisma/engines-version': 6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed - '@prisma/fetch-engine': 6.7.0 - '@prisma/get-platform': 6.7.0 + '@prisma/debug': 6.8.2 + '@prisma/engines-version': 6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e + '@prisma/fetch-engine': 6.8.2 + '@prisma/get-platform': 6.8.2 - '@prisma/fetch-engine@6.7.0': + '@prisma/fetch-engine@6.8.2': dependencies: - '@prisma/debug': 6.7.0 - '@prisma/engines-version': 6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed - '@prisma/get-platform': 6.7.0 + '@prisma/debug': 6.8.2 + '@prisma/engines-version': 6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e + '@prisma/get-platform': 6.8.2 - '@prisma/get-platform@6.7.0': + '@prisma/get-platform@6.8.2': dependencies: - '@prisma/debug': 6.7.0 + '@prisma/debug': 6.8.2 '@protobufjs/aspromise@1.1.2': {} @@ -2450,41 +2211,6 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - esbuild-register@3.6.0(esbuild@0.25.0): - dependencies: - debug: 4.4.0 - esbuild: 0.25.0 - transitivePeerDependencies: - - supports-color - - esbuild@0.25.0: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.0 - '@esbuild/android-arm': 0.25.0 - '@esbuild/android-arm64': 0.25.0 - '@esbuild/android-x64': 0.25.0 - '@esbuild/darwin-arm64': 0.25.0 - '@esbuild/darwin-x64': 0.25.0 - '@esbuild/freebsd-arm64': 0.25.0 - '@esbuild/freebsd-x64': 0.25.0 - '@esbuild/linux-arm': 0.25.0 - '@esbuild/linux-arm64': 0.25.0 - '@esbuild/linux-ia32': 0.25.0 - '@esbuild/linux-loong64': 0.25.0 - '@esbuild/linux-mips64el': 0.25.0 - '@esbuild/linux-ppc64': 0.25.0 - '@esbuild/linux-riscv64': 0.25.0 - '@esbuild/linux-s390x': 0.25.0 - '@esbuild/linux-x64': 0.25.0 - '@esbuild/netbsd-arm64': 0.25.0 - '@esbuild/netbsd-x64': 0.25.0 - '@esbuild/openbsd-arm64': 0.25.0 - '@esbuild/openbsd-x64': 0.25.0 - '@esbuild/sunos-x64': 0.25.0 - '@esbuild/win32-arm64': 0.25.0 - '@esbuild/win32-ia32': 0.25.0 - '@esbuild/win32-x64': 0.25.0 - escalade@3.2.0: {} escape-html@1.0.3: {} @@ -2561,9 +2287,6 @@ snapshots: fresh@2.0.0: {} - fsevents@2.3.3: - optional: true - function-bind@1.1.2: {} gaxios@6.7.1: @@ -2671,6 +2394,8 @@ snapshots: filelist: 1.0.4 minimatch: 3.1.2 + jiti@2.4.2: {} + json-bigint@1.0.0: dependencies: bignumber.js: 9.3.0 @@ -2773,14 +2498,10 @@ snapshots: dependencies: xtend: 4.0.2 - prisma@6.7.0: + prisma@6.8.2: dependencies: - '@prisma/config': 6.7.0 - '@prisma/engines': 6.7.0 - optionalDependencies: - fsevents: 2.3.3 - transitivePeerDependencies: - - supports-color + '@prisma/config': 6.8.2 + '@prisma/engines': 6.8.2 protobufjs@7.5.1: dependencies: diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 63e7b4f..06911c0 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -17,3 +17,12 @@ model KVStore { @@id([namespace, key]) } + +model Device { + uuid String @id @db.Char(36) + password String? + name String? + accessType String @default("NO_PASSWORD_WRITABLE") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} diff --git a/routes/kv.js b/routes/kv.js index f5414e7..62a2bbc 100644 --- a/routes/kv.js +++ b/routes/kv.js @@ -1,9 +1,13 @@ import { Router } from "express"; const router = Router(); import kvStore from "../models/kvStore.js"; -import checkSiteKey from "../middleware/auth.js"; +import { checkSiteKey } from "../middleware/auth.js"; import { v4 as uuidv4 } from "uuid"; import errors from "../utils/errors.js"; +import { PrismaClient } from "@prisma/client"; +import { readAuthMiddleware, writeAuthMiddleware } from "../middleware/auth.js"; + +const prisma = new PrismaClient(); // 检查是否为受限UUID的中间件 const checkRestrictedUUID = (req, res, next) => { @@ -17,6 +21,83 @@ const checkRestrictedUUID = (req, res, next) => { }; router.use(checkSiteKey); + +// Get device info +router.get( + "/:namespace/_info", + readAuthMiddleware, + errors.catchAsync(async (req, res) => { + try { + const { device } = req; + res.json({ + uuid: device.uuid, + name: device.name, + accessType: device.accessType, + hasPassword: !!device.password, + }); + } catch (error) { + console.error("Error getting device info:", error); + res.status(500).json({ error: "Internal server error" }); + } + }) +); + +// Update device password +router.post( + "/:namespace/_password", + writeAuthMiddleware, + errors.catchAsync(async (req, res) => { + const { newPassword, oldPassword } = req.body; + const { device } = req; + + try { + if (device.password && oldPassword !== device.password) { + return res.status(401).json({ error: "Invalid old password" }); + } + + await prisma.device.update({ + where: { uuid: device.uuid }, + data: { password: newPassword }, + }); + + res.json({ message: "Password updated successfully" }); + } catch (error) { + console.error("Error updating password:", error); + res.status(500).json({ error: "Internal server error" }); + } + }) +); + +// Update device info +router.put( + "/:namespace/_info", + writeAuthMiddleware, + errors.catchAsync(async (req, res) => { + const { name, accessType } = req.body; + const { device } = req; + + try { + const updatedDevice = await prisma.device.update({ + where: { uuid: device.uuid }, + data: { + name: name || device.name, + accessType: accessType || device.accessType, + }, + }); + + res.json({ + uuid: updatedDevice.uuid, + name: updatedDevice.name, + accessType: updatedDevice.accessType, + hasPassword: !!updatedDevice.password, + }); + } catch (error) { + console.error("Error updating device info:", error); + res.status(500).json({ error: "Internal server error" }); + } + }) +); + /** * GET /:namespace * 获取指定命名空间下的所有键名及元数据列表 @@ -72,6 +153,7 @@ router.get( router.get( "/:namespace/:key", checkRestrictedUUID, + readAuthMiddleware, errors.catchAsync(async (req, res, next) => { const { namespace, key } = req.params; @@ -94,6 +176,7 @@ router.get( router.get( "/:namespace/:key/metadata", checkRestrictedUUID, + readAuthMiddleware, errors.catchAsync(async (req, res, next) => { const { namespace, key } = req.params; const metadata = await kvStore.getMetadata(namespace, key); @@ -115,6 +198,7 @@ router.get( router.post( "/:namespace/:key", checkRestrictedUUID, + writeAuthMiddleware, errors.catchAsync(async (req, res, next) => { const { namespace, key } = req.params; const value = req.body; @@ -147,14 +231,19 @@ router.post( * 批量导入键值对到指定命名空间 */ router.post( - "/:namespace/import/batch-import", + "/:namespace/_batchimport", checkRestrictedUUID, errors.catchAsync(async (req, res, next) => { const { namespace } = req.params; const data = req.body; if (!data || Object.keys(data).length === 0) { - return next(errors.createError(400, "请提供有效的JSON数据,格式为 {\"key\":{}, \"key2\":{}}")); + return next( + errors.createError( + 400, + '请提供有效的JSON数据,格式为 {"key":{}, "key2":{}}' + ) + ); } // 获取客户端IP @@ -174,12 +263,12 @@ router.post( const result = await kvStore.upsert(namespace, key, value, creatorIp); results.push({ key: result.key, - created: result.createdAt.getTime() === result.updatedAt.getTime() + created: result.createdAt.getTime() === result.updatedAt.getTime(), }); } catch (error) { errors.push({ key, - error: error.message + error: error.message, }); } } @@ -190,7 +279,7 @@ router.post( successful: results.length, failed: errors.length, results, - errors: errors.length > 0 ? errors : undefined + errors: errors.length > 0 ? errors : undefined, }); }) ); @@ -253,5 +342,4 @@ router.get( }) ); - export default router;