brunner56's picture
implement app
0bfe2e3
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));
}
}
};