应用:
- {{ selectedToken.appName }}
+ {{ getAppName(selectedToken.appId) }}
令牌:
diff --git a/kv-admin/src/pages/password-manager.vue b/kv-admin/src/pages/password-manager.vue
index e19e2d2..67d07be 100644
--- a/kv-admin/src/pages/password-manager.vue
+++ b/kv-admin/src/pages/password-manager.vue
@@ -42,6 +42,8 @@ const showChangePasswordDialog = ref(false)
const showDeletePasswordDialog = ref(false)
const showHintDialog = ref(false)
const showResetDeviceDialog = ref(false)
+const showRegisterDialog = ref(false)
+const deviceRequired = ref(false)
// Form data
const currentPassword = ref('')
@@ -237,15 +239,30 @@ const handleDeviceReset = () => {
}, 3000)
}
+// 更新设备UUID回调
+const updateUuid = () => {
+ showRegisterDialog.value = false
+ deviceUuid.value = deviceStore.getDeviceUuid()
+ loadDeviceInfo()
+}
+
onMounted(async () => {
- deviceUuid.value = deviceStore.getOrGenerate()
+ // 检查是否存在设备UUID
+ const existingUuid = deviceStore.getDeviceUuid()
+ if (!existingUuid) {
+ deviceRequired.value = true
+ // 如果没有设备UUID,显示设备管理弹框
+ showRegisterDialog.value = true
+ } else {
+ deviceUuid.value = existingUuid
- // 加载设备信息
- await loadDeviceInfo()
+ // 加载设备信息
+ await loadDeviceInfo()
- // 如果有密码但密码提示不存在,单独加载密码提示
- if (hasPassword.value && !passwordHint.value) {
- await loadPasswordHint()
+ // 如果有密码但密码提示不存在,单独加载密码提示
+ if (hasPassword.value && !passwordHint.value) {
+ await loadPasswordHint()
+ }
}
})
@@ -587,5 +604,12 @@ onMounted(async () => {
@confirm="handleDeviceReset"
@update:modelValue="val => showResetDeviceDialog = val"
/>
+
+
+
\ No newline at end of file
diff --git a/middleware/kvTokenAuth.js b/middleware/kvTokenAuth.js
index d1721d7..ff1b847 100644
--- a/middleware/kvTokenAuth.js
+++ b/middleware/kvTokenAuth.js
@@ -27,7 +27,6 @@ export const kvTokenAuth = async (req, res, next) => {
const appInstall = await prisma.appInstall.findUnique({
where: { token },
include: {
- app: true,
device: true,
},
});
@@ -38,7 +37,6 @@ export const kvTokenAuth = async (req, res, next) => {
// 将信息存储到res.locals供后续使用
res.locals.device = appInstall.device;
- res.locals.app = appInstall.app;
res.locals.appInstall = appInstall;
res.locals.deviceId = appInstall.device.id;
diff --git a/prisma/migrations/20251005011323_update/migration.sql b/prisma/migrations/20251005011323_update/migration.sql
new file mode 100644
index 0000000..109bf20
--- /dev/null
+++ b/prisma/migrations/20251005011323_update/migration.sql
@@ -0,0 +1,17 @@
+/*
+ Warnings:
+
+ - You are about to drop the `App` table. If the table is not empty, all the data it contains will be lost.
+
+*/
+-- DropForeignKey
+ALTER TABLE `AppInstall` DROP FOREIGN KEY `AppInstall_appId_fkey`;
+
+-- DropIndex
+DROP INDEX `AppInstall_appId_fkey` ON `AppInstall`;
+
+-- AlterTable
+ALTER TABLE `AppInstall` MODIFY `appId` VARCHAR(191) NOT NULL;
+
+-- DropTable
+DROP TABLE `App`;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index b4c51e4..543b38a 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -56,27 +56,10 @@ model Device {
kvStore KVStore[] // 设备相关的KV存储
}
-model App {
- id Int @id @default(autoincrement()) // 自增ID
- name String // 应用名称
- description String? // 应用简介
- developerName String // 开发者名称
- developerLink String? // 开发者链接
- homepageLink String? // 应用首页链接
- iconHash String? // 图标hash
- repositoryUrl String? // Git仓库地址 (支持 GitHub, GitLab, Bitbucket, Gitea, Forgejo)
- metadata Json? // 元数据
- createdAt DateTime @default(now()) // 自动生成创建时间
- updatedAt DateTime @updatedAt // 自动更新时间
-
- // 关联的应用安装记录
- installs AppInstall[]
-}
-
model AppInstall {
id String @id @default(cuid())
deviceId Int // 关联的设备ID
- appId Int // 关联的应用ID
+ appId String // 应用ID (SHA256 hash)
token String @unique // 应用安装的唯一访问令牌,拥有完整KV读写权限
note String? // 安装备注
installedAt DateTime @default(now())
@@ -84,5 +67,4 @@ model AppInstall {
// 关联关系
device Device @relation(fields: [deviceId], references: [id], onDelete: Cascade)
- app App @relation(fields: [appId], references: [id], onDelete: Cascade)
}
diff --git a/routes/accounts.js b/routes/accounts.js
index 2628fe2..75ef0c4 100644
--- a/routes/accounts.js
+++ b/routes/accounts.js
@@ -465,17 +465,6 @@ router.get("/devices", jwtAuth, async (req, res, next) => {
name: true,
createdAt: true,
updatedAt: true,
- appInstalls: {
- include: {
- app: {
- select: {
- id: true,
- name: true,
- iconHash: true,
- },
- },
- },
- },
},
},
},
diff --git a/routes/apps.js b/routes/apps.js
index cbf408f..b1bf386 100644
--- a/routes/apps.js
+++ b/routes/apps.js
@@ -28,14 +28,12 @@ router.get(
const installations = await prisma.appInstall.findMany({
where: { deviceId: device.id },
- include: { app: true },
});
const apps = installations.map(install => ({
- id: install.app.id,
- name: install.app.name,
- description: install.app.description,
+ appId: install.appId,
token: install.token,
+ note: install.note,
installedAt: install.createdAt,
}));
@@ -49,6 +47,7 @@ router.get(
/**
* POST /apps/devices/:uuid/install/:appId
* 为设备安装应用 (需要UUID认证)
+ * appId 现在是 SHA256 hash
*/
router.post(
"/devices/:uuid/install/:appId",
@@ -58,16 +57,6 @@ router.post(
const { appId } = req.params;
const { note } = req.body;
- // 检查应用是否存在
- const app = await prisma.app.findUnique({
- where: { id: parseInt(appId) },
- });
-
- if (!app) {
- return next(errors.createError(404, "应用不存在"));
- }
-
-
// 生成token
const token = crypto.randomBytes(32).toString("hex");
@@ -75,7 +64,7 @@ router.post(
const installation = await prisma.appInstall.create({
data: {
deviceId: device.id,
- appId: app.id,
+ appId: appId,
token,
note: note || null,
},
@@ -83,8 +72,7 @@ router.post(
return res.status(201).json({
id: installation.id,
- appId: app.id,
- appName: app.name,
+ appId: installation.appId,
token: installation.token,
note: installation.note,
installedAt: installation.createdAt,
@@ -124,30 +112,6 @@ router.delete(
})
);
-/**
- * GET /apps
- * 获取所有可用应用列表
- */
-router.get(
- "/",
- errors.catchAsync(async (req, res) => {
- const apps = await prisma.app.findMany({
- select: {
- id: true,
- name: true,
- description: true,
- createdAt: true,
- },
- });
-
- return res.json({
- success: true,
- apps,
- });
- })
-);
-
-
/**
* GET /apps/tokens
* 获取设备的token列表 (需要设备UUID)
@@ -173,24 +137,13 @@ router.get(
// 获取该设备的所有应用安装记录(即token)
const installations = await prisma.appInstall.findMany({
where: { deviceId: device.id },
- include: {
- app: {
- select: {
- id: true,
- name: true,
- description: true,
- },
- },
- },
orderBy: { installedAt: 'desc' },
});
const tokens = installations.map(install => ({
- id: install.id, // 安装记录ID
+ id: install.id,
token: install.token,
- appId: install.app.id,
- appName: install.app.name,
- appDescription: install.app.description,
+ appId: install.appId,
installedAt: install.installedAt,
note: install.note,
}));
@@ -202,16 +155,5 @@ router.get(
});
})
);
-router.get("/info/:appid",
- errors.catchAsync(async (req, res, next) => {
- const { appid } = req.params;
- const app = await prisma.app.findUnique({
- where: { id: parseInt(appid) },
- });
- if (!app) {
- return next(errors.createError(404, "应用不存在"));
- }
- return res.json(app);
- })
-);
+
export default router;
\ No newline at end of file
diff --git a/routes/kv-token.js b/routes/kv-token.js
index 8bd9902..674703a 100644
--- a/routes/kv-token.js
+++ b/routes/kv-token.js
@@ -3,10 +3,58 @@ const router = Router();
import kvStore from "../utils/kvStore.js";
import { kvTokenAuth } from "../middleware/kvTokenAuth.js";
import errors from "../utils/errors.js";
+import { PrismaClient } from "@prisma/client";
+
+const prisma = new PrismaClient();
// 使用KV专用token认证
router.use(kvTokenAuth);
+/**
+ * GET /_info
+ * 获取当前token所属设备的信息,如果关联了账号也返回账号信息
+ */
+router.get(
+ "/_info",
+ errors.catchAsync(async (req, res) => {
+ const deviceId = res.locals.deviceId;
+
+ // 获取设备信息,包含关联的账号
+ const device = await prisma.device.findUnique({
+ where: { id: deviceId },
+ include: {
+ account: true,
+ },
+ });
+
+ if (!device) {
+ return next(errors.createError(404, "设备不存在"));
+ }
+
+ // 构建响应对象
+ const response = {
+ device: {
+ id: device.id,
+ uuid: device.uuid,
+ name: device.name,
+ createdAt: device.createdAt,
+ updatedAt: device.updatedAt,
+ },
+ };
+
+ // 如果关联了账号,添加账号信息
+ if (device.account) {
+ response.account = {
+ id: device.account.id,
+ name: device.account.name,
+ avatarUrl: device.account.avatarUrl,
+ };
+ }
+
+ return res.json(response);
+ })
+);
+
/**
* GET /_keys
* 获取当前token对应设备的键名列表(分页,不包括内容)