merlyn/server/endpoints/mobile/middleware/index.js
Timothy Carambat c218a0dfe3
Mobile sync support (#4173)
* WIP on mobile connections
todo: register devices
todo: data sync or connection

* improve connection flow and registration
add streaming from service
TODO: user scoping

* dev build mobile support

* fix path

* handle relative URLs

* handle localhost access in product

* add device de-register

* sync styles

* move UI to be out of the normal path since beta only

* Add user scoping to mobile connection requests
Remigrate DB for user associations
Implement temp token registration to prevent unauthorized device registration requests
cleanup middlewares
2025-07-31 12:28:03 -07:00

98 lines
3.3 KiB
JavaScript

const { MobileDevice } = require("../../../models/mobileDevice");
const { SystemSettings } = require("../../../models/systemSettings");
const { User } = require("../../../models/user");
/**
* Validates the device id from the request headers by checking if the device
* exists in the database and is approved.
* @param {import("express").Request} request
* @param {import("express").Response} response
* @param {import("express").NextFunction} next
*/
async function validDeviceToken(request, response, next) {
try {
const token = request.header("x-anythingllm-mobile-device-token");
if (!token)
return response.status(400).json({ error: "Device token is required" });
const device = await MobileDevice.get(
{ token: String(token) },
{ user: true }
);
if (!device)
return response.status(400).json({ error: "Device not found" });
if (!device.approved)
return response.status(400).json({ error: "Device not approved" });
// If the device is associated with a user then we can associate it with the locals
// so we can reuse it later.
if (device.user) {
if (device.user.suspended)
return response.status(400).json({ error: "User is suspended." });
response.locals.user = device.user;
}
delete device.user;
response.locals.device = device;
next();
} catch (error) {
console.error("validDeviceToken", error);
response.status(500).json({ error: "Invalid middleware response" });
}
}
/**
* Validates a temporary registration token that is passed in the request
* and associates the user with the token (if valid). Temporary token is consumed
* and cannot be used again after this middleware is called.
* @param {*} request
* @param {*} response
* @param {*} next
*/
async function validRegistrationToken(request, response, next) {
try {
const authHeader = request.header("Authorization");
const tempToken = authHeader ? authHeader.split(" ")[1] : null;
if (!tempToken)
return response
.status(400)
.json({ error: "Registration token is required" });
const tempTokenData = MobileDevice.tempToken(tempToken);
if (!tempTokenData)
return response
.status(400)
.json({ error: "Invalid or expired registration token" });
// If in multi-user mode, we need to validate the user id
// associated exists, is not banned and then associate with locals so we can reuse it later.
// If not in multi-user mode then simply having a valid token is enough.
const multiUserMode = await SystemSettings.isMultiUserMode();
if (multiUserMode) {
if (!tempTokenData.userId)
return response
.status(400)
.json({ error: "User id not found in registration token" });
const user = await User.get({ id: Number(tempTokenData.userId) });
if (!user) return response.status(400).json({ error: "User not found" });
if (user.suspended)
return response
.status(400)
.json({ error: "User is suspended - cannot register device" });
response.locals.user = user;
}
next();
} catch (error) {
console.error("validRegistrationToken:error", error);
response.status(500).json({
error: "Invalid middleware response from validRegistrationToken",
});
}
}
module.exports = {
validDeviceToken,
validRegistrationToken,
};