mirror of
https://github.com/ZeroCatDev/ClassworksKV.git
synced 2025-10-22 10:23:12 +00:00
131 lines
3.3 KiB
JavaScript
131 lines
3.3 KiB
JavaScript
/**
|
||
* UUID+密码/JWT混合认证中间件
|
||
*
|
||
* 1. 必须提供UUID,读取设备信息并存储到res.locals
|
||
* 2. 验证密码或账户JWT(二选一)
|
||
* 3. 适用于需要设备上下文的接口
|
||
*/
|
||
|
||
import { PrismaClient } from "@prisma/client";
|
||
import errors from "../utils/errors.js";
|
||
import { verifyToken as verifyAccountJWT } from "../utils/jwt.js";
|
||
import { verifyDevicePassword } from "../utils/crypto.js";
|
||
|
||
const prisma = new PrismaClient();
|
||
|
||
/**
|
||
* UUID+密码/JWT混合认证中间件
|
||
*/
|
||
export const uuidAuth = async (req, res, next) => {
|
||
try {
|
||
// 1. 获取UUID(必需)
|
||
const uuid = extractUuid(req);
|
||
if (!uuid) {
|
||
return next(errors.createError(400, "需要提供设备UUID"));
|
||
}
|
||
|
||
// 2. 查找设备并存储到locals
|
||
const device = await prisma.device.findUnique({
|
||
where: { uuid },
|
||
});
|
||
|
||
if (!device) {
|
||
return next(errors.createError(404, "设备不存在"));
|
||
}
|
||
|
||
// 存储设备信息到locals
|
||
res.locals.device = device;
|
||
res.locals.deviceId = device.id;
|
||
|
||
// 3. 验证密码或JWT(二选一)
|
||
const password = extractPassword(req);
|
||
const jwt = extractJWT(req);
|
||
|
||
if (jwt) {
|
||
// 验证账户JWT
|
||
try {
|
||
const accountPayload = await verifyAccountJWT(jwt);
|
||
const account = await prisma.account.findUnique({
|
||
where: { id: accountPayload.accountId },
|
||
include: {
|
||
devices: {
|
||
where: { uuid },
|
||
select: { id: true }
|
||
}
|
||
}
|
||
});
|
||
|
||
if (!account) {
|
||
return next(errors.createError(401, "账户不存在"));
|
||
}
|
||
|
||
// 检查设备是否绑定到此账户
|
||
if (account.devices.length === 0) {
|
||
return next(errors.createError(403, "设备未绑定到此账户"));
|
||
}
|
||
|
||
res.locals.account = account;
|
||
res.locals.isAccountOwner = true; // 标记为账户拥有者
|
||
return next();
|
||
} catch (error) {
|
||
return next(errors.createError(401, "无效的JWT token"));
|
||
}
|
||
} else if (password) {
|
||
// 验证设备密码
|
||
if (!device.password) {
|
||
return next(); // 如果设备未设置密码,允许无密码访问
|
||
}
|
||
|
||
const isValid = await verifyDevicePassword(password, device.password);
|
||
if (!isValid) {
|
||
return next(errors.createError(401, "密码错误"));
|
||
}
|
||
|
||
return next();
|
||
} else {
|
||
// 如果设备未设置密码,允许无密码访问
|
||
if (!device.password) {
|
||
return next();
|
||
}
|
||
return next(errors.createError(401, "需要提供密码或JWT token"));
|
||
}
|
||
} catch (error) {
|
||
next(error);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 从请求中提取UUID
|
||
*/
|
||
function extractUuid(req) {
|
||
return (
|
||
req.headers["x-device-uuid"] ||
|
||
req.query.uuid ||
|
||
req.params.uuid ||
|
||
req.params.deviceUuid ||
|
||
(req.body && req.body.uuid) ||
|
||
(req.body && req.body.deviceUuid)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 从请求中提取密码
|
||
*/
|
||
function extractPassword(req) {
|
||
return (
|
||
req.headers["x-device-password"] ||
|
||
req.query.password ||
|
||
req.query.currentPassword
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 从请求中提取JWT
|
||
*/
|
||
function extractJWT(req) {
|
||
const authHeader = req.headers.authorization;
|
||
if (authHeader && authHeader.startsWith("Bearer ")) {
|
||
return authHeader.substring(7);
|
||
}
|
||
return null;
|
||
} |