1
1
mirror of https://github.com/ZeroCatDev/ClassworksKV.git synced 2025-12-07 13:03:09 +00:00
ClassworksKV/routes/auto-auth.js
2025-11-16 16:15:05 +08:00

346 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {Router} from "express";
import {jwtAuth} from "../middleware/jwt-auth.js";
import {PrismaClient} from "@prisma/client";
import errors from "../utils/errors.js";
const router = Router();
const prisma = new PrismaClient();
/**
* GET /auto-auth/devices/:uuid/auth-configs
* 获取设备的所有自动授权配置 (需要 JWT 认证,且设备必须绑定到该账户)
*/
router.get(
"/devices/:uuid/auth-configs",
jwtAuth,
errors.catchAsync(async (req, res, next) => {
const {uuid} = req.params;
const account = res.locals.account;
// 查找设备并验证是否属于当前账户
const device = await prisma.device.findUnique({
where: {uuid},
});
if (!device) {
return next(errors.createError(404, "设备不存在"));
}
// 验证设备是否绑定到当前账户
if (!device.accountId || device.accountId !== account.id) {
return next(errors.createError(403, "该设备未绑定到您的账户"));
}
const autoAuths = await prisma.autoAuth.findMany({
where: {deviceId: device.id},
orderBy: {createdAt: 'desc'},
});
// 返回配置,智能处理密码显示
const configs = autoAuths.map(auth => {
// 检查是否是 bcrypt 哈希密码
const isHashedPassword = auth.password && auth.password.startsWith('$2');
return {
id: auth.id,
password: isHashedPassword ? null : auth.password, // 哈希密码不返回
isLegacyHash: isHashedPassword, // 标记是否为旧的哈希密码
deviceType: auth.deviceType,
isReadOnly: auth.isReadOnly,
createdAt: auth.createdAt,
updatedAt: auth.updatedAt,
};
});
return res.json({
success: true,
configs,
});
})
);
/**
* POST /auto-auth/devices/:uuid/auth-configs
* 创建新的自动授权配置 (需要 JWT 认证,且设备必须绑定到该账户)
* Body: { password?: string, deviceType?: string, isReadOnly?: boolean }
*/
router.post(
"/devices/:uuid/auth-configs",
jwtAuth,
errors.catchAsync(async (req, res, next) => {
const {uuid} = req.params;
const account = res.locals.account;
const {password, deviceType, isReadOnly} = req.body;
// 查找设备并验证是否属于当前账户
const device = await prisma.device.findUnique({
where: {uuid},
});
if (!device) {
return next(errors.createError(404, "设备不存在"));
}
// 验证设备是否绑定到当前账户
if (!device.accountId || device.accountId !== account.id) {
return next(errors.createError(403, "该设备未绑定到您的账户"));
}
// 验证 deviceType 如果提供的话
const validDeviceTypes = ['teacher', 'student', 'classroom', 'parent'];
if (deviceType && !validDeviceTypes.includes(deviceType)) {
return next(errors.createError(400, `设备类型必须是以下之一: ${validDeviceTypes.join(', ')}`));
}
// 规范化密码:空字符串视为 null
const plainPassword = (password !== undefined && password !== '') ? password : null;
// 查询该设备的所有自动授权配置,本地检查是否存在相同密码
const allAuths = await prisma.autoAuth.findMany({
where: {deviceId: device.id},
});
const existingAuth = allAuths.find(auth => auth.password === plainPassword);
if (existingAuth) {
return next(errors.createError(400, "该密码的自动授权配置已存在"));
}
// 创建新的自动授权配置(密码明文存储)
const autoAuth = await prisma.autoAuth.create({
data: {
deviceId: device.id,
password: plainPassword,
deviceType: deviceType || null,
isReadOnly: isReadOnly || false,
},
});
return res.status(201).json({
success: true,
config: {
id: autoAuth.id,
password: autoAuth.password, // 返回明文密码
deviceType: autoAuth.deviceType,
isReadOnly: autoAuth.isReadOnly,
createdAt: autoAuth.createdAt,
},
});
})
);
/**
* PUT /auto-auth/devices/:uuid/auth-configs/:configId
* 更新自动授权配置 (需要 JWT 认证,且设备必须绑定到该账户)
* Body: { password?: string, deviceType?: string, isReadOnly?: boolean }
*/
router.put(
"/devices/:uuid/auth-configs/:configId",
jwtAuth,
errors.catchAsync(async (req, res, next) => {
const {uuid, configId} = req.params;
const account = res.locals.account;
const {password, deviceType, isReadOnly} = req.body;
// 查找设备并验证是否属于当前账户
const device = await prisma.device.findUnique({
where: {uuid},
});
if (!device) {
return next(errors.createError(404, "设备不存在"));
}
// 验证设备是否绑定到当前账户
if (!device.accountId || device.accountId !== account.id) {
return next(errors.createError(403, "该设备未绑定到您的账户"));
}
// 查找自动授权配置
const autoAuth = await prisma.autoAuth.findUnique({
where: {id: configId},
});
if (!autoAuth) {
return next(errors.createError(404, "自动授权配置不存在"));
}
// 确保配置属于当前设备
if (autoAuth.deviceId !== device.id) {
return next(errors.createError(403, "无权操作此配置"));
}
// 验证 deviceType
const validDeviceTypes = ['teacher', 'student', 'classroom', 'parent'];
if (deviceType && !validDeviceTypes.includes(deviceType)) {
return next(errors.createError(400, `设备类型必须是以下之一: ${validDeviceTypes.join(', ')}`));
}
// 准备更新数据
const updateData = {};
if (password !== undefined) {
// 规范化密码:空字符串视为 null
const plainPassword = (password !== '') ? password : null;
// 查询该设备的所有配置,本地检查新密码是否与其他配置冲突
const allAuths = await prisma.autoAuth.findMany({
where: {deviceId: device.id},
});
const conflictAuth = allAuths.find(auth =>
auth.id !== configId && auth.password === plainPassword
);
if (conflictAuth) {
return next(errors.createError(400, "该密码已被其他配置使用"));
}
updateData.password = plainPassword;
}
if (deviceType !== undefined) {
updateData.deviceType = deviceType || null;
}
if (isReadOnly !== undefined) {
updateData.isReadOnly = isReadOnly;
}
// 更新配置
const updatedAuth = await prisma.autoAuth.update({
where: {id: configId},
data: updateData,
});
return res.json({
success: true,
config: {
id: updatedAuth.id,
password: updatedAuth.password, // 返回明文密码
deviceType: updatedAuth.deviceType,
isReadOnly: updatedAuth.isReadOnly,
updatedAt: updatedAuth.updatedAt,
},
});
})
);
/**
* DELETE /auto-auth/devices/:uuid/auth-configs/:configId
* 删除自动授权配置 (需要 JWT 认证,且设备必须绑定到该账户)
*/
router.delete(
"/devices/:uuid/auth-configs/:configId",
jwtAuth,
errors.catchAsync(async (req, res, next) => {
const {uuid, configId} = req.params;
const account = res.locals.account;
// 查找设备并验证是否属于当前账户
const device = await prisma.device.findUnique({
where: {uuid},
});
if (!device) {
return next(errors.createError(404, "设备不存在"));
}
// 验证设备是否绑定到当前账户
if (!device.accountId || device.accountId !== account.id) {
return next(errors.createError(403, "该设备未绑定到您的账户"));
}
// 查找自动授权配置
const autoAuth = await prisma.autoAuth.findUnique({
where: {id: configId},
});
if (!autoAuth) {
return next(errors.createError(404, "自动授权配置不存在"));
}
// 确保配置属于当前设备
if (autoAuth.deviceId !== device.id) {
return next(errors.createError(403, "无权操作此配置"));
}
// 删除配置
await prisma.autoAuth.delete({
where: {id: configId},
});
return res.status(204).end();
})
);
/**
* PUT /auto-auth/devices/:uuid/namespace
* 修改设备的 namespace (需要 JWT 认证,且设备必须绑定到该账户)
* Body: { namespace: string }
*/
router.put(
"/devices/:uuid/namespace",
jwtAuth,
errors.catchAsync(async (req, res, next) => {
const {uuid} = req.params;
const account = res.locals.account;
const {namespace} = req.body;
if (!namespace) {
return next(errors.createError(400, "需要提供 namespace"));
}
// 规范化 namespace去除首尾空格
const trimmedNamespace = namespace.trim();
if (!trimmedNamespace) {
return next(errors.createError(400, "namespace 不能为空"));
}
// 查找设备并验证是否属于当前账户
const device = await prisma.device.findUnique({
where: {uuid},
});
if (!device) {
return next(errors.createError(404, "设备不存在"));
}
// 验证设备是否绑定到当前账户
if (!device.accountId || device.accountId !== account.id) {
return next(errors.createError(403, "该设备未绑定到您的账户"));
}
// 检查新的 namespace 是否已被其他设备使用
if (device.namespace !== trimmedNamespace) {
const existingDevice = await prisma.device.findUnique({
where: {namespace: trimmedNamespace},
});
if (existingDevice) {
return next(errors.createError(409, "该 namespace 已被其他设备使用"));
}
}
// 更新设备的 namespace
const updatedDevice = await prisma.device.update({
where: {id: device.id},
data: {namespace: trimmedNamespace},
});
return res.json({
success: true,
device: {
id: updatedDevice.id,
uuid: updatedDevice.uuid,
name: updatedDevice.name,
namespace: updatedDevice.namespace,
updatedAt: updatedDevice.updatedAt,
},
});
})
);
export default router;