import { Request, Response, NextFunction } from 'express'; import { createLogger, APIError, constants, Env, decryptString, validateConfig, Resource, } from '@aiostreams/core'; import { UserDataSchema, UserRepository, UserData } from '@aiostreams/core'; import { StremioTransformer } from '@aiostreams/core'; const logger = createLogger('server'); // Valid resources that require authentication // const VALID_RESOURCES = ['stream', 'configure']; const VALID_RESOURCES = [...constants.RESOURCES, 'manifest.json', 'configure']; export const userDataMiddleware = async ( req: Request, res: Response, next: NextFunction ) => { const { uuid, encryptedPassword } = req.params; // Both uuid and encryptedPassword should be present since we mounted the router on this path if (!uuid || !encryptedPassword) { next(new APIError(constants.ErrorCode.USER_NOT_FOUND)); return; } // First check - validate path has two components followed by valid resource const resourceRegex = new RegExp(`/(${VALID_RESOURCES.join('|')})`); const resourceMatch = req.path.match(resourceRegex); if (!resourceMatch) { next(); return; } // Second check - validate UUID format (simpler regex that just checks UUID format) const uuidRegex = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i; if (!uuidRegex.test(uuid)) { next(new APIError(constants.ErrorCode.USER_NOT_FOUND)); return; } const resource = resourceMatch[1]; try { // Check if user exists const userExists = await UserRepository.checkUserExists(uuid); if (!userExists) { if (constants.RESOURCES.includes(resource as Resource)) { res.status(200).json( StremioTransformer.createDynamicError(resource as Resource, { errorDescription: 'User not found', }) ); return; } next(new APIError(constants.ErrorCode.USER_NOT_FOUND)); return; } let password = undefined; // decrypt the encrypted password const { success: successfulDecryption, data: decryptedPassword } = decryptString(encryptedPassword!); if (!successfulDecryption) { if (constants.RESOURCES.includes(resource as Resource)) { res.status(200).json( StremioTransformer.createDynamicError(resource as Resource, { errorDescription: 'Invalid password', }) ); return; } next(new APIError(constants.ErrorCode.USER_ERROR)); return; } // Get and validate user data let userData = await UserRepository.getUser(uuid, decryptedPassword); if (!userData) { if (constants.RESOURCES.includes(resource as Resource)) { res.status(200).json( StremioTransformer.createDynamicError(resource as Resource, { errorDescription: 'Invalid password', }) ); return; } next(new APIError(constants.ErrorCode.USER_INVALID_PASSWORD)); return; } userData.encryptedPassword = encryptedPassword; userData.uuid = uuid; if (resource !== 'configure') { try { userData = await validateConfig(userData, true, true); } catch (error: any) { if (constants.RESOURCES.includes(resource as Resource)) { res.status(200).json( StremioTransformer.createDynamicError(resource as Resource, { errorDescription: error.message, }) ); return; } logger.error(`Invalid config for ${uuid}: ${error.message}`); next( new APIError( constants.ErrorCode.USER_INVALID_CONFIG, undefined, error.message ) ); return; } } // Attach validated data to request req.userData = userData; req.userData.ip = req.userIp; req.uuid = uuid; next(); } catch (error: any) { logger.error(error.message); if (error instanceof APIError) { next(error); } else { next(new APIError(constants.ErrorCode.INTERNAL_SERVER_ERROR)); } } };