mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-05-06 16:36:48 +02:00
fix(users): Create User ACL ng-model default and safer submitUserCreation
- Initialize selectedACL in create user template so JSON POST always includes ACL - Explicit option values; pass default_acl_name from view for ng-init - Coerce websitesLimit to int; validate selectedACL before ACL.objects.get - ensure_csrf_cookie on createUser page load - Optional /etc/cyberpanel/csrf_trusted_origins for HTTPS IP:port panels - Allow changeUserACL on getUserHomeDirectories (parity with create user page) - Sync public/static and static copies of userManagment.js with app static
This commit is contained in:
@@ -53,6 +53,20 @@ _default_origins = [
|
||||
# Merge environment and default origins, avoiding duplicates
|
||||
CSRF_TRUSTED_ORIGINS = list(dict.fromkeys(_csrf_origins_list + _default_origins))
|
||||
|
||||
# Optional file: one trusted origin per line (e.g. https://203.0.113.1:2087) for IP:port panel access.
|
||||
# Create /etc/cyberpanel/csrf_trusted_origins on the server if JSON POSTs get 403 CSRF when using HTTPS by IP.
|
||||
_csrf_trusted_origins_file = '/etc/cyberpanel/csrf_trusted_origins'
|
||||
if os.path.isfile(_csrf_trusted_origins_file):
|
||||
try:
|
||||
with open(_csrf_trusted_origins_file, 'r', encoding='utf-8', errors='replace') as _csrf_f:
|
||||
for _csrf_line in _csrf_f:
|
||||
_csrf_line = _csrf_line.strip()
|
||||
if _csrf_line and not _csrf_line.startswith('#'):
|
||||
if _csrf_line not in CSRF_TRUSTED_ORIGINS:
|
||||
CSRF_TRUSTED_ORIGINS.append(_csrf_line)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
|
||||
@@ -2,10 +2,61 @@
|
||||
* Created by usman on 8/5/17.
|
||||
*/
|
||||
|
||||
/* Safe notification - use PNotify if available, else fallback to alert */
|
||||
function safePNotify(opts) {
|
||||
if (typeof PNotify !== 'undefined') {
|
||||
new PNotify(opts);
|
||||
} else {
|
||||
var msg = (opts.title || '') + (opts.text ? ': ' + opts.text : '');
|
||||
alert(msg || JSON.stringify(opts));
|
||||
}
|
||||
}
|
||||
|
||||
/* Java script code to create account */
|
||||
app.controller('createUserCtr', function ($scope, $http) {
|
||||
|
||||
// Home directory functionality
|
||||
$scope.homeDirectories = [];
|
||||
$scope.selectedHomeDirectory = '';
|
||||
$scope.selectedHomeDirectoryInfo = null;
|
||||
|
||||
// Load home directories on page load
|
||||
$scope.loadHomeDirectories = function() {
|
||||
var url = '/users/getUserHomeDirectories';
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
};
|
||||
$http.post(url, {}, config)
|
||||
.then(function(response) {
|
||||
if (response.data && response.data.status === 1) {
|
||||
$scope.homeDirectories = response.data.directories || [];
|
||||
} else {
|
||||
console.error('Error loading home directories:', response.data);
|
||||
$scope.homeDirectories = [];
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('Error loading home directories:', error);
|
||||
$scope.homeDirectories = [];
|
||||
});
|
||||
};
|
||||
|
||||
// Update home directory info when selection changes
|
||||
$scope.updateHomeDirectoryInfo = function() {
|
||||
if ($scope.selectedHomeDirectory) {
|
||||
$scope.selectedHomeDirectoryInfo = $scope.homeDirectories.find(function(dir) {
|
||||
return dir.id == $scope.selectedHomeDirectory;
|
||||
});
|
||||
} else {
|
||||
$scope.selectedHomeDirectoryInfo = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize home directories
|
||||
$scope.loadHomeDirectories();
|
||||
|
||||
$scope.acctsLimit = true;
|
||||
$scope.webLimits = true;
|
||||
$scope.userCreated = true;
|
||||
@@ -23,15 +74,19 @@ app.controller('createUserCtr', function ($scope, $http) {
|
||||
$scope.userCreationLoading = false;
|
||||
$scope.combinedLength = true;
|
||||
|
||||
|
||||
var firstName = $scope.firstName;
|
||||
var lastName = $scope.lastName;
|
||||
var firstName = $scope.firstName || '';
|
||||
var lastName = $scope.lastName || '';
|
||||
var email = $scope.email;
|
||||
var selectedACL = $scope.selectedACL;
|
||||
var websitesLimits = $scope.websitesLimits;
|
||||
var userName = $scope.userName;
|
||||
var password = $scope.password;
|
||||
|
||||
if (firstName.length + lastName.length > 20) {
|
||||
$scope.combinedLength = false;
|
||||
$scope.userCreationLoading = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var url = "/users/submitUserCreation";
|
||||
|
||||
@@ -43,12 +98,14 @@ app.controller('createUserCtr', function ($scope, $http) {
|
||||
websitesLimit: websitesLimits,
|
||||
userName: userName,
|
||||
password: password,
|
||||
securityLevel: $scope.securityLevel
|
||||
securityLevel: $scope.securityLevel,
|
||||
selectedHomeDirectory: $scope.selectedHomeDirectory || ''
|
||||
};
|
||||
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
'X-CSRFToken': getCookie('csrftoken'),
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -59,42 +116,31 @@ app.controller('createUserCtr', function ($scope, $http) {
|
||||
|
||||
|
||||
if (response.data.createStatus == 1) {
|
||||
|
||||
$scope.userCreated = false;
|
||||
$scope.userCreationFailed = false; // hide error on success
|
||||
$scope.userCreated = false; // show success
|
||||
$scope.userCreationFailed = false; // hide error
|
||||
$scope.couldNotConnect = true;
|
||||
$scope.userCreationLoading = true;
|
||||
|
||||
$scope.userName = userName;
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
$scope.acctsLimit = false;
|
||||
$scope.webLimits = false;
|
||||
$scope.userCreated = true;
|
||||
$scope.userCreationFailed = true; // show error on failure
|
||||
$scope.userCreationFailed = true; // true = show error alert
|
||||
$scope.couldNotConnect = true;
|
||||
$scope.userCreationLoading = true;
|
||||
|
||||
$scope.errorMessage = (response.data && (response.data.error_message || response.data.message || response.data.errorMessage)) || 'Unknown error';
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
function cantLoadInitialDatas(response) {
|
||||
|
||||
$scope.acctsLimit = false;
|
||||
$scope.webLimits = false;
|
||||
$scope.userCreated = true;
|
||||
$scope.userCreationFailed = false; // show "Could not connect" instead
|
||||
$scope.couldNotConnect = false;
|
||||
$scope.userCreationFailed = false; // hide server error, show connection error instead
|
||||
$scope.couldNotConnect = false; // show "Could not connect" message
|
||||
$scope.userCreationLoading = true;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -125,14 +171,28 @@ app.controller('createUserCtr', function ($scope, $http) {
|
||||
|
||||
|
||||
/* Java script code to modify user account */
|
||||
app.controller('modifyUser', function ($scope, $http) {
|
||||
app.controller('modifyUser', function ($scope, $http, $timeout) {
|
||||
|
||||
var qrCode = window.qr = new QRious({
|
||||
element: document.getElementById('qr'),
|
||||
var qrEl = document.getElementById('qr');
|
||||
var qrCode = window.qr = (qrEl && typeof QRious !== 'undefined') ? new QRious({
|
||||
element: qrEl,
|
||||
size: 200,
|
||||
value: 'QRious'
|
||||
});
|
||||
}) : null;
|
||||
if (!qrCode && qrEl) {
|
||||
try { window.qr = new QRious({ element: qrEl, size: 200, value: 'QRious' }); } catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
$scope.userSearch = '';
|
||||
/* Prefer global set by inline script (before Angular); fallback to script tag */
|
||||
var list = (typeof window.__CP_ACCT_NAMES !== 'undefined' && Array.isArray(window.__CP_ACCT_NAMES))
|
||||
? window.__CP_ACCT_NAMES
|
||||
: (function() {
|
||||
var el = document.getElementById('acctNamesData');
|
||||
if (!el || !el.textContent) return [];
|
||||
try { return JSON.parse(el.textContent); } catch (e) { return []; }
|
||||
})();
|
||||
$scope.acctNamesList = Array.isArray(list) ? list : [];
|
||||
|
||||
$scope.userModificationLoading = true;
|
||||
$scope.acctDetailsFetched = true;
|
||||
@@ -153,6 +213,223 @@ app.controller('modifyUser', function ($scope, $http) {
|
||||
$scope.qrHidden = true;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.copySecretKey = function() {
|
||||
if ($scope.secretKey) {
|
||||
// Create a temporary textarea element
|
||||
var tempTextarea = document.createElement('textarea');
|
||||
tempTextarea.value = $scope.secretKey;
|
||||
tempTextarea.style.position = 'fixed';
|
||||
tempTextarea.style.opacity = '0';
|
||||
document.body.appendChild(tempTextarea);
|
||||
|
||||
// Select and copy the text
|
||||
tempTextarea.select();
|
||||
tempTextarea.setSelectionRange(0, 99999); // For mobile devices
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
// Show success feedback (you can add a toast notification here if available)
|
||||
alert('Secret key copied to clipboard!');
|
||||
} catch (err) {
|
||||
alert('Failed to copy secret key. Please copy it manually.');
|
||||
}
|
||||
|
||||
// Remove the temporary element
|
||||
document.body.removeChild(tempTextarea);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.regenerateSecret = function() {
|
||||
if (!$scope.accountUsername) {
|
||||
alert('Please select a user first.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('Are you sure you want to regenerate the 2FA secret? This will generate a new secret key and you will need to update your authenticator app.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var url = "/users/regenerateTwoFASecret";
|
||||
var data = {
|
||||
accountUsername: $scope.accountUsername
|
||||
};
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
};
|
||||
|
||||
$http.post(url, data, config).then(function(response) {
|
||||
if (response.data.status === 1) {
|
||||
// Update the secret key and formatted version
|
||||
$scope.secretKey = response.data.secretKey;
|
||||
$scope.formattedSecretKey = response.data.secretKey.match(/.{1,4}/g).join(' ');
|
||||
|
||||
// Update the QR code with new provisioning URI
|
||||
if (qrCode) qrCode.set({ value: response.data.otpauth });
|
||||
|
||||
// Show success message
|
||||
alert('2FA secret has been successfully regenerated! Please update your authenticator app with the new QR code or secret key.');
|
||||
} else {
|
||||
alert('Error regenerating 2FA secret: ' + response.data.error_message);
|
||||
}
|
||||
}, function(error) {
|
||||
console.error('Error regenerating 2FA secret:', error);
|
||||
alert('Failed to regenerate 2FA secret. Please try again.');
|
||||
});
|
||||
};
|
||||
|
||||
// WebAuthn Functions
|
||||
$scope.loadWebAuthnData = function() {
|
||||
if (!$scope.accountUsername) return;
|
||||
$scope.webauthnDataLoaded = false;
|
||||
var url = '/webauthn/credentials/' + $scope.accountUsername + '/';
|
||||
$http.get(url).then(function(response) {
|
||||
if (response.data.success) {
|
||||
$scope.webauthnCredentials = response.data.credentials || [];
|
||||
$scope.webauthnEnabled = response.data.settings.enabled;
|
||||
$scope.webauthnRequirePasskey = response.data.settings.require_passkey;
|
||||
$scope.webauthnAllowMultiple = response.data.settings.allow_multiple_credentials;
|
||||
$scope.webauthnMaxCredentials = response.data.settings.max_credentials;
|
||||
$scope.canAddCredential = !!response.data.settings.can_add_credential;
|
||||
$scope.webauthnDataLoaded = true;
|
||||
} else {
|
||||
$scope.canAddCredential = true;
|
||||
}
|
||||
}, function(error) {
|
||||
console.error('Error loading WebAuthn data:', error);
|
||||
$scope.canAddCredential = true;
|
||||
$scope.webauthnCredentials = [];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.toggleWebAuthn = function() {
|
||||
if ($scope.webauthnEnabled) {
|
||||
$scope.loadWebAuthnData();
|
||||
} else {
|
||||
$scope.webauthnCredentials = [];
|
||||
$scope.canAddCredential = true;
|
||||
$scope.webauthnDataLoaded = false;
|
||||
}
|
||||
};
|
||||
|
||||
/* Inline passkey UX like diabetes.newstargeted.com/profile?tab=2fa: name input + button + message area, no modal */
|
||||
$scope.newPasskeyName = '';
|
||||
$scope.webauthnMessage = '';
|
||||
$scope.webauthnMessageError = false;
|
||||
$scope.registerPasskeyLoading = false;
|
||||
|
||||
$scope.registerNewPasskey = function() {
|
||||
if (!$scope.accountUsername) {
|
||||
$scope.webauthnMessage = 'Please select a user account first.';
|
||||
$scope.webauthnMessageError = true;
|
||||
return;
|
||||
}
|
||||
if (typeof window.cyberPanelWebAuthn === 'undefined') {
|
||||
$scope.webauthnMessage = 'WebAuthn script not loaded. Refresh the page (Ctrl+F5) and try again.';
|
||||
$scope.webauthnMessageError = true;
|
||||
return;
|
||||
}
|
||||
if (typeof window.cyberPanelWebAuthn.isSupported !== 'function' || !window.cyberPanelWebAuthn.isSupported()) {
|
||||
$scope.webauthnMessage = 'WebAuthn is not supported in this browser.';
|
||||
$scope.webauthnMessageError = true;
|
||||
return;
|
||||
}
|
||||
var name = ($scope.newPasskeyName || '').trim() || 'Security key';
|
||||
$scope.webauthnMessage = '';
|
||||
$scope.webauthnMessageError = false;
|
||||
$scope.registerPasskeyLoading = true;
|
||||
var username = $scope.accountUsername;
|
||||
window.cyberPanelWebAuthn.registerPasskey(username, name, { silent: true })
|
||||
.then(function(response) {
|
||||
if (response && response.success) {
|
||||
$scope.webauthnMessage = 'Passkey registered successfully.';
|
||||
$scope.webauthnMessageError = false;
|
||||
$scope.newPasskeyName = '';
|
||||
$timeout(function() { $scope.loadWebAuthnData(); }, 0);
|
||||
} else {
|
||||
$scope.webauthnMessage = (response && response.error) ? response.error : 'Registration failed.';
|
||||
$scope.webauthnMessageError = true;
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
var msg = (error && error.message) ? error.message : 'Passkey registration failed.';
|
||||
if (error && error.name === 'NotAllowedError') msg = 'Registration was cancelled or timed out.';
|
||||
$scope.webauthnMessage = msg;
|
||||
$scope.webauthnMessageError = true;
|
||||
console.error('Error registering passkey:', error);
|
||||
})
|
||||
.finally(function() {
|
||||
$scope.registerPasskeyLoading = false;
|
||||
if (!$scope.$$phase && !$scope.$root.$$phase) { try { $scope.$apply(); } catch (e) { /* already applied */ } }
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteCredential = function(credentialId) {
|
||||
if (!confirm('Are you sure you want to delete this passkey?')) return;
|
||||
|
||||
if (!window.cyberPanelWebAuthn) {
|
||||
alert('WebAuthn is not supported in this browser');
|
||||
return;
|
||||
}
|
||||
|
||||
window.cyberPanelWebAuthn.deleteCredential($scope.accountUsername, credentialId)
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
$scope.loadWebAuthnData();
|
||||
$scope.$apply();
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('Error deleting credential:', error);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.updateCredentialName = function(credentialId, newName) {
|
||||
if (!window.cyberPanelWebAuthn) return;
|
||||
|
||||
window.cyberPanelWebAuthn.updateCredentialName($scope.accountUsername, credentialId, newName)
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
$scope.loadWebAuthnData();
|
||||
$scope.$apply();
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('Error updating credential name:', error);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.refreshCredentials = function() {
|
||||
$scope.loadWebAuthnData();
|
||||
};
|
||||
|
||||
$scope.saveWebAuthnSettings = function() {
|
||||
if (!window.cyberPanelWebAuthn) {
|
||||
alert('WebAuthn is not supported in this browser');
|
||||
return;
|
||||
}
|
||||
|
||||
var settings = {
|
||||
enabled: $scope.webauthnEnabled,
|
||||
require_passkey: $scope.webauthnRequirePasskey,
|
||||
allow_multiple_credentials: $scope.webauthnAllowMultiple,
|
||||
max_credentials: $scope.webauthnMaxCredentials,
|
||||
timeout_seconds: $scope.webauthnTimeout
|
||||
};
|
||||
|
||||
window.cyberPanelWebAuthn.updateSettings($scope.accountUsername, settings)
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
$scope.loadWebAuthnData();
|
||||
$scope.$apply();
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('Error updating WebAuthn settings:', error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
$scope.fetchUserDetails = function () {
|
||||
@@ -191,11 +468,26 @@ app.controller('modifyUser', function ($scope, $http) {
|
||||
$scope.securityLevel = userDetails.securityLevel;
|
||||
$scope.currentSecurityLevel = userDetails.securityLevel;
|
||||
$scope.twofa = Boolean(userDetails.twofa);
|
||||
|
||||
// Format secret key with spaces for better readability
|
||||
if (userDetails.secretKey) {
|
||||
$scope.secretKey = userDetails.secretKey;
|
||||
$scope.formattedSecretKey = userDetails.secretKey.match(/.{1,4}/g).join(' ');
|
||||
}
|
||||
|
||||
// Initialize WebAuthn settings
|
||||
$scope.webauthnEnabled = false;
|
||||
$scope.webauthnRequirePasskey = false;
|
||||
$scope.webauthnAllowMultiple = true;
|
||||
$scope.webauthnMaxCredentials = 10;
|
||||
$scope.webauthnTimeout = 60;
|
||||
$scope.webauthnCredentials = [];
|
||||
$scope.canAddCredential = true;
|
||||
$scope.webauthnDataLoaded = false;
|
||||
// Load WebAuthn settings and credentials
|
||||
$scope.loadWebAuthnData();
|
||||
|
||||
qrCode.set({
|
||||
value: userDetails.otpauth
|
||||
});
|
||||
|
||||
if (qrCode) qrCode.set({ value: userDetails.otpauth });
|
||||
|
||||
$scope.userModificationLoading = true;
|
||||
$scope.acctDetailsFetched = false;
|
||||
@@ -214,7 +506,7 @@ app.controller('modifyUser', function ($scope, $http) {
|
||||
$scope.userModified = true;
|
||||
$scope.canotModifyUser = false; // hide modify error (only fetch failed)
|
||||
$scope.couldNotConnect = true;
|
||||
$scope.canotFetchDetails = true; // show fetch error
|
||||
$scope.canotFetchDetails = true; // show fetch error on failure
|
||||
$scope.detailsFetched = false;
|
||||
|
||||
|
||||
@@ -249,7 +541,7 @@ app.controller('modifyUser', function ($scope, $http) {
|
||||
$scope.userModificationLoading = false;
|
||||
$scope.acctDetailsFetched = false;
|
||||
$scope.userModified = true;
|
||||
$scope.canotModifyUser = false; // hide error until we know result
|
||||
$scope.canotModifyUser = false; // hide modify error until we know result
|
||||
$scope.couldNotConnect = true;
|
||||
$scope.canotFetchDetails = true;
|
||||
$scope.detailsFetched = true;
|
||||
@@ -273,18 +565,28 @@ app.controller('modifyUser', function ($scope, $http) {
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
email: email,
|
||||
passwordByPass: password,
|
||||
securityLevel: $scope.securityLevel,
|
||||
twofa: $scope.twofa
|
||||
};
|
||||
|
||||
// Only include password if it's provided and not empty
|
||||
if (password && password.trim()) {
|
||||
data.passwordByPass = password;
|
||||
}
|
||||
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
};
|
||||
|
||||
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
|
||||
$http.post(url, data, config).then(function(response) {
|
||||
ListInitialDatas(response);
|
||||
// Save WebAuthn settings after successful user modification (only if WebAuthn script loaded)
|
||||
if (response.data.saveStatus == 1 && window.cyberPanelWebAuthn) {
|
||||
$scope.saveWebAuthnSettings();
|
||||
}
|
||||
}, cantLoadInitialDatas);
|
||||
|
||||
|
||||
function ListInitialDatas(response) {
|
||||
@@ -297,7 +599,7 @@ app.controller('modifyUser', function ($scope, $http) {
|
||||
$scope.userModified = false;
|
||||
$scope.canotModifyUser = false; // hide modify error on success
|
||||
$scope.couldNotConnect = true;
|
||||
$scope.canotFetchDetails = true;
|
||||
$scope.canotFetchDetails = false; // hide "Cannot fetch details" on save success
|
||||
$scope.detailsFetched = true;
|
||||
$scope.userAccountsLimit = true;
|
||||
$scope.accountTypeView = true;
|
||||
@@ -312,7 +614,7 @@ app.controller('modifyUser', function ($scope, $http) {
|
||||
$scope.userModificationLoading = true;
|
||||
$scope.acctDetailsFetched = false;
|
||||
$scope.userModified = true;
|
||||
$scope.canotModifyUser = true; // show modify error
|
||||
$scope.canotModifyUser = true; // show modify error on failure
|
||||
$scope.couldNotConnect = true;
|
||||
$scope.canotFetchDetails = true;
|
||||
$scope.detailsFetched = true;
|
||||
@@ -335,7 +637,7 @@ app.controller('modifyUser', function ($scope, $http) {
|
||||
$scope.couldNotConnect = false;
|
||||
$scope.canotFetchDetails = true;
|
||||
$scope.detailsFetched = true;
|
||||
|
||||
$scope.errorMessage = (response && response.data && (response.data.error_message || response.data.message || response.data.errorMessage)) || 'Unknown error';
|
||||
|
||||
}
|
||||
|
||||
@@ -461,6 +763,10 @@ app.controller('deleteUser', function ($scope, $http) {
|
||||
|
||||
app.controller('createACLCTRL', function ($scope, $http) {
|
||||
|
||||
$scope.aclCreated = true;
|
||||
$scope.aclCreationFailed = false; // false = don't show error alert on load
|
||||
$scope.couldNotConnect = true;
|
||||
|
||||
$scope.aclLoading = true;
|
||||
$scope.makeAdmin = false;
|
||||
|
||||
@@ -629,14 +935,14 @@ app.controller('createACLCTRL', function ($scope, $http) {
|
||||
$scope.aclLoading = true;
|
||||
|
||||
if (response.data.status === 1) {
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Success!',
|
||||
text: 'ACL Successfully created.',
|
||||
type: 'success'
|
||||
});
|
||||
} else {
|
||||
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: response.data.errorMessage,
|
||||
type: 'error'
|
||||
@@ -651,7 +957,7 @@ app.controller('createACLCTRL', function ($scope, $http) {
|
||||
|
||||
$scope.aclLoading = false;
|
||||
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: 'Could not connect to server, please refresh this page.',
|
||||
type: 'error'
|
||||
@@ -781,7 +1087,7 @@ app.controller('createACLCTRL', function ($scope, $http) {
|
||||
// Email Management
|
||||
|
||||
$scope.createEmail = true;
|
||||
$scope.listEmails = True;
|
||||
$scope.listEmails = true;
|
||||
$scope.deleteEmail = true;
|
||||
$scope.emailForwarding = true;
|
||||
$scope.changeEmailPassword = true;
|
||||
@@ -850,14 +1156,14 @@ app.controller('deleteACTCTRL', function ($scope, $http) {
|
||||
$scope.aclLoading = true;
|
||||
|
||||
if (response.data.status === 1) {
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Success!',
|
||||
text: 'ACL Successfully deleted.',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
} else {
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: response.data.errorMessage,
|
||||
type: 'error'
|
||||
@@ -869,7 +1175,7 @@ app.controller('deleteACTCTRL', function ($scope, $http) {
|
||||
|
||||
function cantLoadInitialDatas(response) {
|
||||
$scope.aclLoading = true;
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: 'Could not connect to server, please refresh this page.',
|
||||
type: 'error'
|
||||
@@ -913,7 +1219,7 @@ app.controller('modifyACLCtrl', function ($scope, $http) {
|
||||
|
||||
|
||||
if (response.data.status === 1) {
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Success!',
|
||||
text: 'Current settings successfully fetched',
|
||||
type: 'success'
|
||||
@@ -993,7 +1299,7 @@ app.controller('modifyACLCtrl', function ($scope, $http) {
|
||||
$scope.mailServerSSL = Boolean(response.data.mailServerSSL);
|
||||
|
||||
} else {
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: response.data.errorMessage,
|
||||
type: 'error'
|
||||
@@ -1006,7 +1312,7 @@ app.controller('modifyACLCtrl', function ($scope, $http) {
|
||||
|
||||
$scope.aclLoading = false;
|
||||
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: 'Could not connect to server, please refresh this page.',
|
||||
type: 'error'
|
||||
@@ -1108,14 +1414,14 @@ app.controller('modifyACLCtrl', function ($scope, $http) {
|
||||
$scope.aclLoading = true;
|
||||
|
||||
if (response.data.status === 1) {
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Success!',
|
||||
text: 'ACL Successfully modified.',
|
||||
type: 'success'
|
||||
});
|
||||
} else {
|
||||
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: response.data.errorMessage,
|
||||
type: 'error'
|
||||
@@ -1130,7 +1436,7 @@ app.controller('modifyACLCtrl', function ($scope, $http) {
|
||||
|
||||
$scope.aclLoading = false;
|
||||
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: 'Could not connect to server, please refresh this page.',
|
||||
type: 'error'
|
||||
@@ -1260,7 +1566,7 @@ app.controller('modifyACLCtrl', function ($scope, $http) {
|
||||
// Email Management
|
||||
|
||||
$scope.createEmail = true;
|
||||
$scope.listEmails = True;
|
||||
$scope.listEmails = true;
|
||||
$scope.deleteEmail = true;
|
||||
$scope.emailForwarding = true;
|
||||
$scope.changeEmailPassword = true;
|
||||
@@ -1322,14 +1628,14 @@ app.controller('changeUserACLCTRL', function ($scope, $http) {
|
||||
$scope.aclLoading = true;
|
||||
|
||||
if (response.data.status === 1) {
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Success!',
|
||||
text: 'ACL Successfully changed.',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
} else {
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: response.data.errorMessage,
|
||||
type: 'error'
|
||||
@@ -1341,7 +1647,7 @@ app.controller('changeUserACLCTRL', function ($scope, $http) {
|
||||
|
||||
function cantLoadInitialDatas(response) {
|
||||
$scope.aclLoading = true;
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: 'Could not connect to server, please refresh this page.',
|
||||
type: 'error'
|
||||
@@ -1384,14 +1690,14 @@ app.controller('resellerCenterCTRL', function ($scope, $http) {
|
||||
$scope.aclLoading = true;
|
||||
|
||||
if (response.data.status === 1) {
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Success!',
|
||||
text: 'Changes successfully applied!',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
} else {
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: response.data.errorMessage,
|
||||
type: 'error'
|
||||
@@ -1403,7 +1709,7 @@ app.controller('resellerCenterCTRL', function ($scope, $http) {
|
||||
|
||||
function cantLoadInitialDatas(response) {
|
||||
$scope.aclLoading = true;
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: 'Could not connect to server, please refresh this page.',
|
||||
type: 'error'
|
||||
@@ -1453,14 +1759,14 @@ app.controller('apiAccessCTRL', function ($scope, $http) {
|
||||
|
||||
if (response.data.status === 1) {
|
||||
$scope.apiAccessDropDown = true;
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Success!',
|
||||
text: 'Changes successfully applied!',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
} else {
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: response.data.error_message,
|
||||
type: 'error'
|
||||
@@ -1472,7 +1778,7 @@ app.controller('apiAccessCTRL', function ($scope, $http) {
|
||||
|
||||
function cantLoadInitialDatas(response) {
|
||||
$scope.cyberpanelLoading = true;
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: 'Could not connect to server, please refresh this page.',
|
||||
type: 'error'
|
||||
@@ -1486,6 +1792,147 @@ app.controller('apiAccessCTRL', function ($scope, $http) {
|
||||
});
|
||||
/* Java script code for api access */
|
||||
|
||||
/* Java script code for api users list */
|
||||
app.controller('apiUsersCTRL', function ($scope, $http) {
|
||||
$scope.apiUsers = [];
|
||||
$scope.filteredUsers = [];
|
||||
$scope.searchQuery = '';
|
||||
$scope.apiUsersLoading = true;
|
||||
|
||||
$scope.loadAPIUsers = function() {
|
||||
$scope.apiUsersLoading = false;
|
||||
|
||||
var url = "/users/fetchAPIUsers";
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
};
|
||||
|
||||
$http.get(url, config).then(loadAPIUsersSuccess, loadAPIUsersError);
|
||||
};
|
||||
|
||||
function loadAPIUsersSuccess(response) {
|
||||
$scope.apiUsersLoading = true;
|
||||
|
||||
if (response.data.status === 1) {
|
||||
$scope.apiUsers = response.data.users;
|
||||
$scope.filteredUsers = response.data.users;
|
||||
|
||||
safePNotify({
|
||||
title: 'Success!',
|
||||
text: 'API users loaded successfully',
|
||||
type: 'success'
|
||||
});
|
||||
} else {
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: response.data.error_message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function loadAPIUsersError(response) {
|
||||
$scope.apiUsersLoading = true;
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: 'Could not load API users. Please refresh the page.',
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
|
||||
$scope.searchUsers = function() {
|
||||
if (!$scope.searchQuery || $scope.searchQuery.trim() === '') {
|
||||
$scope.filteredUsers = $scope.apiUsers;
|
||||
return;
|
||||
}
|
||||
|
||||
var query = $scope.searchQuery.toLowerCase();
|
||||
$scope.filteredUsers = $scope.apiUsers.filter(function(user) {
|
||||
return user.userName.toLowerCase().includes(query) ||
|
||||
user.firstName.toLowerCase().includes(query) ||
|
||||
user.lastName.toLowerCase().includes(query) ||
|
||||
user.email.toLowerCase().includes(query) ||
|
||||
user.aclName.toLowerCase().includes(query);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.clearSearch = function() {
|
||||
$scope.searchQuery = '';
|
||||
$scope.filteredUsers = $scope.apiUsers;
|
||||
};
|
||||
|
||||
$scope.viewUserDetails = function(user) {
|
||||
safePNotify({
|
||||
title: 'User Details',
|
||||
text: 'Username: ' + user.userName + '<br>' +
|
||||
'Full Name: ' + user.firstName + ' ' + user.lastName + '<br>' +
|
||||
'Email: ' + user.email + '<br>' +
|
||||
'ACL: ' + user.aclName + '<br>' +
|
||||
'Token Status: ' + user.tokenStatus + '<br>' +
|
||||
'State: ' + user.state,
|
||||
type: 'info',
|
||||
styling: 'bootstrap3',
|
||||
delay: 10000
|
||||
});
|
||||
};
|
||||
|
||||
$scope.disableAPI = function(user) {
|
||||
if (confirm('Are you sure you want to disable API access for ' + user.userName + '?')) {
|
||||
$scope.apiUsersLoading = false;
|
||||
|
||||
var url = "/users/saveChangesAPIAccess";
|
||||
var data = {
|
||||
accountUsername: user.userName,
|
||||
access: 'Disable'
|
||||
};
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
};
|
||||
|
||||
$http.post(url, data, config).then(disableAPISuccess, disableAPIError);
|
||||
}
|
||||
};
|
||||
|
||||
function disableAPISuccess(response) {
|
||||
$scope.apiUsersLoading = true;
|
||||
|
||||
if (response.data.status === 1) {
|
||||
// Remove user from the list
|
||||
$scope.apiUsers = $scope.apiUsers.filter(function(u) {
|
||||
return u.userName !== response.data.accountUsername;
|
||||
});
|
||||
$scope.filteredUsers = $scope.apiUsers;
|
||||
|
||||
safePNotify({
|
||||
title: 'Success!',
|
||||
text: 'API access disabled for ' + response.data.accountUsername,
|
||||
type: 'success'
|
||||
});
|
||||
} else {
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: response.data.error_message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function disableAPIError(response) {
|
||||
$scope.apiUsersLoading = true;
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: 'Could not disable API access. Please try again.',
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
|
||||
// Load API users when controller initializes
|
||||
$scope.loadAPIUsers();
|
||||
});
|
||||
|
||||
/* Java script code to list table users */
|
||||
|
||||
@@ -1556,14 +2003,14 @@ app.controller('listTableUsers', function ($scope, $http) {
|
||||
|
||||
$scope.records = JSON.parse(response.data.data);
|
||||
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Success!',
|
||||
text: 'Users successfully fetched!',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
} else {
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: response.data.error_message,
|
||||
type: 'error'
|
||||
@@ -1574,7 +2021,7 @@ app.controller('listTableUsers', function ($scope, $http) {
|
||||
|
||||
function cantLoadInitialDatas(response) {
|
||||
$scope.cyberpanelLoading = true;
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: 'Could not connect to server, please refresh this page.',
|
||||
type: 'error'
|
||||
@@ -1614,7 +2061,7 @@ app.controller('listTableUsers', function ($scope, $http) {
|
||||
if (response.data.deleteStatus === 1) {
|
||||
$scope.populateCurrentRecords();
|
||||
hideModalById('deleteModal');
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Success!',
|
||||
text: 'Users successfully deleted!',
|
||||
type: 'success'
|
||||
@@ -1622,7 +2069,7 @@ app.controller('listTableUsers', function ($scope, $http) {
|
||||
|
||||
} else {
|
||||
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: response.data.error_message,
|
||||
type: 'error'
|
||||
@@ -1636,7 +2083,7 @@ app.controller('listTableUsers', function ($scope, $http) {
|
||||
function cantLoadInitialDatas(response) {
|
||||
|
||||
$scope.cyberpanelLoading = false;
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: 'Could not connect to server, please refresh this page.',
|
||||
type: 'error'
|
||||
@@ -1649,10 +2096,8 @@ app.controller('listTableUsers', function ($scope, $http) {
|
||||
};
|
||||
|
||||
$scope.editInitial = function (name) {
|
||||
|
||||
$scope.name = name;
|
||||
showModalById('editModal');
|
||||
|
||||
};
|
||||
|
||||
$scope.saveResellerChanges = function () {
|
||||
@@ -1680,14 +2125,14 @@ app.controller('listTableUsers', function ($scope, $http) {
|
||||
if (response.data.status === 1) {
|
||||
$scope.populateCurrentRecords();
|
||||
hideModalById('editModal');
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Success!',
|
||||
text: 'Changes successfully applied!',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
} else {
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: response.data.errorMessage,
|
||||
type: 'error'
|
||||
@@ -1698,7 +2143,7 @@ app.controller('listTableUsers', function ($scope, $http) {
|
||||
}
|
||||
|
||||
function cantLoadInitialDatas(response) {
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: 'Could not connect to server, please refresh this page.',
|
||||
type: 'error'
|
||||
@@ -1734,14 +2179,14 @@ app.controller('listTableUsers', function ($scope, $http) {
|
||||
if (response.data.status === 1) {
|
||||
$scope.populateCurrentRecords();
|
||||
hideModalById('editModal');
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Success!',
|
||||
text: 'ACL Successfully changed.',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
} else {
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: response.data.errorMessage,
|
||||
type: 'error'
|
||||
@@ -1753,7 +2198,7 @@ app.controller('listTableUsers', function ($scope, $http) {
|
||||
|
||||
function cantLoadInitialDatas(response) {
|
||||
$scope.aclLoading = true;
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: 'Could not connect to server, please refresh this page.',
|
||||
type: 'error'
|
||||
@@ -1764,6 +2209,7 @@ app.controller('listTableUsers', function ($scope, $http) {
|
||||
};
|
||||
|
||||
$scope.controlUserState = function (userName, state) {
|
||||
|
||||
$scope.cyberpanelLoading = false;
|
||||
|
||||
var url = "/users/controlUserState";
|
||||
@@ -1786,7 +2232,7 @@ app.controller('listTableUsers', function ($scope, $http) {
|
||||
$scope.cyberpanelLoading = true;
|
||||
if (response.data.status === 1) {
|
||||
$scope.populateCurrentRecords();
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Success!',
|
||||
text: 'Action successfully started.',
|
||||
type: 'success'
|
||||
@@ -1794,7 +2240,7 @@ app.controller('listTableUsers', function ($scope, $http) {
|
||||
|
||||
} else {
|
||||
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: response.data.error_message,
|
||||
type: 'error'
|
||||
@@ -1808,7 +2254,7 @@ app.controller('listTableUsers', function ($scope, $http) {
|
||||
function cantLoadInitialDatas(response) {
|
||||
|
||||
$scope.cyberpanelLoading = false;
|
||||
new PNotify({
|
||||
safePNotify({
|
||||
title: 'Error!',
|
||||
text: 'Could not connect to server, please refresh this page.',
|
||||
type: 'error'
|
||||
|
||||
@@ -171,14 +171,28 @@ app.controller('createUserCtr', function ($scope, $http) {
|
||||
|
||||
|
||||
/* Java script code to modify user account */
|
||||
app.controller('modifyUser', function ($scope, $http) {
|
||||
app.controller('modifyUser', function ($scope, $http, $timeout) {
|
||||
|
||||
var qrCode = window.qr = new QRious({
|
||||
element: document.getElementById('qr'),
|
||||
var qrEl = document.getElementById('qr');
|
||||
var qrCode = window.qr = (qrEl && typeof QRious !== 'undefined') ? new QRious({
|
||||
element: qrEl,
|
||||
size: 200,
|
||||
value: 'QRious'
|
||||
});
|
||||
}) : null;
|
||||
if (!qrCode && qrEl) {
|
||||
try { window.qr = new QRious({ element: qrEl, size: 200, value: 'QRious' }); } catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
$scope.userSearch = '';
|
||||
/* Prefer global set by inline script (before Angular); fallback to script tag */
|
||||
var list = (typeof window.__CP_ACCT_NAMES !== 'undefined' && Array.isArray(window.__CP_ACCT_NAMES))
|
||||
? window.__CP_ACCT_NAMES
|
||||
: (function() {
|
||||
var el = document.getElementById('acctNamesData');
|
||||
if (!el || !el.textContent) return [];
|
||||
try { return JSON.parse(el.textContent); } catch (e) { return []; }
|
||||
})();
|
||||
$scope.acctNamesList = Array.isArray(list) ? list : [];
|
||||
|
||||
$scope.userModificationLoading = true;
|
||||
$scope.acctDetailsFetched = true;
|
||||
@@ -253,9 +267,7 @@ app.controller('modifyUser', function ($scope, $http) {
|
||||
$scope.formattedSecretKey = response.data.secretKey.match(/.{1,4}/g).join(' ');
|
||||
|
||||
// Update the QR code with new provisioning URI
|
||||
qrCode.set({
|
||||
value: response.data.otpauth
|
||||
});
|
||||
if (qrCode) qrCode.set({ value: response.data.otpauth });
|
||||
|
||||
// Show success message
|
||||
alert('2FA secret has been successfully regenerated! Please update your authenticator app with the new QR code or secret key.');
|
||||
@@ -271,20 +283,24 @@ app.controller('modifyUser', function ($scope, $http) {
|
||||
// WebAuthn Functions
|
||||
$scope.loadWebAuthnData = function() {
|
||||
if (!$scope.accountUsername) return;
|
||||
|
||||
$scope.webauthnDataLoaded = false;
|
||||
var url = '/webauthn/credentials/' + $scope.accountUsername + '/';
|
||||
|
||||
$http.get(url).then(function(response) {
|
||||
if (response.data.success) {
|
||||
$scope.webauthnCredentials = response.data.credentials;
|
||||
$scope.webauthnCredentials = response.data.credentials || [];
|
||||
$scope.webauthnEnabled = response.data.settings.enabled;
|
||||
$scope.webauthnRequirePasskey = response.data.settings.require_passkey;
|
||||
$scope.webauthnAllowMultiple = response.data.settings.allow_multiple_credentials;
|
||||
$scope.webauthnMaxCredentials = response.data.settings.max_credentials;
|
||||
$scope.canAddCredential = response.data.settings.can_add_credential;
|
||||
$scope.canAddCredential = !!response.data.settings.can_add_credential;
|
||||
$scope.webauthnDataLoaded = true;
|
||||
} else {
|
||||
$scope.canAddCredential = true;
|
||||
}
|
||||
}, function(error) {
|
||||
console.error('Error loading WebAuthn data:', error);
|
||||
$scope.canAddCredential = true;
|
||||
$scope.webauthnCredentials = [];
|
||||
});
|
||||
};
|
||||
|
||||
@@ -294,27 +310,59 @@ app.controller('modifyUser', function ($scope, $http) {
|
||||
} else {
|
||||
$scope.webauthnCredentials = [];
|
||||
$scope.canAddCredential = true;
|
||||
$scope.webauthnDataLoaded = false;
|
||||
}
|
||||
};
|
||||
|
||||
/* Inline passkey UX like diabetes.newstargeted.com/profile?tab=2fa: name input + button + message area, no modal */
|
||||
$scope.newPasskeyName = '';
|
||||
$scope.webauthnMessage = '';
|
||||
$scope.webauthnMessageError = false;
|
||||
$scope.registerPasskeyLoading = false;
|
||||
|
||||
$scope.registerNewPasskey = function() {
|
||||
if (!window.cyberPanelWebAuthn) {
|
||||
alert('WebAuthn is not supported in this browser');
|
||||
if (!$scope.accountUsername) {
|
||||
$scope.webauthnMessage = 'Please select a user account first.';
|
||||
$scope.webauthnMessageError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var credentialName = prompt('Enter a name for this passkey:', 'Passkey ' + new Date().toLocaleDateString());
|
||||
if (!credentialName) return;
|
||||
|
||||
window.cyberPanelWebAuthn.registerPasskey($scope.accountUsername, credentialName)
|
||||
if (typeof window.cyberPanelWebAuthn === 'undefined') {
|
||||
$scope.webauthnMessage = 'WebAuthn script not loaded. Refresh the page (Ctrl+F5) and try again.';
|
||||
$scope.webauthnMessageError = true;
|
||||
return;
|
||||
}
|
||||
if (typeof window.cyberPanelWebAuthn.isSupported !== 'function' || !window.cyberPanelWebAuthn.isSupported()) {
|
||||
$scope.webauthnMessage = 'WebAuthn is not supported in this browser.';
|
||||
$scope.webauthnMessageError = true;
|
||||
return;
|
||||
}
|
||||
var name = ($scope.newPasskeyName || '').trim() || 'Security key';
|
||||
$scope.webauthnMessage = '';
|
||||
$scope.webauthnMessageError = false;
|
||||
$scope.registerPasskeyLoading = true;
|
||||
var username = $scope.accountUsername;
|
||||
window.cyberPanelWebAuthn.registerPasskey(username, name, { silent: true })
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
$scope.loadWebAuthnData();
|
||||
$scope.$apply();
|
||||
if (response && response.success) {
|
||||
$scope.webauthnMessage = 'Passkey registered successfully.';
|
||||
$scope.webauthnMessageError = false;
|
||||
$scope.newPasskeyName = '';
|
||||
$timeout(function() { $scope.loadWebAuthnData(); }, 0);
|
||||
} else {
|
||||
$scope.webauthnMessage = (response && response.error) ? response.error : 'Registration failed.';
|
||||
$scope.webauthnMessageError = true;
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
var msg = (error && error.message) ? error.message : 'Passkey registration failed.';
|
||||
if (error && error.name === 'NotAllowedError') msg = 'Registration was cancelled or timed out.';
|
||||
$scope.webauthnMessage = msg;
|
||||
$scope.webauthnMessageError = true;
|
||||
console.error('Error registering passkey:', error);
|
||||
})
|
||||
.finally(function() {
|
||||
$scope.registerPasskeyLoading = false;
|
||||
if (!$scope.$$phase && !$scope.$root.$$phase) { try { $scope.$apply(); } catch (e) { /* already applied */ } }
|
||||
});
|
||||
};
|
||||
|
||||
@@ -435,14 +483,11 @@ app.controller('modifyUser', function ($scope, $http) {
|
||||
$scope.webauthnTimeout = 60;
|
||||
$scope.webauthnCredentials = [];
|
||||
$scope.canAddCredential = true;
|
||||
|
||||
$scope.webauthnDataLoaded = false;
|
||||
// Load WebAuthn settings and credentials
|
||||
$scope.loadWebAuthnData();
|
||||
|
||||
qrCode.set({
|
||||
value: userDetails.otpauth
|
||||
});
|
||||
|
||||
if (qrCode) qrCode.set({ value: userDetails.otpauth });
|
||||
|
||||
$scope.userModificationLoading = true;
|
||||
$scope.acctDetailsFetched = false;
|
||||
@@ -537,8 +582,8 @@ app.controller('modifyUser', function ($scope, $http) {
|
||||
|
||||
$http.post(url, data, config).then(function(response) {
|
||||
ListInitialDatas(response);
|
||||
// Save WebAuthn settings after successful user modification
|
||||
if (response.data.saveStatus == 1) {
|
||||
// Save WebAuthn settings after successful user modification (only if WebAuthn script loaded)
|
||||
if (response.data.saveStatus == 1 && window.cyberPanelWebAuthn) {
|
||||
$scope.saveWebAuthnSettings();
|
||||
}
|
||||
}, cantLoadInitialDatas);
|
||||
@@ -554,7 +599,7 @@ app.controller('modifyUser', function ($scope, $http) {
|
||||
$scope.userModified = false;
|
||||
$scope.canotModifyUser = false; // hide modify error on success
|
||||
$scope.couldNotConnect = true;
|
||||
$scope.canotFetchDetails = true;
|
||||
$scope.canotFetchDetails = false; // hide "Cannot fetch details" on save success
|
||||
$scope.detailsFetched = true;
|
||||
$scope.userAccountsLimit = true;
|
||||
$scope.accountTypeView = true;
|
||||
@@ -592,7 +637,7 @@ app.controller('modifyUser', function ($scope, $http) {
|
||||
$scope.couldNotConnect = false;
|
||||
$scope.canotFetchDetails = true;
|
||||
$scope.detailsFetched = true;
|
||||
|
||||
$scope.errorMessage = (response && response.data && (response.data.error_message || response.data.message || response.data.errorMessage)) || 'Unknown error';
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -174,7 +174,9 @@ def getUserHomeDirectories(request):
|
||||
userID = request.session['userID']
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
|
||||
if currentACL['admin'] != 1 and currentACL['createNewUser'] != 1:
|
||||
# Same visibility as create user page: admins, ACL editors, and users allowed to create accounts
|
||||
if (currentACL['admin'] != 1 and currentACL['createNewUser'] != 1
|
||||
and currentACL.get('changeUserACL', 0) != 1):
|
||||
return JsonResponse({'status': 0, 'error_message': 'Unauthorized access'})
|
||||
|
||||
# Get active home directories (tables home_directories / user_home_mappings may not exist yet)
|
||||
|
||||
@@ -283,9 +283,11 @@
|
||||
<div class="form-col-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{% trans "Access Control List (ACL)" %}</label>
|
||||
<select ng-model="selectedACL" class="form-control">
|
||||
{# ng-init: Angular otherwise leaves selectedACL undefined until user picks an option, so POST omits ACL and user creation fails. #}
|
||||
<select ng-model="selectedACL" class="form-control"
|
||||
ng-init="selectedACL = selectedACL || '{{ default_acl_name|escapejs }}'">
|
||||
{% for items in aclNames %}
|
||||
<option>{{ items }}</option>
|
||||
<option value="{{ items|escape }}">{{ items }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="help-text">{% trans "Select the permission set for this user" %}</p>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
from django.shortcuts import render, redirect
|
||||
from django.http import HttpResponse
|
||||
from django.db import models
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from loginSystem.views import loadLoginPage
|
||||
from loginSystem.models import Administrator, ACL
|
||||
import json
|
||||
@@ -50,24 +51,31 @@ def viewProfile(request):
|
||||
return proc.render()
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def createUser(request):
|
||||
userID = request.session['userID']
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
|
||||
if currentACL['admin'] == 1:
|
||||
aclNames = ACLManager.unFileteredACLs()
|
||||
default_acl = aclNames[0] if aclNames else 'user'
|
||||
proc = httpProc(request, 'userManagment/createUser.html',
|
||||
{'aclNames': aclNames, 'securityLevels': SecurityLevel.list()})
|
||||
{'aclNames': aclNames, 'default_acl_name': default_acl,
|
||||
'securityLevels': SecurityLevel.list()})
|
||||
return proc.render()
|
||||
elif currentACL['changeUserACL'] == 1:
|
||||
aclNames = ACLManager.unFileteredACLs()
|
||||
default_acl = aclNames[0] if aclNames else 'user'
|
||||
proc = httpProc(request, 'userManagment/createUser.html',
|
||||
{'aclNames': aclNames, 'securityLevels': SecurityLevel.list()})
|
||||
{'aclNames': aclNames, 'default_acl_name': default_acl,
|
||||
'securityLevels': SecurityLevel.list()})
|
||||
return proc.render()
|
||||
elif currentACL['createNewUser'] == 1:
|
||||
aclNames = ['user']
|
||||
default_acl = 'user'
|
||||
proc = httpProc(request, 'userManagment/createUser.html',
|
||||
{'aclNames': aclNames, 'securityLevels': SecurityLevel.list()})
|
||||
{'aclNames': aclNames, 'default_acl_name': default_acl,
|
||||
'securityLevels': SecurityLevel.list()})
|
||||
return proc.render()
|
||||
else:
|
||||
return ACLManager.loadError()
|
||||
@@ -213,8 +221,18 @@ def submitUserCreation(request):
|
||||
email = data['email']
|
||||
userName = data['userName']
|
||||
password = data['password']
|
||||
websitesLimit = data['websitesLimit']
|
||||
selectedACL = data['selectedACL']
|
||||
try:
|
||||
websitesLimit = int(data['websitesLimit'])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
websitesLimit = 0
|
||||
selectedACL = data.get('selectedACL')
|
||||
if selectedACL is None or (isinstance(selectedACL, str) and not selectedACL.strip()):
|
||||
data_ret = {'status': 0, 'createStatus': 0,
|
||||
'error_message': 'Please select an access control list (ACL).'}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data, content_type='application/json')
|
||||
if isinstance(selectedACL, str):
|
||||
selectedACL = selectedACL.strip()
|
||||
selectedHomeDirectory = data.get('selectedHomeDirectory', '')
|
||||
|
||||
if ACLManager.CheckRegEx("^[\w'\-,.][^0-9_!¡?÷?¿/\\+=@#$%ˆ&*(){}|~<>;:[\]]{2,}$", firstName) == 0:
|
||||
|
||||
Reference in New Issue
Block a user