mirror of
https://github.com/Viren070/MediaFusion.git
synced 2025-12-01 23:21:11 +01:00
Add support for preserving and resetting sensitive credentials config fields
Introduced mechanisms to handle preconfigured sensitive fields with masking and reset options in the UI. Extended User functionality to reuse existing encrypted configuration for sensitive fields when editing. Improved styling and user experience for managing credentials and secure configurations.
This commit is contained in:
+67
-6
@@ -166,21 +166,33 @@ async def configure(
|
||||
request: Request,
|
||||
user_data: schemas.UserData = Depends(get_user_data),
|
||||
kodi_code: str = None,
|
||||
secret_str: str = None,
|
||||
):
|
||||
response.headers.update(const.NO_CACHE_HEADERS)
|
||||
|
||||
configured_fields = []
|
||||
# Remove the password from the streaming provider
|
||||
if user_data.streaming_provider:
|
||||
user_data.streaming_provider.password = None
|
||||
user_data.streaming_provider.token = None
|
||||
user_data.streaming_provider.password = "••••••••"
|
||||
user_data.streaming_provider.token = "••••••••"
|
||||
configured_fields.extend(["provider_token", "password"])
|
||||
|
||||
if user_data.streaming_provider.qbittorrent_config:
|
||||
user_data.streaming_provider.qbittorrent_config.qbittorrent_password = None
|
||||
user_data.streaming_provider.qbittorrent_config.webdav_password = None
|
||||
user_data.streaming_provider.qbittorrent_config.qbittorrent_password = (
|
||||
"••••••••"
|
||||
)
|
||||
user_data.streaming_provider.qbittorrent_config.webdav_password = "••••••••"
|
||||
configured_fields.extend(["qbittorrent_password", "webdav_password"])
|
||||
|
||||
# Remove the password from the mediaflow proxy
|
||||
if user_data.mediaflow_config:
|
||||
user_data.mediaflow_config.api_password = None
|
||||
user_data.mediaflow_config.api_password = "••••••••"
|
||||
configured_fields.append("mediaflow_api_password")
|
||||
|
||||
# Check RPDB configuration
|
||||
if user_data.rpdb_config:
|
||||
user_data.rpdb_config.api_key = "••••••••"
|
||||
configured_fields.append("rpdb_api_key")
|
||||
|
||||
user_data.api_password = None
|
||||
|
||||
@@ -223,6 +235,8 @@ async def configure(
|
||||
and not settings.is_public_instance,
|
||||
"kodi_code": kodi_code,
|
||||
"disabled_providers": settings.disabled_providers,
|
||||
"configured_fields": configured_fields,
|
||||
"secret_str": secret_str,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -620,8 +634,13 @@ async def get_streams(
|
||||
|
||||
|
||||
@app.post("/encrypt-user-data", tags=["user_data"])
|
||||
@app.post("/encrypt-user-data/{existing_secret_str}", tags=["user_data"])
|
||||
@wrappers.rate_limit(30, 60 * 5, "user_data")
|
||||
async def encrypt_user_data(user_data: schemas.UserData, request: Request):
|
||||
async def encrypt_user_data(
|
||||
user_data: schemas.UserData,
|
||||
request: Request,
|
||||
existing_secret_str: str | None = None,
|
||||
):
|
||||
async def _validate_all_config() -> dict:
|
||||
if "p2p" in settings.disabled_providers and not user_data.streaming_provider:
|
||||
return {
|
||||
@@ -662,6 +681,48 @@ async def encrypt_user_data(user_data: schemas.UserData, request: Request):
|
||||
"message": f"Unexpected error during validation: {str(e)}",
|
||||
}
|
||||
|
||||
if existing_secret_str:
|
||||
try:
|
||||
existing_config = crypto.decrypt_user_data(existing_secret_str)
|
||||
except ValueError:
|
||||
existing_config = schemas.UserData()
|
||||
|
||||
if user_data.streaming_provider and existing_config.streaming_provider:
|
||||
if user_data.streaming_provider.password == "••••••••":
|
||||
user_data.streaming_provider.password = (
|
||||
existing_config.streaming_provider.password
|
||||
)
|
||||
if user_data.streaming_provider.token == "••••••••":
|
||||
user_data.streaming_provider.token = (
|
||||
existing_config.streaming_provider.token
|
||||
)
|
||||
|
||||
if user_data.streaming_provider.qbittorrent_config:
|
||||
if (
|
||||
user_data.streaming_provider.qbittorrent_config.qbittorrent_password
|
||||
== "••••••••"
|
||||
):
|
||||
user_data.streaming_provider.qbittorrent_config.qbittorrent_password = (
|
||||
existing_config.streaming_provider.qbittorrent_config.qbittorrent_password
|
||||
)
|
||||
if (
|
||||
user_data.streaming_provider.qbittorrent_config.webdav_password
|
||||
== "••••••••"
|
||||
):
|
||||
user_data.streaming_provider.qbittorrent_config.webdav_password = (
|
||||
existing_config.streaming_provider.qbittorrent_config.webdav_password
|
||||
)
|
||||
|
||||
if user_data.mediaflow_config and existing_config.mediaflow_config:
|
||||
if user_data.mediaflow_config.api_password == "••••••••":
|
||||
user_data.mediaflow_config.api_password = (
|
||||
existing_config.mediaflow_config.api_password
|
||||
)
|
||||
|
||||
if user_data.rpdb_config and existing_config.rpdb_config:
|
||||
if user_data.rpdb_config.api_key == "••••••••":
|
||||
user_data.rpdb_config.api_key = existing_config.rpdb_config.api_key
|
||||
|
||||
validation_result = await _validate_all_config()
|
||||
if validation_result["status"] == "error":
|
||||
return validation_result
|
||||
|
||||
@@ -49,6 +49,10 @@ class SecureLoggingMiddleware(BaseHTTPMiddleware):
|
||||
url_path = url_path.replace(
|
||||
request.path_params.get("secret_str"), "*MASKED*"
|
||||
)
|
||||
if request.path_params.get("existing_secret_str"):
|
||||
url_path = url_path.replace(
|
||||
request.path_params.get("existing_secret_str"), "*MASKED*"
|
||||
)
|
||||
logging.info(
|
||||
f'{ip} - "{request.method} {url_path} HTTP/1.1" {response.status_code} {process_time}'
|
||||
)
|
||||
|
||||
@@ -479,6 +479,18 @@ input[type="text"]:focus, input[type="password"]:focus, select.form-control:focu
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.configured-field {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.reset-config-btn {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.input-group .reset-config-btn {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.sort-item {
|
||||
background: rgba(var(--bs-primary-rgb), 0.05);
|
||||
|
||||
@@ -237,10 +237,12 @@
|
||||
<div id="token_input" class="mb-3" style="display:none">
|
||||
<label for="provider_token" class="mb-3">Token: <span id="provider_token_tooltip" class="bi bi-question-circle" data-bs-toggle="tooltip" data-bs-placement="top"
|
||||
title="Enter Encoded Token previously generated or Click 'Authorize' to generate a new token or Provide your Private Token."></span></label>
|
||||
<input class="form-control" type="text" name="provider_token" id="provider_token" placeholder="Enter Token"
|
||||
value="{{ user_data.streaming_provider.token if user_data.streaming_provider.token else '' }}">
|
||||
<div class="invalid-feedback">
|
||||
Token is required.
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="text" name="provider_token" id="provider_token" placeholder="Enter Token"
|
||||
value="{{ user_data.streaming_provider.token if user_data.streaming_provider.token else '' }}">
|
||||
<div class="invalid-feedback">
|
||||
Token is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -894,6 +896,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="configured_fields" value='{{ configured_fields|tojson|safe }}'>
|
||||
<input type="hidden" id="existing_config" value="{{ secret_str if secret_str else '' }}">
|
||||
|
||||
<!-- JS for Bootstrap and form validation -->
|
||||
<script src="/static/js/jquery-3.6.0.min.js"></script>
|
||||
<script src="/static/js/popper.min.js"></script>
|
||||
|
||||
@@ -274,6 +274,7 @@ async function getInstallationUrl(isRedirect = false) {
|
||||
showLoadingWidget();
|
||||
|
||||
const userData = getUserData();
|
||||
const existingConfig = document.getElementById('existing_config').value;
|
||||
let urlPrefix = window.location.protocol + "//";
|
||||
if (isRedirect) {
|
||||
urlPrefix = "stremio://";
|
||||
@@ -284,8 +285,9 @@ async function getInstallationUrl(isRedirect = false) {
|
||||
showNotification('Validation failed. Please check your input.', 'error');
|
||||
return null;
|
||||
}
|
||||
const encryptUrl = '/encrypt-user-data' + (existingConfig ? `/${existingConfig}` : '');
|
||||
|
||||
const response = await fetch('/encrypt-user-data', {
|
||||
const response = await fetch(encryptUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -542,6 +544,52 @@ function setupPasswordToggle(passwordInputId, toggleButtonId, toggleIconId) {
|
||||
});
|
||||
}
|
||||
|
||||
// Function to handle configured credential fields
|
||||
function handleConfiguredFields(field, isConfigured = false) {
|
||||
const inputField = document.getElementById(field);
|
||||
const resetBtn = document.createElement('button');
|
||||
resetBtn.type = 'button';
|
||||
resetBtn.className = 'btn btn-outline-secondary reset-config-btn';
|
||||
resetBtn.innerHTML = '<i class="bi bi-arrow-counterclockwise"></i>';
|
||||
resetBtn.title = 'Reset Configuration';
|
||||
|
||||
if (isConfigured) {
|
||||
inputField.setAttribute('readonly', true);
|
||||
inputField.classList.add('configured-field');
|
||||
|
||||
// Add reset button next to the field
|
||||
if (!inputField.nextElementSibling?.classList.contains('reset-config-btn')) {
|
||||
inputField.parentElement.appendChild(resetBtn);
|
||||
}
|
||||
|
||||
// Handle reset button click
|
||||
resetBtn.onclick = () => {
|
||||
inputField.value = '';
|
||||
inputField.removeAttribute('readonly');
|
||||
inputField.classList.remove('configured-field');
|
||||
resetBtn.remove();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Function to initialize configured fields
|
||||
function initConfiguredFields(configuredFields) {
|
||||
const sensitiveFields = [
|
||||
'provider_token',
|
||||
'password',
|
||||
'qbittorrent_password',
|
||||
'webdav_password',
|
||||
'mediaflow_api_password',
|
||||
'rpdb_api_key'
|
||||
];
|
||||
|
||||
sensitiveFields.forEach(field => {
|
||||
if (configuredFields.includes(field)) {
|
||||
handleConfiguredFields(field, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function initiateKodiSetup() {
|
||||
// Show modal to input Kodi code
|
||||
const kodiCodeModal = new bootstrap.Modal(document.getElementById('kodiCodeModal'));
|
||||
@@ -700,12 +748,12 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
setupPasswordToggle('rpdb_api_key', 'toggleRPDBApiKey', 'toggleRPDBApiKeyIcon');
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
// Initialize sort direction toggles
|
||||
const toggleButtons = document.querySelectorAll('.sort-direction-toggle');
|
||||
toggleButtons.forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
button.addEventListener('click', function () {
|
||||
const sortId = this.dataset.sortId;
|
||||
const directionInput = document.getElementById(`direction_${sortId}`);
|
||||
const currentDirection = directionInput.value;
|
||||
@@ -732,7 +780,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Enable/disable sort direction buttons based on checkbox state
|
||||
const sortCheckboxes = document.querySelectorAll('[name="selected_sorting_options"]');
|
||||
sortCheckboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('change', function() {
|
||||
checkbox.addEventListener('change', function () {
|
||||
const sortId = this.value;
|
||||
const toggleButton = document.querySelector(`[data-sort-id="${sortId}"]`);
|
||||
const sortItem = this.closest('.sort-item');
|
||||
@@ -811,6 +859,10 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Initialize configured fields if they exist
|
||||
const configuredFields = JSON.parse(document.getElementById('configured_fields')?.value || '[]');
|
||||
initConfiguredFields(configuredFields);
|
||||
|
||||
// Show or hide the language sort section based on the sorting options
|
||||
document.querySelectorAll('input[name="selected_sorting_options"]').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', function () {
|
||||
|
||||
Reference in New Issue
Block a user